From 430949ed5ffc1c4a2184350f5122200e31c566d7 Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Fri, 26 Dec 2025 12:48:52 +0800 Subject: [PATCH] feat: 优化门店数据结构分析功能 --- antis-ncc-admin/src/components/store-data-analysis-dialog.vue | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StoreDataAnalysisOutput.cs | 2 +- netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs | 461 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------- sql/优化仪表盘接口性能索引.sql | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/优化金三角业绩排行榜接口性能索引.sql | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/优化门店月度趋势接口性能索引.sql | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 635 insertions(+), 135 deletions(-) create mode 100644 sql/优化仪表盘接口性能索引.sql create mode 100644 sql/优化金三角业绩排行榜接口性能索引.sql create mode 100644 sql/优化门店月度趋势接口性能索引.sql diff --git a/antis-ncc-admin/src/components/store-data-analysis-dialog.vue b/antis-ncc-admin/src/components/store-data-analysis-dialog.vue index 8d2e7f4..bb20683 100644 --- a/antis-ncc-admin/src/components/store-data-analysis-dialog.vue +++ b/antis-ncc-admin/src/components/store-data-analysis-dialog.vue @@ -33,6 +33,22 @@ 开业:{{ formatDate(baseInfo.OpenTime) }} +
+ + 事业部:{{ baseInfo.BusinessUnit }} +
+
+ + 科技部:{{ baseInfo.TechDepartment }} +
+
+ + 教育部:{{ baseInfo.EducationDepartment }} +
+
+ + 大项目部:{{ baseInfo.MajorProjectDepartment }} +
@@ -501,34 +517,42 @@ export default { this.loading = true try { - // 第一步:先获取门店基础信息 + // 准备请求参数(所有接口共用) + const requestParams = { + storeId: this.storeId, + statisticsMonth: this.statisticsMonth || this.getCurrentMonth() + } + + // 第一步:获取门店基础信息(单独接口) console.log('开始获取门店基础信息,门店ID:', this.storeId) + const baseInfoRes = await request({ - url: `/api/Extend/LqMdxx/${this.storeId}`, - method: 'GET' + url: '/api/Extend/LqReport/get-store-base-info', + method: 'POST', + data: requestParams }) console.log('门店基础信息接口响应:', baseInfoRes) // 处理门店基础信息 if (baseInfoRes && baseInfoRes.code === 200 && baseInfoRes.data) { - const data = baseInfoRes.data - console.log('门店数据:', data) + const baseInfoData = baseInfoRes.data + console.log('门店基础信息数据:', baseInfoData) this.baseInfo = { - StoreId: data.id || this.storeId, - StoreName: data.dm || '—', - StoreCode: data.mdbm || '', - City: data.cs || '', - Address: data.dz || '', - Status: data.zxzt || '', - OpenTime: data.kysj || null, - EmployeeCount: data.zzrs || 0, - StoreType: (data.storeType != null ? data.storeType.toString() : '') || '', - StoreCategory: (data.storeCategory != null ? data.storeCategory.toString() : '') || '', - BusinessUnit: data.syb || '', - EducationDepartment: data.jyb || '', - TechDepartment: data.kjb || '', - MajorProjectDepartment: data.dxmb || '' + StoreId: baseInfoData.StoreId || this.storeId, + StoreName: baseInfoData.StoreName || '—', + StoreCode: baseInfoData.StoreCode || '', + City: baseInfoData.City || '', + Address: baseInfoData.Address || '', + Status: baseInfoData.Status || '', + OpenTime: baseInfoData.OpenTime || null, + EmployeeCount: baseInfoData.EmployeeCount || 0, + StoreType: baseInfoData.StoreType || '', + StoreCategory: baseInfoData.StoreCategory || '', + BusinessUnit: baseInfoData.BusinessUnit || '', + EducationDepartment: baseInfoData.EducationDepartment || '', + TechDepartment: baseInfoData.TechDepartment || '', + MajorProjectDepartment: baseInfoData.MajorProjectDepartment || '' } console.log('处理后的门店基础信息:', this.baseInfo) } else { @@ -538,14 +562,37 @@ export default { } else { this.$message.error('获取门店基础信息失败: 接口返回异常') } + // 如果获取失败,尝试从门店接口获取基本信息(但归属信息会是ID) + try { + const fallbackRes = await request({ + url: `/api/Extend/LqMdxx/${this.storeId}`, + method: 'GET' + }) + if (fallbackRes && fallbackRes.code === 200 && fallbackRes.data) { + const data = fallbackRes.data + this.baseInfo = { + StoreId: data.id || this.storeId, + StoreName: data.dm || '—', + StoreCode: data.mdbm || '', + City: data.cs || '', + Address: data.dz || '', + Status: data.zxzt || '', + OpenTime: data.kysj || null, + EmployeeCount: data.zzrs || 0, + StoreType: (data.storeType != null ? data.storeType.toString() : '') || '', + StoreCategory: (data.storeCategory != null ? data.storeCategory.toString() : '') || '', + BusinessUnit: '', // 备用方案:无法获取名称时留空 + EducationDepartment: '', + TechDepartment: '', + MajorProjectDepartment: '' + } + } + } catch (e) { + console.error('备用方案也失败:', e) + } } // 第二步:获取业绩概览 - const requestParams = { - storeId: this.storeId, - statisticsMonth: this.statisticsMonth || this.getCurrentMonth() - } - console.log('开始获取业绩概览,参数:', requestParams) const performanceRes = await request({ url: '/api/Extend/LqReport/get-store-performance-overview', diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StoreDataAnalysisOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StoreDataAnalysisOutput.cs index c91c8b0..294a495 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StoreDataAnalysisOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StoreDataAnalysisOutput.cs @@ -36,7 +36,7 @@ namespace NCC.Extend.Entitys.Dto.LqReport /// /// 品项分析 /// - public ItemAnalysis Item { get; set; } + public ItemAnalysis? Item { get; set; } /// /// 每日运营数据(当月) diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs index 24ac63a..7b0bdad 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs @@ -1130,28 +1130,47 @@ namespace NCC.Extend ? DateTime.Now : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59); - // 实时查询金三角业绩 - var sql = $@" + // 使用参数化查询,优化性能 + var parameters = new List + { + new SugarParameter("@StatisticsMonth", input.StatisticsMonth), + new SugarParameter("@StartDate", startDate), + new SugarParameter("@EndDate", endDateForQuery) + }; + + // 优化SQL:使用子查询先聚合数据,减少JOIN的数据量 + // 1. 先过滤有效业绩数据并按金三角ID聚合(减少数据量) + // 2. 再关联金三角设定和门店信息 + // 这样可以避免在大数据量下进行复杂的多表JOIN + var sql = @" SELECT jsj.F_Id as F_GoldTriangleId, jsj.jsj as F_GoldTriangleName, jsj.md as F_StoreId, - mdxx.dm as F_StoreName, - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) as F_TotalPerformance, - COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount + COALESCE(mdxx.dm, '') as F_StoreName, + COALESCE(performance.F_TotalPerformance, 0) as F_TotalPerformance, + COALESCE(performance.F_OrderCount, 0) as F_OrderCount FROM lq_ycsd_jsj jsj - INNER JOIN lq_jinsanjiao_user jsjUser ON jsj.F_Id = jsjUser.jsj_id - AND jsjUser.status = 'ACTIVE' - AND jsjUser.F_DeleteMark = 0 - INNER JOIN lq_kd_jksyj jksyj ON jsjUser.user_id = jksyj.jkszh - INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id + LEFT JOIN ( + -- 子查询:先过滤并聚合有效业绩数据,减少JOIN数据量 + SELECT + jsjUser.jsj_id, + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) as F_TotalPerformance, + COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount + FROM lq_kd_jksyj jksyj + INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id + INNER JOIN lq_jinsanjiao_user jsjUser ON jksyj.jkszh = jsjUser.user_id + WHERE jksyj.F_IsEffective = 1 + AND kd.F_IsEffective = 1 + AND kd.kdrq >= @StartDate + AND kd.kdrq <= @EndDate + AND jsjUser.status = 'ACTIVE' + AND jsjUser.F_DeleteMark = 0 + GROUP BY jsjUser.jsj_id + ) performance ON jsj.F_Id = performance.jsj_id LEFT JOIN lq_mdxx mdxx ON jsj.md = mdxx.F_Id - WHERE jsj.yf = '{input.StatisticsMonth}' - AND jksyj.F_IsEffective = 1 - AND kd.F_IsEffective = 1 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}' - GROUP BY jsj.F_Id, jsj.jsj, jsj.md, mdxx.dm + WHERE jsj.yf = @StatisticsMonth + AND performance.jsj_id IS NOT NULL ORDER BY F_TotalPerformance DESC"; if (input.TopCount > 0) @@ -1159,7 +1178,7 @@ namespace NCC.Extend sql += $" LIMIT {input.TopCount}"; } - var data = await _db.Ado.SqlQueryAsync(sql); + var data = await _db.Ado.SqlQueryAsync(sql, parameters); if (data == null || !data.Any()) { @@ -1320,31 +1339,39 @@ namespace NCC.Extend ? DateTime.Now : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59); + // 使用参数化查询,优化性能 + var parameters = new List + { + new SugarParameter("@StartDate", startDate), + new SugarParameter("@EndDate", endDateForQuery), + new SugarParameter("@StatisticsMonth", statisticsMonth) + }; + // 1. 门店业绩汇总(实时查询) // 先查询开单业绩 - var billingSql = $@" + var billingSql = @" SELECT COUNT(DISTINCT kd.djmd) as StoreCount, COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as TotalBillingAmount FROM lq_kd_kdjlb kd WHERE kd.F_IsEffective = 1 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; + AND kd.kdrq >= @StartDate + AND kd.kdrq <= @EndDate"; - var billingResult = await _db.Ado.SqlQueryAsync(billingSql); + var billingResult = await _db.Ado.SqlQueryAsync(billingSql, parameters); var storeCount = Convert.ToInt32(billingResult?.FirstOrDefault()?.StoreCount ?? 0); var totalBillingAmount = Convert.ToDecimal(billingResult?.FirstOrDefault()?.TotalBillingAmount ?? 0m); // 查询退卡金额 - var refundSql = $@" + var refundSql = @" SELECT COALESCE(SUM(CAST(COALESCE(F_ActualRefundAmount, tkje, 0) AS DECIMAL(18,2))), 0) as TotalRefundAmount FROM lq_hytk_hytk WHERE F_IsEffective = 1 - AND tksj >= '{startDate:yyyy-MM-dd 00:00:00}' - AND tksj <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; + AND tksj >= @StartDate + AND tksj <= @EndDate"; - var refundResult = await _db.Ado.SqlQueryAsync(refundSql); + var refundResult = await _db.Ado.SqlQueryAsync(refundSql, parameters); var totalRefundAmount = Convert.ToDecimal(refundResult?.FirstOrDefault()?.TotalRefundAmount ?? 0m); var totalPerformance = totalBillingAmount - totalRefundAmount; @@ -1363,7 +1390,7 @@ namespace NCC.Extend }.Select(x => (dynamic)x).ToList(); // 2. 健康师业绩汇总(实时查询) - var healthCoachPerformanceSql = $@" + var healthCoachPerformanceSql = @" SELECT COUNT(DISTINCT jks.jkszh) as HealthCoachCount, COALESCE(SUM(CAST(jks.jksyj AS DECIMAL(18,2))), 0) as TotalPerformance, @@ -1378,38 +1405,47 @@ namespace NCC.Extend INNER JOIN lq_kd_kdjlb kd ON jks.glkdbh = kd.F_Id WHERE jks.F_IsEffective = 1 AND kd.F_IsEffective = 1 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; + AND kd.kdrq >= @StartDate + AND kd.kdrq <= @EndDate"; - var healthCoachPerformance = await _db.Ado.SqlQueryAsync(healthCoachPerformanceSql); + var healthCoachPerformance = await _db.Ado.SqlQueryAsync(healthCoachPerformanceSql, parameters); - // 3. 金三角业绩汇总(实时查询) - var goldTrianglePerformanceSql = $@" + // 3. 金三角业绩汇总(实时查询)- 使用子查询优化性能 + var goldTrianglePerformanceSql = @" SELECT COUNT(DISTINCT jsj.F_Id) as GoldTriangleCount, - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) as TotalPerformance, - COUNT(DISTINCT jksyj.glkdbh) as TotalOrderCount, + COALESCE(SUM(performance.F_TotalPerformance), 0) as TotalPerformance, + COALESCE(SUM(performance.F_OrderCount), 0) as TotalOrderCount, CASE WHEN COUNT(DISTINCT jsj.F_Id) > 0 THEN - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) / COUNT(DISTINCT jsj.F_Id) + COALESCE(SUM(performance.F_TotalPerformance), 0) / COUNT(DISTINCT jsj.F_Id) ELSE 0 END as AvgPerformance FROM lq_ycsd_jsj jsj - INNER JOIN lq_jinsanjiao_user jsjUser ON jsj.F_Id = jsjUser.jsj_id - AND jsjUser.status = 'ACTIVE' - AND jsjUser.F_DeleteMark = 0 - INNER JOIN lq_kd_jksyj jksyj ON jsjUser.user_id = jksyj.jkszh - INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id - WHERE jsj.yf = '{statisticsMonth}' - AND jksyj.F_IsEffective = 1 - AND kd.F_IsEffective = 1 - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; - - var goldTrianglePerformance = await _db.Ado.SqlQueryAsync(goldTrianglePerformanceSql); + LEFT JOIN ( + -- 子查询:先过滤并聚合有效业绩数据,减少JOIN数据量 + SELECT + jsjUser.jsj_id, + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) as F_TotalPerformance, + COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount + FROM lq_kd_jksyj jksyj + INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id + INNER JOIN lq_jinsanjiao_user jsjUser ON jksyj.jkszh = jsjUser.user_id + WHERE jksyj.F_IsEffective = 1 + AND kd.F_IsEffective = 1 + AND kd.kdrq >= @StartDate + AND kd.kdrq <= @EndDate + AND jsjUser.status = 'ACTIVE' + AND jsjUser.F_DeleteMark = 0 + GROUP BY jsjUser.jsj_id + ) performance ON jsj.F_Id = performance.jsj_id + WHERE jsj.yf = @StatisticsMonth + AND performance.jsj_id IS NOT NULL"; + + var goldTrianglePerformance = await _db.Ado.SqlQueryAsync(goldTrianglePerformanceSql, parameters); // 4. 消耗业绩汇总(实时查询) - var consumePerformanceSql = $@" + var consumePerformanceSql = @" SELECT COUNT(*) as ConsumeRecordCount, COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as TotalConsumePerformance, @@ -1419,18 +1455,23 @@ namespace NCC.Extend FROM lq_xh_hyhk xh LEFT JOIN lq_xh_pxmx pxmx ON xh.F_Id = pxmx.F_ConsumeInfoId AND pxmx.F_IsEffective = 1 WHERE xh.F_IsEffective = 1 - AND xh.hksj >= '{startDate:yyyy-MM-dd 00:00:00}' - AND xh.hksj <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; + AND xh.hksj >= @StartDate + AND xh.hksj <= @EndDate"; - var consumePerformance = await _db.Ado.SqlQueryAsync(consumePerformanceSql); + var consumePerformance = await _db.Ado.SqlQueryAsync(consumePerformanceSql, parameters); // 5. 会员统计汇总 var lastMonth = DateTime.ParseExact(statisticsMonth, "yyyyMM", null).AddMonths(-1).ToString("yyyyMM"); - var memberStatisticsSql = $@" + var memberStatisticsParameters = new List + { + new SugarParameter("@statisticsMonth", statisticsMonth), + new SugarParameter("@lastMonth", lastMonth) + }; + var memberStatisticsSql = @" SELECT COUNT(*) as TotalMembers, SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = @statisticsMonth THEN 1 ELSE 0 END) as NewMembersThisMonth, - SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = '{lastMonth}' THEN 1 ELSE 0 END) as NewMembersLastMonth, + SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = @lastMonth THEN 1 ELSE 0 END) as NewMembersLastMonth, SUM(CASE WHEN F_SleepDays IS NULL OR F_SleepDays <= 3 THEN 1 ELSE 0 END) as ActiveMembers0_3, SUM(CASE WHEN F_SleepDays > 3 AND F_SleepDays < 60 THEN 1 ELSE 0 END) as ActiveMembers4_59, SUM(CASE WHEN F_LastVisitTime IS NOT NULL AND DATEDIFF(NOW(), F_LastVisitTime) <= 30 THEN 1 ELSE 0 END) as ActiveMembers30d, @@ -1447,7 +1488,7 @@ namespace NCC.Extend WHERE F_IsEffective = 1 AND khlx = '3'"; - var memberStatistics = await _db.Ado.SqlQueryAsync(memberStatisticsSql, new { statisticsMonth }); + var memberStatistics = await _db.Ado.SqlQueryAsync(memberStatisticsSql, memberStatisticsParameters); var memberStats = memberStatistics.FirstOrDefault(); // 6. 会员类型分布统计 @@ -1496,12 +1537,13 @@ namespace NCC.Extend FROM lq_kd_kdjlb kd INNER JOIN lq_khxx kh ON kh.F_Id = kd.kdhy WHERE kd.F_IsEffective = 1 - AND DATE_FORMAT(kd.kdrq, '%Y%m') = @statisticsMonth + AND kd.kdrq >= @StartDate + AND kd.kdrq <= @EndDate GROUP BY kh.F_Id, kh.Khmc ORDER BY Amount DESC LIMIT 1"; - var topBilling = (await _db.Ado.SqlQueryAsync(topBillingSql, new { statisticsMonth })).FirstOrDefault(); + var topBilling = (await _db.Ado.SqlQueryAsync(topBillingSql, parameters)).FirstOrDefault(); var topConsumeSql = @" SELECT @@ -1511,12 +1553,13 @@ namespace NCC.Extend FROM lq_xh_hyhk xh INNER JOIN lq_khxx kh ON kh.F_Id = xh.hy WHERE xh.F_IsEffective = 1 - AND DATE_FORMAT(xh.hksj, '%Y%m') = @statisticsMonth + AND xh.hksj >= @StartDate + AND xh.hksj <= @EndDate GROUP BY kh.F_Id, kh.Khmc ORDER BY Amount DESC LIMIT 1"; - var topConsume = (await _db.Ado.SqlQueryAsync(topConsumeSql, new { statisticsMonth })).FirstOrDefault(); + var topConsume = (await _db.Ado.SqlQueryAsync(topConsumeSql, parameters)).FirstOrDefault(); var totalMembers = Convert.ToInt32(memberStats?.TotalMembers ?? 0); var activeMembers0_3 = Convert.ToInt32(memberStats?.ActiveMembers0_3 ?? 0); @@ -4514,6 +4557,125 @@ namespace NCC.Extend #region 门店数据结构分析 /// + /// 获取门店基础信息 + /// + /// + /// 获取单个门店的基础信息,包括门店基本信息、归属信息(事业部、科技部、教育部、大项目部)等 + /// + /// 示例请求: + /// ```json + /// { + /// "storeId": "门店ID", + /// "statisticsMonth": "202512" + /// } + /// ``` + /// + /// 参数说明: + /// - storeId: 门店ID(必填) + /// - statisticsMonth: 统计月份(YYYYMM格式,可选,不传则默认为当前月份) + /// + /// 查询参数 + /// 门店基础信息 + /// 成功返回基础信息 + /// 参数错误 + /// 服务器错误 + [HttpPost("get-store-base-info")] + public async Task GetStoreBaseInfo(StoreDataAnalysisInput input) + { + try + { + if (string.IsNullOrWhiteSpace(input.StoreId)) + { + throw NCCException.Oh("门店ID不能为空"); + } + + // 确定统计月份 + var statisticsMonth = input.StatisticsMonth; + if (string.IsNullOrWhiteSpace(statisticsMonth)) + { + statisticsMonth = DateTime.Now.ToString("yyyyMM"); + } + + // 1. 获取门店基础信息 + var store = await _db.Queryable() + .Where(x => x.Id == input.StoreId) + .FirstAsync(); + + if (store == null) + { + throw NCCException.Oh("门店不存在"); + } + + // 2. 获取本月归属信息(优先从门店目标表获取,如果不存在则从门店表获取) + var target = await _db.Queryable() + .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth) + .FirstAsync(); + + string businessUnitName = ""; + string techDepartmentName = ""; + string educationDepartmentName = ""; + string majorProjectDepartmentName = ""; + + // 从门店目标表获取归属ID + var businessUnitId = target?.BusinessUnit ?? store.Syb; + var techDepartmentId = target?.TechDepartment ?? store.Kjb; + var educationDepartmentId = target?.EducationDepartment ?? store.Jyb; + var majorProjectDepartmentId = target?.MajorProjectDepartment ?? store.Dxmb; + + // 关联组织表获取归属名称(批量查询优化性能) + var organizeIds = new List(); + if (!string.IsNullOrEmpty(businessUnitId)) organizeIds.Add(businessUnitId); + if (!string.IsNullOrEmpty(techDepartmentId)) organizeIds.Add(techDepartmentId); + if (!string.IsNullOrEmpty(educationDepartmentId)) organizeIds.Add(educationDepartmentId); + if (!string.IsNullOrEmpty(majorProjectDepartmentId)) organizeIds.Add(majorProjectDepartmentId); + + var organizeDict = new Dictionary(); + if (organizeIds.Any()) + { + var organizes = await _db.Queryable() + .Where(x => organizeIds.Contains(x.Id)) + .Select(x => new { x.Id, x.FullName }) + .ToListAsync(); + organizeDict = organizes.ToDictionary(x => x.Id, x => x.FullName ?? ""); + } + + businessUnitName = !string.IsNullOrEmpty(businessUnitId) && organizeDict.ContainsKey(businessUnitId) + ? organizeDict[businessUnitId] : ""; + techDepartmentName = !string.IsNullOrEmpty(techDepartmentId) && organizeDict.ContainsKey(techDepartmentId) + ? organizeDict[techDepartmentId] : ""; + educationDepartmentName = !string.IsNullOrEmpty(educationDepartmentId) && organizeDict.ContainsKey(educationDepartmentId) + ? organizeDict[educationDepartmentId] : ""; + majorProjectDepartmentName = !string.IsNullOrEmpty(majorProjectDepartmentId) && organizeDict.ContainsKey(majorProjectDepartmentId) + ? organizeDict[majorProjectDepartmentId] : ""; + + var baseInfo = new StoreBaseInfo + { + StoreId = store.Id, + StoreName = store.Dm ?? "未知门店", + StoreCode = store.Mdbm ?? "", + City = store.Cs ?? "", + Address = store.Dz ?? "", + Status = store.Zxzt ?? "", + OpenTime = store.Kysj, + EmployeeCount = store.Zzrs, + StoreType = store.StoreType?.ToString() ?? "", + StoreCategory = store.StoreCategory?.ToString() ?? "", + BusinessUnit = businessUnitName, + EducationDepartment = educationDepartmentName, + TechDepartment = techDepartmentName, + MajorProjectDepartment = majorProjectDepartmentName + }; + + return baseInfo; + } + catch (Exception ex) + { + _logger.LogError(ex, $"获取门店基础信息失败 - 门店ID: {input?.StoreId}, 月份: {input?.StatisticsMonth}"); + throw NCCException.Oh($"获取门店基础信息失败: {ex.Message}"); + } + } + + /// /// 获取门店数据结构分析 /// /// @@ -4573,6 +4735,49 @@ namespace NCC.Extend throw NCCException.Oh("门店不存在"); } + // 2.4 目标业绩(从门店目标表获取) + var target = await _db.Queryable() + .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth) + .FirstAsync(); + + // 获取本月归属信息(优先从门店目标表获取,如果不存在则从门店表获取) + string businessUnitName = ""; + string techDepartmentName = ""; + string educationDepartmentName = ""; + string majorProjectDepartmentName = ""; + + // 从门店目标表获取归属ID + var businessUnitId = target?.BusinessUnit ?? store.Syb; + var techDepartmentId = target?.TechDepartment ?? store.Kjb; + var educationDepartmentId = target?.EducationDepartment ?? store.Jyb; + var majorProjectDepartmentId = target?.MajorProjectDepartment ?? store.Dxmb; + + // 关联组织表获取归属名称(批量查询优化性能) + var organizeIds = new List(); + if (!string.IsNullOrEmpty(businessUnitId)) organizeIds.Add(businessUnitId); + if (!string.IsNullOrEmpty(techDepartmentId)) organizeIds.Add(techDepartmentId); + if (!string.IsNullOrEmpty(educationDepartmentId)) organizeIds.Add(educationDepartmentId); + if (!string.IsNullOrEmpty(majorProjectDepartmentId)) organizeIds.Add(majorProjectDepartmentId); + + var organizeDict = new Dictionary(); + if (organizeIds.Any()) + { + var organizes = await _db.Queryable() + .Where(x => organizeIds.Contains(x.Id)) + .Select(x => new { x.Id, x.FullName }) + .ToListAsync(); + organizeDict = organizes.ToDictionary(x => x.Id, x => x.FullName ?? ""); + } + + businessUnitName = !string.IsNullOrEmpty(businessUnitId) && organizeDict.ContainsKey(businessUnitId) + ? organizeDict[businessUnitId] : ""; + techDepartmentName = !string.IsNullOrEmpty(techDepartmentId) && organizeDict.ContainsKey(techDepartmentId) + ? organizeDict[techDepartmentId] : ""; + educationDepartmentName = !string.IsNullOrEmpty(educationDepartmentId) && organizeDict.ContainsKey(educationDepartmentId) + ? organizeDict[educationDepartmentId] : ""; + majorProjectDepartmentName = !string.IsNullOrEmpty(majorProjectDepartmentId) && organizeDict.ContainsKey(majorProjectDepartmentId) + ? organizeDict[majorProjectDepartmentId] : ""; + var baseInfo = new StoreBaseInfo { StoreId = store.Id, @@ -4585,10 +4790,10 @@ namespace NCC.Extend EmployeeCount = store.Zzrs, StoreType = store.StoreType?.ToString() ?? "", StoreCategory = store.StoreCategory?.ToString() ?? "", - BusinessUnit = store.Syb ?? "", - EducationDepartment = store.Jyb ?? "", - TechDepartment = store.Kjb ?? "", - MajorProjectDepartment = store.Dxmb ?? "" + BusinessUnit = businessUnitName, + EducationDepartment = educationDepartmentName, + TechDepartment = techDepartmentName, + MajorProjectDepartment = majorProjectDepartmentName }; // 2. 获取业绩概览 @@ -4598,7 +4803,7 @@ namespace NCC.Extend .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime) .SumAsync(x => (decimal?)x.Sfyj) ?? 0m; - var billingCount = await _db.Queryable() + int billingCount = await _db.Queryable() .Where(x => x.Djmd == input.StoreId && x.IsEffective == 1) .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime) .CountAsync(); @@ -4609,7 +4814,7 @@ namespace NCC.Extend .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime) .SumAsync(x => (decimal?)x.Xfje) ?? 0m; - var consumeCount = await _db.Queryable() + int consumeCount = await _db.Queryable() .Where(x => x.Md == input.StoreId && x.IsEffective == 1) .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime) .CountAsync(); @@ -4620,16 +4825,11 @@ namespace NCC.Extend .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date) .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m; - var refundCount = await _db.Queryable() + int refundCount = await _db.Queryable() .Where(x => x.Md == input.StoreId && x.IsEffective == 1) .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date) .CountAsync(); - // 2.4 目标业绩(从门店目标表获取) - var target = await _db.Queryable() - .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth) - .FirstAsync(); - var targetPerformance = target?.StoreTarget ?? store.Xsyj ?? 0m; // 2.5 剩余权益总额 @@ -4697,20 +4897,19 @@ namespace NCC.Extend AND consume.Md = '{input.StoreId}' AND DATE(consume.hksj) >= '{startDate:yyyy-MM-dd}' AND DATE(consume.hksj) <= '{endDate:yyyy-MM-dd}' - GROUP BY consume.Md", - new { startDate, endDate }); + GROUP BY consume.Md"); var operation = new OperationMetrics(); if (dailyStats != null && dailyStats.Any()) { var stats = dailyStats.FirstOrDefault(); - operation.HeadCount = Convert.ToInt32(Convert.ToDecimal(stats?.HeadCount ?? 0)); - operation.PersonCount = Convert.ToInt32(Convert.ToDecimal(stats?.PersonCount ?? 0)); - operation.ProjectCount = Convert.ToInt32(Convert.ToDecimal(stats?.ProjectCount ?? 0)); - operation.TotalProjectCount = Convert.ToDecimal(stats?.TotalProjectCount ?? 0); - operation.OriginalProjectCount = Convert.ToDecimal(stats?.OriginalProjectCount ?? 0); - operation.OvertimeProjectCount = Convert.ToDecimal(stats?.OvertimeProjectCount ?? 0); - operation.AccompaniedProjectCount = Convert.ToDecimal(stats?.AccompaniedProjectCount ?? 0); + operation.HeadCount = Convert.ToInt32(Convert.ToDecimal(stats?.HeadCount ?? 0m)); + operation.PersonCount = Convert.ToInt32(Convert.ToDecimal(stats?.PersonCount ?? 0m)); + operation.ProjectCount = Convert.ToInt32(Convert.ToDecimal(stats?.ProjectCount ?? 0m)); + operation.TotalProjectCount = Convert.ToDecimal(stats?.TotalProjectCount ?? 0m); + operation.OriginalProjectCount = Convert.ToDecimal(stats?.OriginalProjectCount ?? 0m); + operation.OvertimeProjectCount = Convert.ToDecimal(stats?.OvertimeProjectCount ?? 0m); + operation.AccompaniedProjectCount = Convert.ToDecimal(stats?.AccompaniedProjectCount ?? 0m); operation.AvgAmountPerPerson = operation.PersonCount > 0 ? consumeAmount / (decimal)operation.PersonCount : 0m; operation.AvgAmountPerProject = operation.ProjectCount > 0 ? consumeAmount / (decimal)operation.ProjectCount : 0m; operation.AvgProjectPerHead = operation.HeadCount > 0 ? (decimal)operation.ProjectCount / (decimal)operation.HeadCount : 0m; @@ -4730,8 +4929,7 @@ namespace NCC.Extend FROM lq_khxx WHERE F_IsEffective = 1 AND gsmd = '{input.StoreId}' - AND khlx = '3'", - new { statisticsMonth }); + AND khlx = '3'"); var member = new MemberAnalysis(); if (memberStats != null && memberStats.Any()) @@ -5052,7 +5250,7 @@ namespace NCC.Extend GROUP BY kd.djmd, refund.RefundAmount ORDER BY (COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) - COALESCE(refund.RefundAmount, 0)) DESC"); - var performanceRanking = 1; + int performanceRanking = 1; int totalStoreCount = 0; if (allStoresPerformance != null) { @@ -5074,7 +5272,7 @@ namespace NCC.Extend .Select(x => x.Id) .ToListAsync(); - var sameTypeStoreCount = sameTypeStores.Count; + int sameTypeStoreCount = sameTypeStores.Count; var avgPerformanceSameType = 0m; if (sameTypeStoreCount > 0) { @@ -5128,7 +5326,7 @@ namespace NCC.Extend .ToListAsync(); } - var sameOrgStoreCount = sameOrgStoreIds.Count; + int sameOrgStoreCount = sameOrgStoreIds.Count; var avgPerformanceSameOrg = 0m; if (sameOrgStoreCount > 0) { @@ -5818,41 +6016,84 @@ namespace NCC.Extend throw NCCException.Oh("门店ID不能为空"); } + // 计算时间范围:近12个月 + var endDate = DateTime.Now; + var startDate = endDate.AddMonths(-11); + var startMonth = new DateTime(startDate.Year, startDate.Month, 1); + + // 使用参数化查询,一次性获取12个月的数据 + var parameters = new List + { + new SugarParameter("@StoreId", input.StoreId), + new SugarParameter("@StartDate", startMonth), + new SugarParameter("@EndDate", endDate) + }; + + // 优化:使用单个SQL查询获取所有月份的开单业绩 + var billingSql = @" + SELECT + DATE_FORMAT(kd.kdrq, '%Y%m') as Month, + COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as BillingPerformance + FROM lq_kd_kdjlb kd + WHERE kd.djmd = @StoreId + AND kd.F_IsEffective = 1 + AND kd.kdrq >= @StartDate + AND kd.kdrq <= @EndDate + GROUP BY DATE_FORMAT(kd.kdrq, '%Y%m') + ORDER BY Month"; + + var billingData = await _db.Ado.SqlQueryAsync(billingSql, parameters); + var billingDict = billingData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.BillingPerformance ?? 0m)); + + // 优化:使用单个SQL查询获取所有月份的消耗业绩 + var consumeSql = @" + SELECT + DATE_FORMAT(xh.hksj, '%Y%m') as Month, + COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as ConsumePerformance + FROM lq_xh_hyhk xh + WHERE xh.md = @StoreId + AND xh.F_IsEffective = 1 + AND xh.hksj >= @StartDate + AND xh.hksj <= @EndDate + GROUP BY DATE_FORMAT(xh.hksj, '%Y%m') + ORDER BY Month"; + + var consumeData = await _db.Ado.SqlQueryAsync(consumeSql, parameters); + var consumeDict = consumeData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.ConsumePerformance ?? 0m)); + + // 优化:使用单个SQL查询获取所有月份的退卡金额 + var refundSql = @" + SELECT + DATE_FORMAT(tk.tksj, '%Y%m') as Month, + COALESCE(SUM(CAST(COALESCE(tk.F_ActualRefundAmount, tk.tkje, 0) AS DECIMAL(18,2))), 0) as RefundAmount + FROM lq_hytk_hytk tk + WHERE tk.md = @StoreId + AND tk.F_IsEffective = 1 + AND tk.tksj >= @StartDate + AND tk.tksj <= @EndDate + GROUP BY DATE_FORMAT(tk.tksj, '%Y%m') + ORDER BY Month"; + + var refundData = await _db.Ado.SqlQueryAsync(refundSql, parameters); + var refundDict = refundData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.RefundAmount ?? 0m)); + + // 构建12个月的趋势数据 var monthlyTrend = new List(); for (int i = 11; i >= 0; i--) { var trendMonth = DateTime.Now.AddMonths(-i); var trendMonthStr = trendMonth.ToString("yyyyMM"); - var trendStartDate = new DateTime(trendMonth.Year, trendMonth.Month, 1); - var trendEndDate = trendStartDate.AddMonths(1).AddDays(-1); - var trendEndDateTime = trendEndDate.Date.AddDays(1).AddSeconds(-1); - if (trendMonthStr == DateTime.Now.ToString("yyyyMM")) - { - trendEndDateTime = DateTime.Now; - } - - var trendBilling = await _db.Queryable() - .Where(x => x.Djmd == input.StoreId && x.IsEffective == 1) - .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= trendStartDate && x.Kdrq.Value <= trendEndDateTime) - .SumAsync(x => (decimal?)x.Sfyj) ?? 0m; - - var trendConsume = await _db.Queryable() - .Where(x => x.Md == input.StoreId && x.IsEffective == 1) - .Where(x => x.Hksj.HasValue && x.Hksj.Value >= trendStartDate && x.Hksj.Value <= trendEndDateTime) - .SumAsync(x => (decimal?)x.Xfje) ?? 0m; - - var trendRefund = await _db.Queryable() - .Where(x => x.Md == input.StoreId && x.IsEffective == 1) - .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= trendStartDate.Date && x.Tksj.Value.Date <= trendEndDate.Date) - .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? (x.Tkje ?? 0))) ?? 0m; + var billing = billingDict.GetValueOrDefault(trendMonthStr, 0m); + var consume = consumeDict.GetValueOrDefault(trendMonthStr, 0m); + var refund = refundDict.GetValueOrDefault(trendMonthStr, 0m); monthlyTrend.Add(new MonthlyTrendPoint { Month = trendMonthStr, - BillingPerformance = trendBilling, - ConsumePerformance = trendConsume, - NetPerformance = trendBilling - trendRefund + BillingPerformance = billing, + ConsumePerformance = consume, + NetPerformance = billing - refund }); } @@ -5951,7 +6192,7 @@ namespace NCC.Extend .Select(x => x.Id) .ToListAsync(); - int sameTypeStoreCount = sameTypeStores != null ? sameTypeStores.Count : 0; + var sameTypeStoreCount = sameTypeStores != null ? sameTypeStores.Count : 0; var avgPerformanceSameType = 0m; if (sameTypeStoreCount > 0) { @@ -6005,7 +6246,7 @@ namespace NCC.Extend .ToListAsync(); } - int sameOrgStoreCount = sameOrgStoreIds != null ? sameOrgStoreIds.Count : 0; + var sameOrgStoreCount = sameOrgStoreIds != null ? sameOrgStoreIds.Count : 0; var avgPerformanceSameOrg = 0m; if (sameOrgStoreCount > 0) { diff --git a/sql/优化仪表盘接口性能索引.sql b/sql/优化仪表盘接口性能索引.sql new file mode 100644 index 0000000..8f2d76c --- /dev/null +++ b/sql/优化仪表盘接口性能索引.sql @@ -0,0 +1,83 @@ +-- ============================================ +-- 优化 get-dashboard-data 接口性能的索引 +-- ============================================ +-- 说明:这些索引专门为仪表盘接口优化 +-- 执行前请检查索引是否已存在,避免重复创建 + +-- ============================================ +-- 1. lq_kd_kdjlb (开单记录表) 索引 +-- ============================================ +-- 用于:门店业绩汇总、健康师业绩汇总、最高开单金额会员查询 +-- 查询条件:F_IsEffective, kdrq (时间范围), djmd (门店ID) + +-- 索引:用于时间范围查询和门店统计(如果已存在 idx_kd_kdjlb_store_date_effective 可跳过) +-- CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_effective +-- ON lq_kd_kdjlb(djmd, kdrq, F_IsEffective); + +-- ============================================ +-- 2. lq_hytk_hytk (退卡表) 索引 +-- ============================================ +-- 用于:退卡金额统计 +-- 查询条件:F_IsEffective, tksj (退卡时间) + +-- 索引:用于时间范围查询 +CREATE INDEX IF NOT EXISTS idx_hytk_effective_date +ON lq_hytk_hytk(F_IsEffective, tksj); + +-- ============================================ +-- 3. lq_xh_hyhk (耗卡记录表) 索引 +-- ============================================ +-- 用于:消耗业绩汇总、最高消耗金额会员查询 +-- 查询条件:F_IsEffective, hksj (耗卡时间), hy (会员ID) + +-- 索引1:用于时间范围查询 +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_effective_date +ON lq_xh_hyhk(F_IsEffective, hksj); + +-- 索引2:用于会员统计(补充) +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_member_effective +ON lq_xh_hyhk(hy, F_IsEffective, hksj); + +-- ============================================ +-- 4. lq_xh_pxmx (耗卡品项明细表) 索引 +-- ============================================ +-- 用于:消耗业绩汇总(JOIN) +-- 查询条件:F_ConsumeInfoId, F_IsEffective + +-- 索引:用于JOIN(如果已存在可跳过) +-- CREATE INDEX IF NOT EXISTS idx_xh_pxmx_consume_effective +-- ON lq_xh_pxmx(F_ConsumeInfoId, F_IsEffective); + +-- ============================================ +-- 5. lq_khxx (客户信息表) 索引 +-- ============================================ +-- 用于:会员统计汇总、会员类型分布、最高剩余权益会员 +-- 查询条件:F_IsEffective, khlx (客户类型), F_CreateTime, F_SleepDays, F_RemainingRightsAmount + +-- 索引1:用于会员类型和有效性过滤(最常用) +CREATE INDEX IF NOT EXISTS idx_khxx_type_effective +ON lq_khxx(khlx, F_IsEffective); + +-- 索引2:用于创建时间统计(新会员统计) +CREATE INDEX IF NOT EXISTS idx_khxx_create_time +ON lq_khxx(F_CreateTime, F_IsEffective, khlx); + +-- 索引3:用于剩余权益排序(最高剩余权益会员) +CREATE INDEX IF NOT EXISTS idx_khxx_remaining_effective +ON lq_khxx(F_RemainingRightsAmount, F_IsEffective); + +-- ============================================ +-- 6. 验证索引创建 +-- ============================================ +-- 验证索引是否创建成功 +-- SELECT +-- TABLE_NAME, +-- INDEX_NAME, +-- COLUMN_NAME, +-- SEQ_IN_INDEX +-- FROM INFORMATION_SCHEMA.STATISTICS +-- WHERE TABLE_SCHEMA = DATABASE() +-- AND TABLE_NAME IN ('lq_kd_kdjlb', 'lq_hytk_hytk', 'lq_xh_hyhk', 'lq_xh_pxmx', 'lq_khxx') +-- AND INDEX_NAME LIKE 'idx_%' +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX; + diff --git a/sql/优化金三角业绩排行榜接口性能索引.sql b/sql/优化金三角业绩排行榜接口性能索引.sql new file mode 100644 index 0000000..e6eb489 --- /dev/null +++ b/sql/优化金三角业绩排行榜接口性能索引.sql @@ -0,0 +1,67 @@ +-- ============================================ +-- 优化 get-gold-triangle-performance-ranking 接口性能的索引 +-- ============================================ +-- 说明:这些索引专门为金三角业绩排行榜接口优化 +-- 执行前请检查索引是否已存在,避免重复创建 + +-- ============================================ +-- 1. lq_ycsd_jsj (金三角设定表) 索引 +-- ============================================ +-- 用于:WHERE jsj.yf = @StatisticsMonth 查询条件 +-- 查询条件:yf (月份) + +-- 索引:用于月份查询(最常用) +CREATE INDEX IF NOT EXISTS idx_ycsd_jsj_yf +ON lq_ycsd_jsj(yf); + +-- ============================================ +-- 2. lq_jinsanjiao_user (金三角用户绑定表) 索引 +-- ============================================ +-- 用于:JOIN和过滤条件 +-- 查询条件:status = 'ACTIVE', F_DeleteMark = 0, user_id (用于JOIN) + +-- 索引1:用于JOIN和状态过滤(最常用) +CREATE INDEX IF NOT EXISTS idx_jinsanjiao_user_user_status_delete +ON lq_jinsanjiao_user(user_id, status, F_DeleteMark); + +-- 索引2:用于反向JOIN(jsj_id用于关联金三角设定) +CREATE INDEX IF NOT EXISTS idx_jinsanjiao_user_jsj_status_delete +ON lq_jinsanjiao_user(jsj_id, status, F_DeleteMark); + +-- ============================================ +-- 3. lq_kd_jksyj (开单健康师业绩表) 索引 +-- ============================================ +-- 用于:JOIN和过滤条件 +-- 查询条件:jkszh (用于JOIN), F_IsEffective = 1 + +-- 索引:用于JOIN和有效性过滤 +CREATE INDEX IF NOT EXISTS idx_kd_jksyj_jkszh_effective +ON lq_kd_jksyj(jkszh, F_IsEffective); + +-- ============================================ +-- 4. lq_kd_kdjlb (开单记录表) 索引 +-- ============================================ +-- 用于:JOIN和过滤条件 +-- 查询条件:F_Id (用于JOIN), F_IsEffective = 1, kdrq (时间范围) + +-- 索引:用于JOIN、有效性过滤和时间范围查询 +-- 注意:如果 idx_kd_kdjlb_store_date_effective 已存在,此索引可能冗余 +-- 但为了JOIN性能,保留此索引 +CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_id_effective_date +ON lq_kd_kdjlb(F_Id, F_IsEffective, kdrq); + +-- ============================================ +-- 5. 验证索引创建 +-- ============================================ +-- 验证索引是否创建成功 +-- SELECT +-- TABLE_NAME, +-- INDEX_NAME, +-- COLUMN_NAME, +-- SEQ_IN_INDEX +-- FROM INFORMATION_SCHEMA.STATISTICS +-- WHERE TABLE_SCHEMA = DATABASE() +-- AND TABLE_NAME IN ('lq_ycsd_jsj', 'lq_jinsanjiao_user', 'lq_kd_jksyj', 'lq_kd_kdjlb') +-- AND INDEX_NAME LIKE 'idx_%' +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX; + diff --git a/sql/优化门店月度趋势接口性能索引.sql b/sql/优化门店月度趋势接口性能索引.sql new file mode 100644 index 0000000..074b291 --- /dev/null +++ b/sql/优化门店月度趋势接口性能索引.sql @@ -0,0 +1,62 @@ +-- ============================================ +-- 优化 get-store-monthly-trend 接口性能的索引 +-- ============================================ +-- 说明:这些索引专门为门店月度趋势接口优化 +-- 执行前请检查索引是否已存在,避免重复创建 + +-- ============================================ +-- 1. lq_kd_kdjlb (开单记录表) 索引 +-- ============================================ +-- 用于:开单业绩按月统计 +-- 查询条件:djmd (门店ID), F_IsEffective, kdrq (开单日期) +-- 分组:DATE_FORMAT(kdrq, '%Y%m') + +-- 索引:用于门店+时间范围查询(如果已存在 idx_kd_kdjlb_store_date_effective 可跳过) +-- CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_effective +-- ON lq_kd_kdjlb(djmd, kdrq, F_IsEffective); + +-- 补充索引:用于月份分组查询(优化GROUP BY性能) +CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_month +ON lq_kd_kdjlb(djmd, F_IsEffective, kdrq); + +-- ============================================ +-- 2. lq_xh_hyhk (耗卡记录表) 索引 +-- ============================================ +-- 用于:消耗业绩按月统计 +-- 查询条件:md (门店ID), F_IsEffective, hksj (耗卡时间) +-- 分组:DATE_FORMAT(hksj, '%Y%m') + +-- 索引:用于门店+时间范围查询 +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_store_date_month +ON lq_xh_hyhk(md, F_IsEffective, hksj); + +-- ============================================ +-- 3. lq_hytk_hytk (退卡表) 索引 +-- ============================================ +-- 用于:退卡金额按月统计 +-- 查询条件:md (门店ID), F_IsEffective, tksj (退卡时间) +-- 分组:DATE_FORMAT(tksj, '%Y%m') + +-- 索引:用于门店+时间范围查询(如果已存在 idx_hytk_effective_date 可跳过) +-- CREATE INDEX IF NOT EXISTS idx_hytk_effective_date +-- ON lq_hytk_hytk(F_IsEffective, tksj); + +-- 补充索引:用于门店+时间范围查询(优化门店维度查询) +CREATE INDEX IF NOT EXISTS idx_hytk_store_date_month +ON lq_hytk_hytk(md, F_IsEffective, tksj); + +-- ============================================ +-- 4. 验证索引创建 +-- ============================================ +-- 验证索引是否创建成功 +-- SELECT +-- TABLE_NAME, +-- INDEX_NAME, +-- COLUMN_NAME, +-- SEQ_IN_INDEX +-- FROM INFORMATION_SCHEMA.STATISTICS +-- WHERE TABLE_SCHEMA = DATABASE() +-- AND TABLE_NAME IN ('lq_kd_kdjlb', 'lq_xh_hyhk', 'lq_hytk_hytk') +-- AND INDEX_NAME LIKE 'idx_%' +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX; + -- libgit2 0.21.4