fix: speed up business portrait statistics
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz/business"
|
||||
)
|
||||
@@ -161,15 +162,13 @@ func (r *businessPortraitRepo) Statistics(ctx context.Context, scope business.Po
|
||||
if scope.Range.Hourly {
|
||||
tables = append(tables, "tab_user", "tab_spend")
|
||||
}
|
||||
for _, table := range tables {
|
||||
available, err := businessTableAvailable(db, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
result.Available = false
|
||||
return result, nil
|
||||
}
|
||||
available, err := businessTablesAvailable(db, tables...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
result.Available = false
|
||||
return result, nil
|
||||
}
|
||||
mode := 0
|
||||
if scope.IDCardDedup {
|
||||
@@ -177,49 +176,77 @@ func (r *businessPortraitRepo) Statistics(ctx context.Context, scope business.Po
|
||||
}
|
||||
begin, end := scope.Range.Begin.Format("2006-01-02"), scope.Range.End.Format("2006-01-02")
|
||||
previousBegin, previousEnd := scope.Range.PreviousBegin.Format("2006-01-02"), scope.Range.PreviousEnd.Format("2006-01-02")
|
||||
periods := []struct {
|
||||
begin, end string
|
||||
counts *business.PortraitCounts
|
||||
pay *string
|
||||
}{{begin, end, &result.Current, &result.CurrentPay}}
|
||||
if !scope.NoCompare {
|
||||
periods = append(periods, struct {
|
||||
begin, end string
|
||||
counts *business.PortraitCounts
|
||||
pay *string
|
||||
}{previousBegin, previousEnd, &result.Previous, &result.PreviousPay})
|
||||
// 合并本期和对比期的扫描;本月同期之间的日期空隙不能混入统计。
|
||||
group, queryCtx := errgroup.WithContext(ctx)
|
||||
group.SetLimit(2)
|
||||
queryDB := db.WithContext(queryCtx)
|
||||
summaryQuery := func(table string, dedup int) *gorm.DB {
|
||||
if scope.NoCompare {
|
||||
return portraitReportQuery(queryDB, scope, table, begin, end, dedup)
|
||||
}
|
||||
return portraitReportQuery(queryDB, scope, table, min(begin, previousBegin), max(end, previousEnd), dedup).
|
||||
Where("(r.stat_date >= ? AND r.stat_date <= ?) OR (r.stat_date >= ? AND r.stat_date <= ?)", begin, end, previousBegin, previousEnd)
|
||||
}
|
||||
for _, period := range periods {
|
||||
var rows []portraitCountPO
|
||||
q := portraitReportQuery(db, scope, "tab_sw_report_day_key", period.begin, period.end, mode)
|
||||
if err := q.Select("r.metric,COUNT(DISTINCT " + portraitIdentity(db, scope.IDCardDedup) + ") AS count").Group("r.metric").Scan(&rows).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
group.Go(func() error {
|
||||
var rows []struct {
|
||||
Metric int
|
||||
Current, Previous int64
|
||||
}
|
||||
*period.counts = toPortraitCounts(rows)
|
||||
if err := portraitReportQuery(db, scope, "tab_sw_report_day", period.begin, period.end, 0).Select("COALESCE(SUM(r.total_pay),0)").Scan(period.pay).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
identity := portraitIdentity(queryDB, scope.IDCardDedup)
|
||||
selectSQL := "r.metric,COUNT(DISTINCT CASE WHEN r.stat_date >= ? AND r.stat_date <= ? THEN " + identity + " END) AS current"
|
||||
args := []any{begin, end}
|
||||
if !scope.NoCompare {
|
||||
selectSQL += ",COUNT(DISTINCT CASE WHEN r.stat_date >= ? AND r.stat_date <= ? THEN " + identity + " END) AS previous"
|
||||
args = append(args, previousBegin, previousEnd)
|
||||
}
|
||||
}
|
||||
if scope.Range.Hourly {
|
||||
if err := portraitHourly(db, scope, result); err != nil {
|
||||
return nil, err
|
||||
if err := summaryQuery("tab_sw_report_day_key", mode).Select(selectSQL, args...).Group("r.metric").Scan(&rows).Error; err != nil {
|
||||
return business.ErrStorage
|
||||
}
|
||||
} else {
|
||||
if err := portraitDaily(db, scope, mode, begin, end, result); err != nil {
|
||||
return nil, err
|
||||
current, previous := make([]portraitCountPO, 0, len(rows)), make([]portraitCountPO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
current = append(current, portraitCountPO{Metric: row.Metric, Count: row.Current})
|
||||
previous = append(previous, portraitCountPO{Metric: row.Metric, Count: row.Previous})
|
||||
}
|
||||
}
|
||||
result.Current, result.Previous = toPortraitCounts(current), toPortraitCounts(previous)
|
||||
return nil
|
||||
})
|
||||
group.Go(func() error {
|
||||
var amounts struct{ Current, Previous string }
|
||||
selectSQL := "COALESCE(SUM(CASE WHEN r.stat_date >= ? AND r.stat_date <= ? THEN r.total_pay ELSE 0 END),0) AS current"
|
||||
args := []any{begin, end}
|
||||
if !scope.NoCompare {
|
||||
selectSQL += ",COALESCE(SUM(CASE WHEN r.stat_date >= ? AND r.stat_date <= ? THEN r.total_pay ELSE 0 END),0) AS previous"
|
||||
args = append(args, previousBegin, previousEnd)
|
||||
}
|
||||
if err := summaryQuery("tab_sw_report_day", 0).Select(selectSQL, args...).Scan(&amounts).Error; err != nil {
|
||||
return business.ErrStorage
|
||||
}
|
||||
result.CurrentPay = amounts.Current
|
||||
if !scope.NoCompare {
|
||||
result.PreviousPay = amounts.Previous
|
||||
}
|
||||
return nil
|
||||
})
|
||||
group.Go(func() error {
|
||||
if scope.Range.Hourly {
|
||||
return portraitHourly(queryDB, scope, result)
|
||||
}
|
||||
return portraitDaily(queryDB, scope, mode, begin, end, result)
|
||||
})
|
||||
if scope.RankLimit > 0 {
|
||||
register, err := portraitRankQuery(db, scope, mode, begin, end, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.RegisterRanks = register
|
||||
pay, err := portraitRankQuery(db, scope, 0, begin, end, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.PayRanks = pay
|
||||
group.Go(func() error {
|
||||
register, err := portraitRankQuery(queryDB, scope, mode, begin, end, false)
|
||||
result.RegisterRanks = register
|
||||
return err
|
||||
})
|
||||
group.Go(func() error {
|
||||
pay, err := portraitRankQuery(queryDB, scope, 0, begin, end, true)
|
||||
result.PayRanks = pay
|
||||
return err
|
||||
})
|
||||
}
|
||||
if err := group.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -247,11 +274,17 @@ func portraitDaily(db *gorm.DB, scope business.PortraitReportScope, mode int, be
|
||||
}
|
||||
func portraitHourly(db *gorm.DB, scope business.PortraitReportScope, result *business.PortraitStatistics) error {
|
||||
start, end := scope.Range.Begin.Unix(), scope.Range.End.AddDate(0, 0, 1).Unix()
|
||||
// register_time 是历史字符串列;数值比较不能利用其字符串索引。
|
||||
// 复用已有数值生成列和时间范围索引,未迁移的库保留 CAST 回退。
|
||||
registerTime, err := registrationTimeExpression(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, metric := range []struct {
|
||||
table, column, aggregate, condition string
|
||||
series map[string]string
|
||||
}{{"tab_user", "register_time", "COUNT(*)", "(r.puid = 0 OR r.puid IS NULL)", result.RegisterSeries}, {"tab_spend", "pay_time", "COALESCE(SUM(r.pay_amount),0)", "r.pay_status = 1", result.PaySeries}} {
|
||||
q := db.Table(metric.table+" r").Where("r."+metric.column+" >= ? AND r."+metric.column+" < ?", start, end).Where(metric.condition).Where("(r.source_db,r.promote_id) IN (?)", portraitPromoteScope(db, scope))
|
||||
}{{"tab_user", registerTime, "COUNT(*)", "(r.puid = 0 OR r.puid IS NULL)", result.RegisterSeries}, {"tab_spend", "r.pay_time", "COALESCE(SUM(r.pay_amount),0)", "r.pay_status = 1", result.PaySeries}} {
|
||||
q := db.Table(metric.table+" r").Where(metric.column+" >= ? AND "+metric.column+" < ?", start, end).Where(metric.condition).Where("(r.source_db,r.promote_id) IN (?)", portraitPromoteScope(db, scope))
|
||||
if len(scope.Sources) > 0 {
|
||||
q = q.Where("r.source_db IN ?", scope.Sources)
|
||||
}
|
||||
@@ -261,12 +294,12 @@ func portraitHourly(db *gorm.DB, scope business.PortraitReportScope, result *bus
|
||||
}
|
||||
if db.Dialector.Name() == "mysql" {
|
||||
// 保留原 Mapper 的数据库会话时区口径,最终实库验收同时核对时区配置。
|
||||
hour := "HOUR(FROM_UNIXTIME(r." + metric.column + "))"
|
||||
hour := "HOUR(FROM_UNIXTIME(" + metric.column + "))"
|
||||
if err := q.Select(hour + " AS hour," + metric.aggregate + " AS value").Group(hour).Scan(&rows).Error; err != nil {
|
||||
return business.ErrStorage
|
||||
}
|
||||
} else {
|
||||
if err := q.Select("CAST((r."+metric.column+" - ?) / 3600 AS INTEGER) AS hour,"+metric.aggregate+" AS value", start).Group("hour").Scan(&rows).Error; err != nil {
|
||||
if err := q.Select("CAST(("+metric.column+" - ?) / 3600 AS INTEGER) AS hour,"+metric.aggregate+" AS value", start).Group("hour").Scan(&rows).Error; err != nil {
|
||||
return business.ErrStorage
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user