Commit 430949ed5ffc1c4a2184350f5122200e31c566d7
1 parent
9bad463b
feat: 优化门店数据结构分析功能
- 创建单独的门店基础信息接口(get-store-base-info),支持单独获取门店基础信息和归属信息 - 修复门店数据结构分析接口的类型转换问题 - 优化前端调用逻辑,支持分步骤获取数据 - 添加门店归属信息显示(事业部、科技部、教育部、大项目部) - 添加性能优化相关的SQL索引脚本
Showing
6 changed files
with
635 additions
and
135 deletions
antis-ncc-admin/src/components/store-data-analysis-dialog.vue
| ... | ... | @@ -33,6 +33,22 @@ |
| 33 | 33 | <i class="el-icon-calendar"></i> |
| 34 | 34 | <span>开业:{{ formatDate(baseInfo.OpenTime) }}</span> |
| 35 | 35 | </div> |
| 36 | + <div class="meta-item" v-if="baseInfo.BusinessUnit"> | |
| 37 | + <i class="el-icon-office-building"></i> | |
| 38 | + <span>事业部:{{ baseInfo.BusinessUnit }}</span> | |
| 39 | + </div> | |
| 40 | + <div class="meta-item" v-if="baseInfo.TechDepartment"> | |
| 41 | + <i class="el-icon-cpu"></i> | |
| 42 | + <span>科技部:{{ baseInfo.TechDepartment }}</span> | |
| 43 | + </div> | |
| 44 | + <div class="meta-item" v-if="baseInfo.EducationDepartment"> | |
| 45 | + <i class="el-icon-reading"></i> | |
| 46 | + <span>教育部:{{ baseInfo.EducationDepartment }}</span> | |
| 47 | + </div> | |
| 48 | + <div class="meta-item" v-if="baseInfo.MajorProjectDepartment"> | |
| 49 | + <i class="el-icon-folder-opened"></i> | |
| 50 | + <span>大项目部:{{ baseInfo.MajorProjectDepartment }}</span> | |
| 51 | + </div> | |
| 36 | 52 | </div> |
| 37 | 53 | </div> |
| 38 | 54 | <div class="header-right"> |
| ... | ... | @@ -501,34 +517,42 @@ export default { |
| 501 | 517 | |
| 502 | 518 | this.loading = true |
| 503 | 519 | try { |
| 504 | - // 第一步:先获取门店基础信息 | |
| 520 | + // 准备请求参数(所有接口共用) | |
| 521 | + const requestParams = { | |
| 522 | + storeId: this.storeId, | |
| 523 | + statisticsMonth: this.statisticsMonth || this.getCurrentMonth() | |
| 524 | + } | |
| 525 | + | |
| 526 | + // 第一步:获取门店基础信息(单独接口) | |
| 505 | 527 | console.log('开始获取门店基础信息,门店ID:', this.storeId) |
| 528 | + | |
| 506 | 529 | const baseInfoRes = await request({ |
| 507 | - url: `/api/Extend/LqMdxx/${this.storeId}`, | |
| 508 | - method: 'GET' | |
| 530 | + url: '/api/Extend/LqReport/get-store-base-info', | |
| 531 | + method: 'POST', | |
| 532 | + data: requestParams | |
| 509 | 533 | }) |
| 510 | 534 | |
| 511 | 535 | console.log('门店基础信息接口响应:', baseInfoRes) |
| 512 | 536 | |
| 513 | 537 | // 处理门店基础信息 |
| 514 | 538 | if (baseInfoRes && baseInfoRes.code === 200 && baseInfoRes.data) { |
| 515 | - const data = baseInfoRes.data | |
| 516 | - console.log('门店数据:', data) | |
| 539 | + const baseInfoData = baseInfoRes.data | |
| 540 | + console.log('门店基础信息数据:', baseInfoData) | |
| 517 | 541 | this.baseInfo = { |
| 518 | - StoreId: data.id || this.storeId, | |
| 519 | - StoreName: data.dm || '—', | |
| 520 | - StoreCode: data.mdbm || '', | |
| 521 | - City: data.cs || '', | |
| 522 | - Address: data.dz || '', | |
| 523 | - Status: data.zxzt || '', | |
| 524 | - OpenTime: data.kysj || null, | |
| 525 | - EmployeeCount: data.zzrs || 0, | |
| 526 | - StoreType: (data.storeType != null ? data.storeType.toString() : '') || '', | |
| 527 | - StoreCategory: (data.storeCategory != null ? data.storeCategory.toString() : '') || '', | |
| 528 | - BusinessUnit: data.syb || '', | |
| 529 | - EducationDepartment: data.jyb || '', | |
| 530 | - TechDepartment: data.kjb || '', | |
| 531 | - MajorProjectDepartment: data.dxmb || '' | |
| 542 | + StoreId: baseInfoData.StoreId || this.storeId, | |
| 543 | + StoreName: baseInfoData.StoreName || '—', | |
| 544 | + StoreCode: baseInfoData.StoreCode || '', | |
| 545 | + City: baseInfoData.City || '', | |
| 546 | + Address: baseInfoData.Address || '', | |
| 547 | + Status: baseInfoData.Status || '', | |
| 548 | + OpenTime: baseInfoData.OpenTime || null, | |
| 549 | + EmployeeCount: baseInfoData.EmployeeCount || 0, | |
| 550 | + StoreType: baseInfoData.StoreType || '', | |
| 551 | + StoreCategory: baseInfoData.StoreCategory || '', | |
| 552 | + BusinessUnit: baseInfoData.BusinessUnit || '', | |
| 553 | + EducationDepartment: baseInfoData.EducationDepartment || '', | |
| 554 | + TechDepartment: baseInfoData.TechDepartment || '', | |
| 555 | + MajorProjectDepartment: baseInfoData.MajorProjectDepartment || '' | |
| 532 | 556 | } |
| 533 | 557 | console.log('处理后的门店基础信息:', this.baseInfo) |
| 534 | 558 | } else { |
| ... | ... | @@ -538,14 +562,37 @@ export default { |
| 538 | 562 | } else { |
| 539 | 563 | this.$message.error('获取门店基础信息失败: 接口返回异常') |
| 540 | 564 | } |
| 565 | + // 如果获取失败,尝试从门店接口获取基本信息(但归属信息会是ID) | |
| 566 | + try { | |
| 567 | + const fallbackRes = await request({ | |
| 568 | + url: `/api/Extend/LqMdxx/${this.storeId}`, | |
| 569 | + method: 'GET' | |
| 570 | + }) | |
| 571 | + if (fallbackRes && fallbackRes.code === 200 && fallbackRes.data) { | |
| 572 | + const data = fallbackRes.data | |
| 573 | + this.baseInfo = { | |
| 574 | + StoreId: data.id || this.storeId, | |
| 575 | + StoreName: data.dm || '—', | |
| 576 | + StoreCode: data.mdbm || '', | |
| 577 | + City: data.cs || '', | |
| 578 | + Address: data.dz || '', | |
| 579 | + Status: data.zxzt || '', | |
| 580 | + OpenTime: data.kysj || null, | |
| 581 | + EmployeeCount: data.zzrs || 0, | |
| 582 | + StoreType: (data.storeType != null ? data.storeType.toString() : '') || '', | |
| 583 | + StoreCategory: (data.storeCategory != null ? data.storeCategory.toString() : '') || '', | |
| 584 | + BusinessUnit: '', // 备用方案:无法获取名称时留空 | |
| 585 | + EducationDepartment: '', | |
| 586 | + TechDepartment: '', | |
| 587 | + MajorProjectDepartment: '' | |
| 588 | + } | |
| 589 | + } | |
| 590 | + } catch (e) { | |
| 591 | + console.error('备用方案也失败:', e) | |
| 592 | + } | |
| 541 | 593 | } |
| 542 | 594 | |
| 543 | 595 | // 第二步:获取业绩概览 |
| 544 | - const requestParams = { | |
| 545 | - storeId: this.storeId, | |
| 546 | - statisticsMonth: this.statisticsMonth || this.getCurrentMonth() | |
| 547 | - } | |
| 548 | - | |
| 549 | 596 | console.log('开始获取业绩概览,参数:', requestParams) |
| 550 | 597 | const performanceRes = await request({ |
| 551 | 598 | url: '/api/Extend/LqReport/get-store-performance-overview', | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StoreDataAnalysisOutput.cs
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs
| ... | ... | @@ -1130,28 +1130,47 @@ namespace NCC.Extend |
| 1130 | 1130 | ? DateTime.Now |
| 1131 | 1131 | : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59); |
| 1132 | 1132 | |
| 1133 | - // 实时查询金三角业绩 | |
| 1134 | - var sql = $@" | |
| 1133 | + // 使用参数化查询,优化性能 | |
| 1134 | + var parameters = new List<SugarParameter> | |
| 1135 | + { | |
| 1136 | + new SugarParameter("@StatisticsMonth", input.StatisticsMonth), | |
| 1137 | + new SugarParameter("@StartDate", startDate), | |
| 1138 | + new SugarParameter("@EndDate", endDateForQuery) | |
| 1139 | + }; | |
| 1140 | + | |
| 1141 | + // 优化SQL:使用子查询先聚合数据,减少JOIN的数据量 | |
| 1142 | + // 1. 先过滤有效业绩数据并按金三角ID聚合(减少数据量) | |
| 1143 | + // 2. 再关联金三角设定和门店信息 | |
| 1144 | + // 这样可以避免在大数据量下进行复杂的多表JOIN | |
| 1145 | + var sql = @" | |
| 1135 | 1146 | SELECT |
| 1136 | 1147 | jsj.F_Id as F_GoldTriangleId, |
| 1137 | 1148 | jsj.jsj as F_GoldTriangleName, |
| 1138 | 1149 | jsj.md as F_StoreId, |
| 1139 | - mdxx.dm as F_StoreName, | |
| 1140 | - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) as F_TotalPerformance, | |
| 1141 | - COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount | |
| 1150 | + COALESCE(mdxx.dm, '') as F_StoreName, | |
| 1151 | + COALESCE(performance.F_TotalPerformance, 0) as F_TotalPerformance, | |
| 1152 | + COALESCE(performance.F_OrderCount, 0) as F_OrderCount | |
| 1142 | 1153 | FROM lq_ycsd_jsj jsj |
| 1143 | - INNER JOIN lq_jinsanjiao_user jsjUser ON jsj.F_Id = jsjUser.jsj_id | |
| 1144 | - AND jsjUser.status = 'ACTIVE' | |
| 1145 | - AND jsjUser.F_DeleteMark = 0 | |
| 1146 | - INNER JOIN lq_kd_jksyj jksyj ON jsjUser.user_id = jksyj.jkszh | |
| 1147 | - INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id | |
| 1154 | + LEFT JOIN ( | |
| 1155 | + -- 子查询:先过滤并聚合有效业绩数据,减少JOIN数据量 | |
| 1156 | + SELECT | |
| 1157 | + jsjUser.jsj_id, | |
| 1158 | + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) as F_TotalPerformance, | |
| 1159 | + COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount | |
| 1160 | + FROM lq_kd_jksyj jksyj | |
| 1161 | + INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id | |
| 1162 | + INNER JOIN lq_jinsanjiao_user jsjUser ON jksyj.jkszh = jsjUser.user_id | |
| 1163 | + WHERE jksyj.F_IsEffective = 1 | |
| 1164 | + AND kd.F_IsEffective = 1 | |
| 1165 | + AND kd.kdrq >= @StartDate | |
| 1166 | + AND kd.kdrq <= @EndDate | |
| 1167 | + AND jsjUser.status = 'ACTIVE' | |
| 1168 | + AND jsjUser.F_DeleteMark = 0 | |
| 1169 | + GROUP BY jsjUser.jsj_id | |
| 1170 | + ) performance ON jsj.F_Id = performance.jsj_id | |
| 1148 | 1171 | LEFT JOIN lq_mdxx mdxx ON jsj.md = mdxx.F_Id |
| 1149 | - WHERE jsj.yf = '{input.StatisticsMonth}' | |
| 1150 | - AND jksyj.F_IsEffective = 1 | |
| 1151 | - AND kd.F_IsEffective = 1 | |
| 1152 | - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' | |
| 1153 | - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}' | |
| 1154 | - GROUP BY jsj.F_Id, jsj.jsj, jsj.md, mdxx.dm | |
| 1172 | + WHERE jsj.yf = @StatisticsMonth | |
| 1173 | + AND performance.jsj_id IS NOT NULL | |
| 1155 | 1174 | ORDER BY F_TotalPerformance DESC"; |
| 1156 | 1175 | |
| 1157 | 1176 | if (input.TopCount > 0) |
| ... | ... | @@ -1159,7 +1178,7 @@ namespace NCC.Extend |
| 1159 | 1178 | sql += $" LIMIT {input.TopCount}"; |
| 1160 | 1179 | } |
| 1161 | 1180 | |
| 1162 | - var data = await _db.Ado.SqlQueryAsync<dynamic>(sql); | |
| 1181 | + var data = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters); | |
| 1163 | 1182 | |
| 1164 | 1183 | if (data == null || !data.Any()) |
| 1165 | 1184 | { |
| ... | ... | @@ -1320,31 +1339,39 @@ namespace NCC.Extend |
| 1320 | 1339 | ? DateTime.Now |
| 1321 | 1340 | : endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59); |
| 1322 | 1341 | |
| 1342 | + // 使用参数化查询,优化性能 | |
| 1343 | + var parameters = new List<SugarParameter> | |
| 1344 | + { | |
| 1345 | + new SugarParameter("@StartDate", startDate), | |
| 1346 | + new SugarParameter("@EndDate", endDateForQuery), | |
| 1347 | + new SugarParameter("@StatisticsMonth", statisticsMonth) | |
| 1348 | + }; | |
| 1349 | + | |
| 1323 | 1350 | // 1. 门店业绩汇总(实时查询) |
| 1324 | 1351 | // 先查询开单业绩 |
| 1325 | - var billingSql = $@" | |
| 1352 | + var billingSql = @" | |
| 1326 | 1353 | SELECT |
| 1327 | 1354 | COUNT(DISTINCT kd.djmd) as StoreCount, |
| 1328 | 1355 | COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as TotalBillingAmount |
| 1329 | 1356 | FROM lq_kd_kdjlb kd |
| 1330 | 1357 | WHERE kd.F_IsEffective = 1 |
| 1331 | - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' | |
| 1332 | - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; | |
| 1358 | + AND kd.kdrq >= @StartDate | |
| 1359 | + AND kd.kdrq <= @EndDate"; | |
| 1333 | 1360 | |
| 1334 | - var billingResult = await _db.Ado.SqlQueryAsync<dynamic>(billingSql); | |
| 1361 | + var billingResult = await _db.Ado.SqlQueryAsync<dynamic>(billingSql, parameters); | |
| 1335 | 1362 | var storeCount = Convert.ToInt32(billingResult?.FirstOrDefault()?.StoreCount ?? 0); |
| 1336 | 1363 | var totalBillingAmount = Convert.ToDecimal(billingResult?.FirstOrDefault()?.TotalBillingAmount ?? 0m); |
| 1337 | 1364 | |
| 1338 | 1365 | // 查询退卡金额 |
| 1339 | - var refundSql = $@" | |
| 1366 | + var refundSql = @" | |
| 1340 | 1367 | SELECT |
| 1341 | 1368 | COALESCE(SUM(CAST(COALESCE(F_ActualRefundAmount, tkje, 0) AS DECIMAL(18,2))), 0) as TotalRefundAmount |
| 1342 | 1369 | FROM lq_hytk_hytk |
| 1343 | 1370 | WHERE F_IsEffective = 1 |
| 1344 | - AND tksj >= '{startDate:yyyy-MM-dd 00:00:00}' | |
| 1345 | - AND tksj <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; | |
| 1371 | + AND tksj >= @StartDate | |
| 1372 | + AND tksj <= @EndDate"; | |
| 1346 | 1373 | |
| 1347 | - var refundResult = await _db.Ado.SqlQueryAsync<dynamic>(refundSql); | |
| 1374 | + var refundResult = await _db.Ado.SqlQueryAsync<dynamic>(refundSql, parameters); | |
| 1348 | 1375 | var totalRefundAmount = Convert.ToDecimal(refundResult?.FirstOrDefault()?.TotalRefundAmount ?? 0m); |
| 1349 | 1376 | |
| 1350 | 1377 | var totalPerformance = totalBillingAmount - totalRefundAmount; |
| ... | ... | @@ -1363,7 +1390,7 @@ namespace NCC.Extend |
| 1363 | 1390 | }.Select(x => (dynamic)x).ToList(); |
| 1364 | 1391 | |
| 1365 | 1392 | // 2. 健康师业绩汇总(实时查询) |
| 1366 | - var healthCoachPerformanceSql = $@" | |
| 1393 | + var healthCoachPerformanceSql = @" | |
| 1367 | 1394 | SELECT |
| 1368 | 1395 | COUNT(DISTINCT jks.jkszh) as HealthCoachCount, |
| 1369 | 1396 | COALESCE(SUM(CAST(jks.jksyj AS DECIMAL(18,2))), 0) as TotalPerformance, |
| ... | ... | @@ -1378,38 +1405,47 @@ namespace NCC.Extend |
| 1378 | 1405 | INNER JOIN lq_kd_kdjlb kd ON jks.glkdbh = kd.F_Id |
| 1379 | 1406 | WHERE jks.F_IsEffective = 1 |
| 1380 | 1407 | AND kd.F_IsEffective = 1 |
| 1381 | - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' | |
| 1382 | - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; | |
| 1408 | + AND kd.kdrq >= @StartDate | |
| 1409 | + AND kd.kdrq <= @EndDate"; | |
| 1383 | 1410 | |
| 1384 | - var healthCoachPerformance = await _db.Ado.SqlQueryAsync<dynamic>(healthCoachPerformanceSql); | |
| 1411 | + var healthCoachPerformance = await _db.Ado.SqlQueryAsync<dynamic>(healthCoachPerformanceSql, parameters); | |
| 1385 | 1412 | |
| 1386 | - // 3. 金三角业绩汇总(实时查询) | |
| 1387 | - var goldTrianglePerformanceSql = $@" | |
| 1413 | + // 3. 金三角业绩汇总(实时查询)- 使用子查询优化性能 | |
| 1414 | + var goldTrianglePerformanceSql = @" | |
| 1388 | 1415 | SELECT |
| 1389 | 1416 | COUNT(DISTINCT jsj.F_Id) as GoldTriangleCount, |
| 1390 | - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) as TotalPerformance, | |
| 1391 | - COUNT(DISTINCT jksyj.glkdbh) as TotalOrderCount, | |
| 1417 | + COALESCE(SUM(performance.F_TotalPerformance), 0) as TotalPerformance, | |
| 1418 | + COALESCE(SUM(performance.F_OrderCount), 0) as TotalOrderCount, | |
| 1392 | 1419 | CASE |
| 1393 | 1420 | WHEN COUNT(DISTINCT jsj.F_Id) > 0 THEN |
| 1394 | - COALESCE(SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))), 0) / COUNT(DISTINCT jsj.F_Id) | |
| 1421 | + COALESCE(SUM(performance.F_TotalPerformance), 0) / COUNT(DISTINCT jsj.F_Id) | |
| 1395 | 1422 | ELSE 0 |
| 1396 | 1423 | END as AvgPerformance |
| 1397 | 1424 | FROM lq_ycsd_jsj jsj |
| 1398 | - INNER JOIN lq_jinsanjiao_user jsjUser ON jsj.F_Id = jsjUser.jsj_id | |
| 1399 | - AND jsjUser.status = 'ACTIVE' | |
| 1400 | - AND jsjUser.F_DeleteMark = 0 | |
| 1401 | - INNER JOIN lq_kd_jksyj jksyj ON jsjUser.user_id = jksyj.jkszh | |
| 1402 | - INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id | |
| 1403 | - WHERE jsj.yf = '{statisticsMonth}' | |
| 1404 | - AND jksyj.F_IsEffective = 1 | |
| 1405 | - AND kd.F_IsEffective = 1 | |
| 1406 | - AND kd.kdrq >= '{startDate:yyyy-MM-dd 00:00:00}' | |
| 1407 | - AND kd.kdrq <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; | |
| 1408 | - | |
| 1409 | - var goldTrianglePerformance = await _db.Ado.SqlQueryAsync<dynamic>(goldTrianglePerformanceSql); | |
| 1425 | + LEFT JOIN ( | |
| 1426 | + -- 子查询:先过滤并聚合有效业绩数据,减少JOIN数据量 | |
| 1427 | + SELECT | |
| 1428 | + jsjUser.jsj_id, | |
| 1429 | + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) as F_TotalPerformance, | |
| 1430 | + COUNT(DISTINCT jksyj.glkdbh) as F_OrderCount | |
| 1431 | + FROM lq_kd_jksyj jksyj | |
| 1432 | + INNER JOIN lq_kd_kdjlb kd ON jksyj.glkdbh = kd.F_Id | |
| 1433 | + INNER JOIN lq_jinsanjiao_user jsjUser ON jksyj.jkszh = jsjUser.user_id | |
| 1434 | + WHERE jksyj.F_IsEffective = 1 | |
| 1435 | + AND kd.F_IsEffective = 1 | |
| 1436 | + AND kd.kdrq >= @StartDate | |
| 1437 | + AND kd.kdrq <= @EndDate | |
| 1438 | + AND jsjUser.status = 'ACTIVE' | |
| 1439 | + AND jsjUser.F_DeleteMark = 0 | |
| 1440 | + GROUP BY jsjUser.jsj_id | |
| 1441 | + ) performance ON jsj.F_Id = performance.jsj_id | |
| 1442 | + WHERE jsj.yf = @StatisticsMonth | |
| 1443 | + AND performance.jsj_id IS NOT NULL"; | |
| 1444 | + | |
| 1445 | + var goldTrianglePerformance = await _db.Ado.SqlQueryAsync<dynamic>(goldTrianglePerformanceSql, parameters); | |
| 1410 | 1446 | |
| 1411 | 1447 | // 4. 消耗业绩汇总(实时查询) |
| 1412 | - var consumePerformanceSql = $@" | |
| 1448 | + var consumePerformanceSql = @" | |
| 1413 | 1449 | SELECT |
| 1414 | 1450 | COUNT(*) as ConsumeRecordCount, |
| 1415 | 1451 | COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as TotalConsumePerformance, |
| ... | ... | @@ -1419,18 +1455,23 @@ namespace NCC.Extend |
| 1419 | 1455 | FROM lq_xh_hyhk xh |
| 1420 | 1456 | LEFT JOIN lq_xh_pxmx pxmx ON xh.F_Id = pxmx.F_ConsumeInfoId AND pxmx.F_IsEffective = 1 |
| 1421 | 1457 | WHERE xh.F_IsEffective = 1 |
| 1422 | - AND xh.hksj >= '{startDate:yyyy-MM-dd 00:00:00}' | |
| 1423 | - AND xh.hksj <= '{endDateForQuery:yyyy-MM-dd HH:mm:ss}'"; | |
| 1458 | + AND xh.hksj >= @StartDate | |
| 1459 | + AND xh.hksj <= @EndDate"; | |
| 1424 | 1460 | |
| 1425 | - var consumePerformance = await _db.Ado.SqlQueryAsync<dynamic>(consumePerformanceSql); | |
| 1461 | + var consumePerformance = await _db.Ado.SqlQueryAsync<dynamic>(consumePerformanceSql, parameters); | |
| 1426 | 1462 | |
| 1427 | 1463 | // 5. 会员统计汇总 |
| 1428 | 1464 | var lastMonth = DateTime.ParseExact(statisticsMonth, "yyyyMM", null).AddMonths(-1).ToString("yyyyMM"); |
| 1429 | - var memberStatisticsSql = $@" | |
| 1465 | + var memberStatisticsParameters = new List<SugarParameter> | |
| 1466 | + { | |
| 1467 | + new SugarParameter("@statisticsMonth", statisticsMonth), | |
| 1468 | + new SugarParameter("@lastMonth", lastMonth) | |
| 1469 | + }; | |
| 1470 | + var memberStatisticsSql = @" | |
| 1430 | 1471 | SELECT |
| 1431 | 1472 | COUNT(*) as TotalMembers, |
| 1432 | 1473 | SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = @statisticsMonth THEN 1 ELSE 0 END) as NewMembersThisMonth, |
| 1433 | - SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = '{lastMonth}' THEN 1 ELSE 0 END) as NewMembersLastMonth, | |
| 1474 | + SUM(CASE WHEN DATE_FORMAT(F_CreateTime, '%Y%m') = @lastMonth THEN 1 ELSE 0 END) as NewMembersLastMonth, | |
| 1434 | 1475 | SUM(CASE WHEN F_SleepDays IS NULL OR F_SleepDays <= 3 THEN 1 ELSE 0 END) as ActiveMembers0_3, |
| 1435 | 1476 | SUM(CASE WHEN F_SleepDays > 3 AND F_SleepDays < 60 THEN 1 ELSE 0 END) as ActiveMembers4_59, |
| 1436 | 1477 | 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 |
| 1447 | 1488 | WHERE F_IsEffective = 1 |
| 1448 | 1489 | AND khlx = '3'"; |
| 1449 | 1490 | |
| 1450 | - var memberStatistics = await _db.Ado.SqlQueryAsync<dynamic>(memberStatisticsSql, new { statisticsMonth }); | |
| 1491 | + var memberStatistics = await _db.Ado.SqlQueryAsync<dynamic>(memberStatisticsSql, memberStatisticsParameters); | |
| 1451 | 1492 | var memberStats = memberStatistics.FirstOrDefault(); |
| 1452 | 1493 | |
| 1453 | 1494 | // 6. 会员类型分布统计 |
| ... | ... | @@ -1496,12 +1537,13 @@ namespace NCC.Extend |
| 1496 | 1537 | FROM lq_kd_kdjlb kd |
| 1497 | 1538 | INNER JOIN lq_khxx kh ON kh.F_Id = kd.kdhy |
| 1498 | 1539 | WHERE kd.F_IsEffective = 1 |
| 1499 | - AND DATE_FORMAT(kd.kdrq, '%Y%m') = @statisticsMonth | |
| 1540 | + AND kd.kdrq >= @StartDate | |
| 1541 | + AND kd.kdrq <= @EndDate | |
| 1500 | 1542 | GROUP BY kh.F_Id, kh.Khmc |
| 1501 | 1543 | ORDER BY Amount DESC |
| 1502 | 1544 | LIMIT 1"; |
| 1503 | 1545 | |
| 1504 | - var topBilling = (await _db.Ado.SqlQueryAsync<dynamic>(topBillingSql, new { statisticsMonth })).FirstOrDefault(); | |
| 1546 | + var topBilling = (await _db.Ado.SqlQueryAsync<dynamic>(topBillingSql, parameters)).FirstOrDefault(); | |
| 1505 | 1547 | |
| 1506 | 1548 | var topConsumeSql = @" |
| 1507 | 1549 | SELECT |
| ... | ... | @@ -1511,12 +1553,13 @@ namespace NCC.Extend |
| 1511 | 1553 | FROM lq_xh_hyhk xh |
| 1512 | 1554 | INNER JOIN lq_khxx kh ON kh.F_Id = xh.hy |
| 1513 | 1555 | WHERE xh.F_IsEffective = 1 |
| 1514 | - AND DATE_FORMAT(xh.hksj, '%Y%m') = @statisticsMonth | |
| 1556 | + AND xh.hksj >= @StartDate | |
| 1557 | + AND xh.hksj <= @EndDate | |
| 1515 | 1558 | GROUP BY kh.F_Id, kh.Khmc |
| 1516 | 1559 | ORDER BY Amount DESC |
| 1517 | 1560 | LIMIT 1"; |
| 1518 | 1561 | |
| 1519 | - var topConsume = (await _db.Ado.SqlQueryAsync<dynamic>(topConsumeSql, new { statisticsMonth })).FirstOrDefault(); | |
| 1562 | + var topConsume = (await _db.Ado.SqlQueryAsync<dynamic>(topConsumeSql, parameters)).FirstOrDefault(); | |
| 1520 | 1563 | |
| 1521 | 1564 | var totalMembers = Convert.ToInt32(memberStats?.TotalMembers ?? 0); |
| 1522 | 1565 | var activeMembers0_3 = Convert.ToInt32(memberStats?.ActiveMembers0_3 ?? 0); |
| ... | ... | @@ -4514,6 +4557,125 @@ namespace NCC.Extend |
| 4514 | 4557 | |
| 4515 | 4558 | #region 门店数据结构分析 |
| 4516 | 4559 | /// <summary> |
| 4560 | + /// 获取门店基础信息 | |
| 4561 | + /// </summary> | |
| 4562 | + /// <remarks> | |
| 4563 | + /// 获取单个门店的基础信息,包括门店基本信息、归属信息(事业部、科技部、教育部、大项目部)等 | |
| 4564 | + /// | |
| 4565 | + /// 示例请求: | |
| 4566 | + /// ```json | |
| 4567 | + /// { | |
| 4568 | + /// "storeId": "门店ID", | |
| 4569 | + /// "statisticsMonth": "202512" | |
| 4570 | + /// } | |
| 4571 | + /// ``` | |
| 4572 | + /// | |
| 4573 | + /// 参数说明: | |
| 4574 | + /// - storeId: 门店ID(必填) | |
| 4575 | + /// - statisticsMonth: 统计月份(YYYYMM格式,可选,不传则默认为当前月份) | |
| 4576 | + /// </remarks> | |
| 4577 | + /// <param name="input">查询参数</param> | |
| 4578 | + /// <returns>门店基础信息</returns> | |
| 4579 | + /// <response code="200">成功返回基础信息</response> | |
| 4580 | + /// <response code="400">参数错误</response> | |
| 4581 | + /// <response code="500">服务器错误</response> | |
| 4582 | + [HttpPost("get-store-base-info")] | |
| 4583 | + public async Task<StoreBaseInfo> GetStoreBaseInfo(StoreDataAnalysisInput input) | |
| 4584 | + { | |
| 4585 | + try | |
| 4586 | + { | |
| 4587 | + if (string.IsNullOrWhiteSpace(input.StoreId)) | |
| 4588 | + { | |
| 4589 | + throw NCCException.Oh("门店ID不能为空"); | |
| 4590 | + } | |
| 4591 | + | |
| 4592 | + // 确定统计月份 | |
| 4593 | + var statisticsMonth = input.StatisticsMonth; | |
| 4594 | + if (string.IsNullOrWhiteSpace(statisticsMonth)) | |
| 4595 | + { | |
| 4596 | + statisticsMonth = DateTime.Now.ToString("yyyyMM"); | |
| 4597 | + } | |
| 4598 | + | |
| 4599 | + // 1. 获取门店基础信息 | |
| 4600 | + var store = await _db.Queryable<LqMdxxEntity>() | |
| 4601 | + .Where(x => x.Id == input.StoreId) | |
| 4602 | + .FirstAsync(); | |
| 4603 | + | |
| 4604 | + if (store == null) | |
| 4605 | + { | |
| 4606 | + throw NCCException.Oh("门店不存在"); | |
| 4607 | + } | |
| 4608 | + | |
| 4609 | + // 2. 获取本月归属信息(优先从门店目标表获取,如果不存在则从门店表获取) | |
| 4610 | + var target = await _db.Queryable<LqMdTargetEntity>() | |
| 4611 | + .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth) | |
| 4612 | + .FirstAsync(); | |
| 4613 | + | |
| 4614 | + string businessUnitName = ""; | |
| 4615 | + string techDepartmentName = ""; | |
| 4616 | + string educationDepartmentName = ""; | |
| 4617 | + string majorProjectDepartmentName = ""; | |
| 4618 | + | |
| 4619 | + // 从门店目标表获取归属ID | |
| 4620 | + var businessUnitId = target?.BusinessUnit ?? store.Syb; | |
| 4621 | + var techDepartmentId = target?.TechDepartment ?? store.Kjb; | |
| 4622 | + var educationDepartmentId = target?.EducationDepartment ?? store.Jyb; | |
| 4623 | + var majorProjectDepartmentId = target?.MajorProjectDepartment ?? store.Dxmb; | |
| 4624 | + | |
| 4625 | + // 关联组织表获取归属名称(批量查询优化性能) | |
| 4626 | + var organizeIds = new List<string>(); | |
| 4627 | + if (!string.IsNullOrEmpty(businessUnitId)) organizeIds.Add(businessUnitId); | |
| 4628 | + if (!string.IsNullOrEmpty(techDepartmentId)) organizeIds.Add(techDepartmentId); | |
| 4629 | + if (!string.IsNullOrEmpty(educationDepartmentId)) organizeIds.Add(educationDepartmentId); | |
| 4630 | + if (!string.IsNullOrEmpty(majorProjectDepartmentId)) organizeIds.Add(majorProjectDepartmentId); | |
| 4631 | + | |
| 4632 | + var organizeDict = new Dictionary<string, string>(); | |
| 4633 | + if (organizeIds.Any()) | |
| 4634 | + { | |
| 4635 | + var organizes = await _db.Queryable<OrganizeEntity>() | |
| 4636 | + .Where(x => organizeIds.Contains(x.Id)) | |
| 4637 | + .Select(x => new { x.Id, x.FullName }) | |
| 4638 | + .ToListAsync(); | |
| 4639 | + organizeDict = organizes.ToDictionary(x => x.Id, x => x.FullName ?? ""); | |
| 4640 | + } | |
| 4641 | + | |
| 4642 | + businessUnitName = !string.IsNullOrEmpty(businessUnitId) && organizeDict.ContainsKey(businessUnitId) | |
| 4643 | + ? organizeDict[businessUnitId] : ""; | |
| 4644 | + techDepartmentName = !string.IsNullOrEmpty(techDepartmentId) && organizeDict.ContainsKey(techDepartmentId) | |
| 4645 | + ? organizeDict[techDepartmentId] : ""; | |
| 4646 | + educationDepartmentName = !string.IsNullOrEmpty(educationDepartmentId) && organizeDict.ContainsKey(educationDepartmentId) | |
| 4647 | + ? organizeDict[educationDepartmentId] : ""; | |
| 4648 | + majorProjectDepartmentName = !string.IsNullOrEmpty(majorProjectDepartmentId) && organizeDict.ContainsKey(majorProjectDepartmentId) | |
| 4649 | + ? organizeDict[majorProjectDepartmentId] : ""; | |
| 4650 | + | |
| 4651 | + var baseInfo = new StoreBaseInfo | |
| 4652 | + { | |
| 4653 | + StoreId = store.Id, | |
| 4654 | + StoreName = store.Dm ?? "未知门店", | |
| 4655 | + StoreCode = store.Mdbm ?? "", | |
| 4656 | + City = store.Cs ?? "", | |
| 4657 | + Address = store.Dz ?? "", | |
| 4658 | + Status = store.Zxzt ?? "", | |
| 4659 | + OpenTime = store.Kysj, | |
| 4660 | + EmployeeCount = store.Zzrs, | |
| 4661 | + StoreType = store.StoreType?.ToString() ?? "", | |
| 4662 | + StoreCategory = store.StoreCategory?.ToString() ?? "", | |
| 4663 | + BusinessUnit = businessUnitName, | |
| 4664 | + EducationDepartment = educationDepartmentName, | |
| 4665 | + TechDepartment = techDepartmentName, | |
| 4666 | + MajorProjectDepartment = majorProjectDepartmentName | |
| 4667 | + }; | |
| 4668 | + | |
| 4669 | + return baseInfo; | |
| 4670 | + } | |
| 4671 | + catch (Exception ex) | |
| 4672 | + { | |
| 4673 | + _logger.LogError(ex, $"获取门店基础信息失败 - 门店ID: {input?.StoreId}, 月份: {input?.StatisticsMonth}"); | |
| 4674 | + throw NCCException.Oh($"获取门店基础信息失败: {ex.Message}"); | |
| 4675 | + } | |
| 4676 | + } | |
| 4677 | + | |
| 4678 | + /// <summary> | |
| 4517 | 4679 | /// 获取门店数据结构分析 |
| 4518 | 4680 | /// </summary> |
| 4519 | 4681 | /// <remarks> |
| ... | ... | @@ -4573,6 +4735,49 @@ namespace NCC.Extend |
| 4573 | 4735 | throw NCCException.Oh("门店不存在"); |
| 4574 | 4736 | } |
| 4575 | 4737 | |
| 4738 | + // 2.4 目标业绩(从门店目标表获取) | |
| 4739 | + var target = await _db.Queryable<LqMdTargetEntity>() | |
| 4740 | + .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth) | |
| 4741 | + .FirstAsync(); | |
| 4742 | + | |
| 4743 | + // 获取本月归属信息(优先从门店目标表获取,如果不存在则从门店表获取) | |
| 4744 | + string businessUnitName = ""; | |
| 4745 | + string techDepartmentName = ""; | |
| 4746 | + string educationDepartmentName = ""; | |
| 4747 | + string majorProjectDepartmentName = ""; | |
| 4748 | + | |
| 4749 | + // 从门店目标表获取归属ID | |
| 4750 | + var businessUnitId = target?.BusinessUnit ?? store.Syb; | |
| 4751 | + var techDepartmentId = target?.TechDepartment ?? store.Kjb; | |
| 4752 | + var educationDepartmentId = target?.EducationDepartment ?? store.Jyb; | |
| 4753 | + var majorProjectDepartmentId = target?.MajorProjectDepartment ?? store.Dxmb; | |
| 4754 | + | |
| 4755 | + // 关联组织表获取归属名称(批量查询优化性能) | |
| 4756 | + var organizeIds = new List<string>(); | |
| 4757 | + if (!string.IsNullOrEmpty(businessUnitId)) organizeIds.Add(businessUnitId); | |
| 4758 | + if (!string.IsNullOrEmpty(techDepartmentId)) organizeIds.Add(techDepartmentId); | |
| 4759 | + if (!string.IsNullOrEmpty(educationDepartmentId)) organizeIds.Add(educationDepartmentId); | |
| 4760 | + if (!string.IsNullOrEmpty(majorProjectDepartmentId)) organizeIds.Add(majorProjectDepartmentId); | |
| 4761 | + | |
| 4762 | + var organizeDict = new Dictionary<string, string>(); | |
| 4763 | + if (organizeIds.Any()) | |
| 4764 | + { | |
| 4765 | + var organizes = await _db.Queryable<OrganizeEntity>() | |
| 4766 | + .Where(x => organizeIds.Contains(x.Id)) | |
| 4767 | + .Select(x => new { x.Id, x.FullName }) | |
| 4768 | + .ToListAsync(); | |
| 4769 | + organizeDict = organizes.ToDictionary(x => x.Id, x => x.FullName ?? ""); | |
| 4770 | + } | |
| 4771 | + | |
| 4772 | + businessUnitName = !string.IsNullOrEmpty(businessUnitId) && organizeDict.ContainsKey(businessUnitId) | |
| 4773 | + ? organizeDict[businessUnitId] : ""; | |
| 4774 | + techDepartmentName = !string.IsNullOrEmpty(techDepartmentId) && organizeDict.ContainsKey(techDepartmentId) | |
| 4775 | + ? organizeDict[techDepartmentId] : ""; | |
| 4776 | + educationDepartmentName = !string.IsNullOrEmpty(educationDepartmentId) && organizeDict.ContainsKey(educationDepartmentId) | |
| 4777 | + ? organizeDict[educationDepartmentId] : ""; | |
| 4778 | + majorProjectDepartmentName = !string.IsNullOrEmpty(majorProjectDepartmentId) && organizeDict.ContainsKey(majorProjectDepartmentId) | |
| 4779 | + ? organizeDict[majorProjectDepartmentId] : ""; | |
| 4780 | + | |
| 4576 | 4781 | var baseInfo = new StoreBaseInfo |
| 4577 | 4782 | { |
| 4578 | 4783 | StoreId = store.Id, |
| ... | ... | @@ -4585,10 +4790,10 @@ namespace NCC.Extend |
| 4585 | 4790 | EmployeeCount = store.Zzrs, |
| 4586 | 4791 | StoreType = store.StoreType?.ToString() ?? "", |
| 4587 | 4792 | StoreCategory = store.StoreCategory?.ToString() ?? "", |
| 4588 | - BusinessUnit = store.Syb ?? "", | |
| 4589 | - EducationDepartment = store.Jyb ?? "", | |
| 4590 | - TechDepartment = store.Kjb ?? "", | |
| 4591 | - MajorProjectDepartment = store.Dxmb ?? "" | |
| 4793 | + BusinessUnit = businessUnitName, | |
| 4794 | + EducationDepartment = educationDepartmentName, | |
| 4795 | + TechDepartment = techDepartmentName, | |
| 4796 | + MajorProjectDepartment = majorProjectDepartmentName | |
| 4592 | 4797 | }; |
| 4593 | 4798 | |
| 4594 | 4799 | // 2. 获取业绩概览 |
| ... | ... | @@ -4598,7 +4803,7 @@ namespace NCC.Extend |
| 4598 | 4803 | .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime) |
| 4599 | 4804 | .SumAsync(x => (decimal?)x.Sfyj) ?? 0m; |
| 4600 | 4805 | |
| 4601 | - var billingCount = await _db.Queryable<LqKdKdjlbEntity>() | |
| 4806 | + int billingCount = await _db.Queryable<LqKdKdjlbEntity>() | |
| 4602 | 4807 | .Where(x => x.Djmd == input.StoreId && x.IsEffective == 1) |
| 4603 | 4808 | .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime) |
| 4604 | 4809 | .CountAsync(); |
| ... | ... | @@ -4609,7 +4814,7 @@ namespace NCC.Extend |
| 4609 | 4814 | .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime) |
| 4610 | 4815 | .SumAsync(x => (decimal?)x.Xfje) ?? 0m; |
| 4611 | 4816 | |
| 4612 | - var consumeCount = await _db.Queryable<LqXhHyhkEntity>() | |
| 4817 | + int consumeCount = await _db.Queryable<LqXhHyhkEntity>() | |
| 4613 | 4818 | .Where(x => x.Md == input.StoreId && x.IsEffective == 1) |
| 4614 | 4819 | .Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime) |
| 4615 | 4820 | .CountAsync(); |
| ... | ... | @@ -4620,16 +4825,11 @@ namespace NCC.Extend |
| 4620 | 4825 | .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date) |
| 4621 | 4826 | .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m; |
| 4622 | 4827 | |
| 4623 | - var refundCount = await _db.Queryable<LqHytkHytkEntity>() | |
| 4828 | + int refundCount = await _db.Queryable<LqHytkHytkEntity>() | |
| 4624 | 4829 | .Where(x => x.Md == input.StoreId && x.IsEffective == 1) |
| 4625 | 4830 | .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date) |
| 4626 | 4831 | .CountAsync(); |
| 4627 | 4832 | |
| 4628 | - // 2.4 目标业绩(从门店目标表获取) | |
| 4629 | - var target = await _db.Queryable<LqMdTargetEntity>() | |
| 4630 | - .Where(x => x.StoreId == input.StoreId && x.Month == statisticsMonth) | |
| 4631 | - .FirstAsync(); | |
| 4632 | - | |
| 4633 | 4833 | var targetPerformance = target?.StoreTarget ?? store.Xsyj ?? 0m; |
| 4634 | 4834 | |
| 4635 | 4835 | // 2.5 剩余权益总额 |
| ... | ... | @@ -4697,20 +4897,19 @@ namespace NCC.Extend |
| 4697 | 4897 | AND consume.Md = '{input.StoreId}' |
| 4698 | 4898 | AND DATE(consume.hksj) >= '{startDate:yyyy-MM-dd}' |
| 4699 | 4899 | AND DATE(consume.hksj) <= '{endDate:yyyy-MM-dd}' |
| 4700 | - GROUP BY consume.Md", | |
| 4701 | - new { startDate, endDate }); | |
| 4900 | + GROUP BY consume.Md"); | |
| 4702 | 4901 | |
| 4703 | 4902 | var operation = new OperationMetrics(); |
| 4704 | 4903 | if (dailyStats != null && dailyStats.Any()) |
| 4705 | 4904 | { |
| 4706 | 4905 | var stats = dailyStats.FirstOrDefault(); |
| 4707 | - operation.HeadCount = Convert.ToInt32(Convert.ToDecimal(stats?.HeadCount ?? 0)); | |
| 4708 | - operation.PersonCount = Convert.ToInt32(Convert.ToDecimal(stats?.PersonCount ?? 0)); | |
| 4709 | - operation.ProjectCount = Convert.ToInt32(Convert.ToDecimal(stats?.ProjectCount ?? 0)); | |
| 4710 | - operation.TotalProjectCount = Convert.ToDecimal(stats?.TotalProjectCount ?? 0); | |
| 4711 | - operation.OriginalProjectCount = Convert.ToDecimal(stats?.OriginalProjectCount ?? 0); | |
| 4712 | - operation.OvertimeProjectCount = Convert.ToDecimal(stats?.OvertimeProjectCount ?? 0); | |
| 4713 | - operation.AccompaniedProjectCount = Convert.ToDecimal(stats?.AccompaniedProjectCount ?? 0); | |
| 4906 | + operation.HeadCount = Convert.ToInt32(Convert.ToDecimal(stats?.HeadCount ?? 0m)); | |
| 4907 | + operation.PersonCount = Convert.ToInt32(Convert.ToDecimal(stats?.PersonCount ?? 0m)); | |
| 4908 | + operation.ProjectCount = Convert.ToInt32(Convert.ToDecimal(stats?.ProjectCount ?? 0m)); | |
| 4909 | + operation.TotalProjectCount = Convert.ToDecimal(stats?.TotalProjectCount ?? 0m); | |
| 4910 | + operation.OriginalProjectCount = Convert.ToDecimal(stats?.OriginalProjectCount ?? 0m); | |
| 4911 | + operation.OvertimeProjectCount = Convert.ToDecimal(stats?.OvertimeProjectCount ?? 0m); | |
| 4912 | + operation.AccompaniedProjectCount = Convert.ToDecimal(stats?.AccompaniedProjectCount ?? 0m); | |
| 4714 | 4913 | operation.AvgAmountPerPerson = operation.PersonCount > 0 ? consumeAmount / (decimal)operation.PersonCount : 0m; |
| 4715 | 4914 | operation.AvgAmountPerProject = operation.ProjectCount > 0 ? consumeAmount / (decimal)operation.ProjectCount : 0m; |
| 4716 | 4915 | operation.AvgProjectPerHead = operation.HeadCount > 0 ? (decimal)operation.ProjectCount / (decimal)operation.HeadCount : 0m; |
| ... | ... | @@ -4730,8 +4929,7 @@ namespace NCC.Extend |
| 4730 | 4929 | FROM lq_khxx |
| 4731 | 4930 | WHERE F_IsEffective = 1 |
| 4732 | 4931 | AND gsmd = '{input.StoreId}' |
| 4733 | - AND khlx = '3'", | |
| 4734 | - new { statisticsMonth }); | |
| 4932 | + AND khlx = '3'"); | |
| 4735 | 4933 | |
| 4736 | 4934 | var member = new MemberAnalysis(); |
| 4737 | 4935 | if (memberStats != null && memberStats.Any()) |
| ... | ... | @@ -5052,7 +5250,7 @@ namespace NCC.Extend |
| 5052 | 5250 | GROUP BY kd.djmd, refund.RefundAmount |
| 5053 | 5251 | ORDER BY (COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) - COALESCE(refund.RefundAmount, 0)) DESC"); |
| 5054 | 5252 | |
| 5055 | - var performanceRanking = 1; | |
| 5253 | + int performanceRanking = 1; | |
| 5056 | 5254 | int totalStoreCount = 0; |
| 5057 | 5255 | if (allStoresPerformance != null) |
| 5058 | 5256 | { |
| ... | ... | @@ -5074,7 +5272,7 @@ namespace NCC.Extend |
| 5074 | 5272 | .Select(x => x.Id) |
| 5075 | 5273 | .ToListAsync(); |
| 5076 | 5274 | |
| 5077 | - var sameTypeStoreCount = sameTypeStores.Count; | |
| 5275 | + int sameTypeStoreCount = sameTypeStores.Count; | |
| 5078 | 5276 | var avgPerformanceSameType = 0m; |
| 5079 | 5277 | if (sameTypeStoreCount > 0) |
| 5080 | 5278 | { |
| ... | ... | @@ -5128,7 +5326,7 @@ namespace NCC.Extend |
| 5128 | 5326 | .ToListAsync(); |
| 5129 | 5327 | } |
| 5130 | 5328 | |
| 5131 | - var sameOrgStoreCount = sameOrgStoreIds.Count; | |
| 5329 | + int sameOrgStoreCount = sameOrgStoreIds.Count; | |
| 5132 | 5330 | var avgPerformanceSameOrg = 0m; |
| 5133 | 5331 | if (sameOrgStoreCount > 0) |
| 5134 | 5332 | { |
| ... | ... | @@ -5818,41 +6016,84 @@ namespace NCC.Extend |
| 5818 | 6016 | throw NCCException.Oh("门店ID不能为空"); |
| 5819 | 6017 | } |
| 5820 | 6018 | |
| 6019 | + // 计算时间范围:近12个月 | |
| 6020 | + var endDate = DateTime.Now; | |
| 6021 | + var startDate = endDate.AddMonths(-11); | |
| 6022 | + var startMonth = new DateTime(startDate.Year, startDate.Month, 1); | |
| 6023 | + | |
| 6024 | + // 使用参数化查询,一次性获取12个月的数据 | |
| 6025 | + var parameters = new List<SugarParameter> | |
| 6026 | + { | |
| 6027 | + new SugarParameter("@StoreId", input.StoreId), | |
| 6028 | + new SugarParameter("@StartDate", startMonth), | |
| 6029 | + new SugarParameter("@EndDate", endDate) | |
| 6030 | + }; | |
| 6031 | + | |
| 6032 | + // 优化:使用单个SQL查询获取所有月份的开单业绩 | |
| 6033 | + var billingSql = @" | |
| 6034 | + SELECT | |
| 6035 | + DATE_FORMAT(kd.kdrq, '%Y%m') as Month, | |
| 6036 | + COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as BillingPerformance | |
| 6037 | + FROM lq_kd_kdjlb kd | |
| 6038 | + WHERE kd.djmd = @StoreId | |
| 6039 | + AND kd.F_IsEffective = 1 | |
| 6040 | + AND kd.kdrq >= @StartDate | |
| 6041 | + AND kd.kdrq <= @EndDate | |
| 6042 | + GROUP BY DATE_FORMAT(kd.kdrq, '%Y%m') | |
| 6043 | + ORDER BY Month"; | |
| 6044 | + | |
| 6045 | + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql, parameters); | |
| 6046 | + var billingDict = billingData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.BillingPerformance ?? 0m)); | |
| 6047 | + | |
| 6048 | + // 优化:使用单个SQL查询获取所有月份的消耗业绩 | |
| 6049 | + var consumeSql = @" | |
| 6050 | + SELECT | |
| 6051 | + DATE_FORMAT(xh.hksj, '%Y%m') as Month, | |
| 6052 | + COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as ConsumePerformance | |
| 6053 | + FROM lq_xh_hyhk xh | |
| 6054 | + WHERE xh.md = @StoreId | |
| 6055 | + AND xh.F_IsEffective = 1 | |
| 6056 | + AND xh.hksj >= @StartDate | |
| 6057 | + AND xh.hksj <= @EndDate | |
| 6058 | + GROUP BY DATE_FORMAT(xh.hksj, '%Y%m') | |
| 6059 | + ORDER BY Month"; | |
| 6060 | + | |
| 6061 | + var consumeData = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql, parameters); | |
| 6062 | + var consumeDict = consumeData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.ConsumePerformance ?? 0m)); | |
| 6063 | + | |
| 6064 | + // 优化:使用单个SQL查询获取所有月份的退卡金额 | |
| 6065 | + var refundSql = @" | |
| 6066 | + SELECT | |
| 6067 | + DATE_FORMAT(tk.tksj, '%Y%m') as Month, | |
| 6068 | + COALESCE(SUM(CAST(COALESCE(tk.F_ActualRefundAmount, tk.tkje, 0) AS DECIMAL(18,2))), 0) as RefundAmount | |
| 6069 | + FROM lq_hytk_hytk tk | |
| 6070 | + WHERE tk.md = @StoreId | |
| 6071 | + AND tk.F_IsEffective = 1 | |
| 6072 | + AND tk.tksj >= @StartDate | |
| 6073 | + AND tk.tksj <= @EndDate | |
| 6074 | + GROUP BY DATE_FORMAT(tk.tksj, '%Y%m') | |
| 6075 | + ORDER BY Month"; | |
| 6076 | + | |
| 6077 | + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql, parameters); | |
| 6078 | + var refundDict = refundData.ToDictionary(x => x.Month?.ToString() ?? "", x => Convert.ToDecimal(x.RefundAmount ?? 0m)); | |
| 6079 | + | |
| 6080 | + // 构建12个月的趋势数据 | |
| 5821 | 6081 | var monthlyTrend = new List<MonthlyTrendPoint>(); |
| 5822 | 6082 | for (int i = 11; i >= 0; i--) |
| 5823 | 6083 | { |
| 5824 | 6084 | var trendMonth = DateTime.Now.AddMonths(-i); |
| 5825 | 6085 | var trendMonthStr = trendMonth.ToString("yyyyMM"); |
| 5826 | - var trendStartDate = new DateTime(trendMonth.Year, trendMonth.Month, 1); | |
| 5827 | - var trendEndDate = trendStartDate.AddMonths(1).AddDays(-1); | |
| 5828 | - var trendEndDateTime = trendEndDate.Date.AddDays(1).AddSeconds(-1); | |
| 5829 | 6086 | |
| 5830 | - if (trendMonthStr == DateTime.Now.ToString("yyyyMM")) | |
| 5831 | - { | |
| 5832 | - trendEndDateTime = DateTime.Now; | |
| 5833 | - } | |
| 5834 | - | |
| 5835 | - var trendBilling = await _db.Queryable<LqKdKdjlbEntity>() | |
| 5836 | - .Where(x => x.Djmd == input.StoreId && x.IsEffective == 1) | |
| 5837 | - .Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= trendStartDate && x.Kdrq.Value <= trendEndDateTime) | |
| 5838 | - .SumAsync(x => (decimal?)x.Sfyj) ?? 0m; | |
| 5839 | - | |
| 5840 | - var trendConsume = await _db.Queryable<LqXhHyhkEntity>() | |
| 5841 | - .Where(x => x.Md == input.StoreId && x.IsEffective == 1) | |
| 5842 | - .Where(x => x.Hksj.HasValue && x.Hksj.Value >= trendStartDate && x.Hksj.Value <= trendEndDateTime) | |
| 5843 | - .SumAsync(x => (decimal?)x.Xfje) ?? 0m; | |
| 5844 | - | |
| 5845 | - var trendRefund = await _db.Queryable<LqHytkHytkEntity>() | |
| 5846 | - .Where(x => x.Md == input.StoreId && x.IsEffective == 1) | |
| 5847 | - .Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= trendStartDate.Date && x.Tksj.Value.Date <= trendEndDate.Date) | |
| 5848 | - .SumAsync(x => (decimal?)(x.ActualRefundAmount ?? (x.Tkje ?? 0))) ?? 0m; | |
| 6087 | + var billing = billingDict.GetValueOrDefault(trendMonthStr, 0m); | |
| 6088 | + var consume = consumeDict.GetValueOrDefault(trendMonthStr, 0m); | |
| 6089 | + var refund = refundDict.GetValueOrDefault(trendMonthStr, 0m); | |
| 5849 | 6090 | |
| 5850 | 6091 | monthlyTrend.Add(new MonthlyTrendPoint |
| 5851 | 6092 | { |
| 5852 | 6093 | Month = trendMonthStr, |
| 5853 | - BillingPerformance = trendBilling, | |
| 5854 | - ConsumePerformance = trendConsume, | |
| 5855 | - NetPerformance = trendBilling - trendRefund | |
| 6094 | + BillingPerformance = billing, | |
| 6095 | + ConsumePerformance = consume, | |
| 6096 | + NetPerformance = billing - refund | |
| 5856 | 6097 | }); |
| 5857 | 6098 | } |
| 5858 | 6099 | |
| ... | ... | @@ -5951,7 +6192,7 @@ namespace NCC.Extend |
| 5951 | 6192 | .Select(x => x.Id) |
| 5952 | 6193 | .ToListAsync(); |
| 5953 | 6194 | |
| 5954 | - int sameTypeStoreCount = sameTypeStores != null ? sameTypeStores.Count : 0; | |
| 6195 | + var sameTypeStoreCount = sameTypeStores != null ? sameTypeStores.Count : 0; | |
| 5955 | 6196 | var avgPerformanceSameType = 0m; |
| 5956 | 6197 | if (sameTypeStoreCount > 0) |
| 5957 | 6198 | { |
| ... | ... | @@ -6005,7 +6246,7 @@ namespace NCC.Extend |
| 6005 | 6246 | .ToListAsync(); |
| 6006 | 6247 | } |
| 6007 | 6248 | |
| 6008 | - int sameOrgStoreCount = sameOrgStoreIds != null ? sameOrgStoreIds.Count : 0; | |
| 6249 | + var sameOrgStoreCount = sameOrgStoreIds != null ? sameOrgStoreIds.Count : 0; | |
| 6009 | 6250 | var avgPerformanceSameOrg = 0m; |
| 6010 | 6251 | if (sameOrgStoreCount > 0) |
| 6011 | 6252 | { | ... | ... |
sql/优化仪表盘接口性能索引.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 优化 get-dashboard-data 接口性能的索引 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明:这些索引专门为仪表盘接口优化 | |
| 5 | +-- 执行前请检查索引是否已存在,避免重复创建 | |
| 6 | + | |
| 7 | +-- ============================================ | |
| 8 | +-- 1. lq_kd_kdjlb (开单记录表) 索引 | |
| 9 | +-- ============================================ | |
| 10 | +-- 用于:门店业绩汇总、健康师业绩汇总、最高开单金额会员查询 | |
| 11 | +-- 查询条件:F_IsEffective, kdrq (时间范围), djmd (门店ID) | |
| 12 | + | |
| 13 | +-- 索引:用于时间范围查询和门店统计(如果已存在 idx_kd_kdjlb_store_date_effective 可跳过) | |
| 14 | +-- CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_effective | |
| 15 | +-- ON lq_kd_kdjlb(djmd, kdrq, F_IsEffective); | |
| 16 | + | |
| 17 | +-- ============================================ | |
| 18 | +-- 2. lq_hytk_hytk (退卡表) 索引 | |
| 19 | +-- ============================================ | |
| 20 | +-- 用于:退卡金额统计 | |
| 21 | +-- 查询条件:F_IsEffective, tksj (退卡时间) | |
| 22 | + | |
| 23 | +-- 索引:用于时间范围查询 | |
| 24 | +CREATE INDEX IF NOT EXISTS idx_hytk_effective_date | |
| 25 | +ON lq_hytk_hytk(F_IsEffective, tksj); | |
| 26 | + | |
| 27 | +-- ============================================ | |
| 28 | +-- 3. lq_xh_hyhk (耗卡记录表) 索引 | |
| 29 | +-- ============================================ | |
| 30 | +-- 用于:消耗业绩汇总、最高消耗金额会员查询 | |
| 31 | +-- 查询条件:F_IsEffective, hksj (耗卡时间), hy (会员ID) | |
| 32 | + | |
| 33 | +-- 索引1:用于时间范围查询 | |
| 34 | +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_effective_date | |
| 35 | +ON lq_xh_hyhk(F_IsEffective, hksj); | |
| 36 | + | |
| 37 | +-- 索引2:用于会员统计(补充) | |
| 38 | +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_member_effective | |
| 39 | +ON lq_xh_hyhk(hy, F_IsEffective, hksj); | |
| 40 | + | |
| 41 | +-- ============================================ | |
| 42 | +-- 4. lq_xh_pxmx (耗卡品项明细表) 索引 | |
| 43 | +-- ============================================ | |
| 44 | +-- 用于:消耗业绩汇总(JOIN) | |
| 45 | +-- 查询条件:F_ConsumeInfoId, F_IsEffective | |
| 46 | + | |
| 47 | +-- 索引:用于JOIN(如果已存在可跳过) | |
| 48 | +-- CREATE INDEX IF NOT EXISTS idx_xh_pxmx_consume_effective | |
| 49 | +-- ON lq_xh_pxmx(F_ConsumeInfoId, F_IsEffective); | |
| 50 | + | |
| 51 | +-- ============================================ | |
| 52 | +-- 5. lq_khxx (客户信息表) 索引 | |
| 53 | +-- ============================================ | |
| 54 | +-- 用于:会员统计汇总、会员类型分布、最高剩余权益会员 | |
| 55 | +-- 查询条件:F_IsEffective, khlx (客户类型), F_CreateTime, F_SleepDays, F_RemainingRightsAmount | |
| 56 | + | |
| 57 | +-- 索引1:用于会员类型和有效性过滤(最常用) | |
| 58 | +CREATE INDEX IF NOT EXISTS idx_khxx_type_effective | |
| 59 | +ON lq_khxx(khlx, F_IsEffective); | |
| 60 | + | |
| 61 | +-- 索引2:用于创建时间统计(新会员统计) | |
| 62 | +CREATE INDEX IF NOT EXISTS idx_khxx_create_time | |
| 63 | +ON lq_khxx(F_CreateTime, F_IsEffective, khlx); | |
| 64 | + | |
| 65 | +-- 索引3:用于剩余权益排序(最高剩余权益会员) | |
| 66 | +CREATE INDEX IF NOT EXISTS idx_khxx_remaining_effective | |
| 67 | +ON lq_khxx(F_RemainingRightsAmount, F_IsEffective); | |
| 68 | + | |
| 69 | +-- ============================================ | |
| 70 | +-- 6. 验证索引创建 | |
| 71 | +-- ============================================ | |
| 72 | +-- 验证索引是否创建成功 | |
| 73 | +-- SELECT | |
| 74 | +-- TABLE_NAME, | |
| 75 | +-- INDEX_NAME, | |
| 76 | +-- COLUMN_NAME, | |
| 77 | +-- SEQ_IN_INDEX | |
| 78 | +-- FROM INFORMATION_SCHEMA.STATISTICS | |
| 79 | +-- WHERE TABLE_SCHEMA = DATABASE() | |
| 80 | +-- AND TABLE_NAME IN ('lq_kd_kdjlb', 'lq_hytk_hytk', 'lq_xh_hyhk', 'lq_xh_pxmx', 'lq_khxx') | |
| 81 | +-- AND INDEX_NAME LIKE 'idx_%' | |
| 82 | +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX; | |
| 83 | + | ... | ... |
sql/优化金三角业绩排行榜接口性能索引.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 优化 get-gold-triangle-performance-ranking 接口性能的索引 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明:这些索引专门为金三角业绩排行榜接口优化 | |
| 5 | +-- 执行前请检查索引是否已存在,避免重复创建 | |
| 6 | + | |
| 7 | +-- ============================================ | |
| 8 | +-- 1. lq_ycsd_jsj (金三角设定表) 索引 | |
| 9 | +-- ============================================ | |
| 10 | +-- 用于:WHERE jsj.yf = @StatisticsMonth 查询条件 | |
| 11 | +-- 查询条件:yf (月份) | |
| 12 | + | |
| 13 | +-- 索引:用于月份查询(最常用) | |
| 14 | +CREATE INDEX IF NOT EXISTS idx_ycsd_jsj_yf | |
| 15 | +ON lq_ycsd_jsj(yf); | |
| 16 | + | |
| 17 | +-- ============================================ | |
| 18 | +-- 2. lq_jinsanjiao_user (金三角用户绑定表) 索引 | |
| 19 | +-- ============================================ | |
| 20 | +-- 用于:JOIN和过滤条件 | |
| 21 | +-- 查询条件:status = 'ACTIVE', F_DeleteMark = 0, user_id (用于JOIN) | |
| 22 | + | |
| 23 | +-- 索引1:用于JOIN和状态过滤(最常用) | |
| 24 | +CREATE INDEX IF NOT EXISTS idx_jinsanjiao_user_user_status_delete | |
| 25 | +ON lq_jinsanjiao_user(user_id, status, F_DeleteMark); | |
| 26 | + | |
| 27 | +-- 索引2:用于反向JOIN(jsj_id用于关联金三角设定) | |
| 28 | +CREATE INDEX IF NOT EXISTS idx_jinsanjiao_user_jsj_status_delete | |
| 29 | +ON lq_jinsanjiao_user(jsj_id, status, F_DeleteMark); | |
| 30 | + | |
| 31 | +-- ============================================ | |
| 32 | +-- 3. lq_kd_jksyj (开单健康师业绩表) 索引 | |
| 33 | +-- ============================================ | |
| 34 | +-- 用于:JOIN和过滤条件 | |
| 35 | +-- 查询条件:jkszh (用于JOIN), F_IsEffective = 1 | |
| 36 | + | |
| 37 | +-- 索引:用于JOIN和有效性过滤 | |
| 38 | +CREATE INDEX IF NOT EXISTS idx_kd_jksyj_jkszh_effective | |
| 39 | +ON lq_kd_jksyj(jkszh, F_IsEffective); | |
| 40 | + | |
| 41 | +-- ============================================ | |
| 42 | +-- 4. lq_kd_kdjlb (开单记录表) 索引 | |
| 43 | +-- ============================================ | |
| 44 | +-- 用于:JOIN和过滤条件 | |
| 45 | +-- 查询条件:F_Id (用于JOIN), F_IsEffective = 1, kdrq (时间范围) | |
| 46 | + | |
| 47 | +-- 索引:用于JOIN、有效性过滤和时间范围查询 | |
| 48 | +-- 注意:如果 idx_kd_kdjlb_store_date_effective 已存在,此索引可能冗余 | |
| 49 | +-- 但为了JOIN性能,保留此索引 | |
| 50 | +CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_id_effective_date | |
| 51 | +ON lq_kd_kdjlb(F_Id, F_IsEffective, kdrq); | |
| 52 | + | |
| 53 | +-- ============================================ | |
| 54 | +-- 5. 验证索引创建 | |
| 55 | +-- ============================================ | |
| 56 | +-- 验证索引是否创建成功 | |
| 57 | +-- SELECT | |
| 58 | +-- TABLE_NAME, | |
| 59 | +-- INDEX_NAME, | |
| 60 | +-- COLUMN_NAME, | |
| 61 | +-- SEQ_IN_INDEX | |
| 62 | +-- FROM INFORMATION_SCHEMA.STATISTICS | |
| 63 | +-- WHERE TABLE_SCHEMA = DATABASE() | |
| 64 | +-- AND TABLE_NAME IN ('lq_ycsd_jsj', 'lq_jinsanjiao_user', 'lq_kd_jksyj', 'lq_kd_kdjlb') | |
| 65 | +-- AND INDEX_NAME LIKE 'idx_%' | |
| 66 | +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX; | |
| 67 | + | ... | ... |
sql/优化门店月度趋势接口性能索引.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 优化 get-store-monthly-trend 接口性能的索引 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明:这些索引专门为门店月度趋势接口优化 | |
| 5 | +-- 执行前请检查索引是否已存在,避免重复创建 | |
| 6 | + | |
| 7 | +-- ============================================ | |
| 8 | +-- 1. lq_kd_kdjlb (开单记录表) 索引 | |
| 9 | +-- ============================================ | |
| 10 | +-- 用于:开单业绩按月统计 | |
| 11 | +-- 查询条件:djmd (门店ID), F_IsEffective, kdrq (开单日期) | |
| 12 | +-- 分组:DATE_FORMAT(kdrq, '%Y%m') | |
| 13 | + | |
| 14 | +-- 索引:用于门店+时间范围查询(如果已存在 idx_kd_kdjlb_store_date_effective 可跳过) | |
| 15 | +-- CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_effective | |
| 16 | +-- ON lq_kd_kdjlb(djmd, kdrq, F_IsEffective); | |
| 17 | + | |
| 18 | +-- 补充索引:用于月份分组查询(优化GROUP BY性能) | |
| 19 | +CREATE INDEX IF NOT EXISTS idx_kd_kdjlb_store_date_month | |
| 20 | +ON lq_kd_kdjlb(djmd, F_IsEffective, kdrq); | |
| 21 | + | |
| 22 | +-- ============================================ | |
| 23 | +-- 2. lq_xh_hyhk (耗卡记录表) 索引 | |
| 24 | +-- ============================================ | |
| 25 | +-- 用于:消耗业绩按月统计 | |
| 26 | +-- 查询条件:md (门店ID), F_IsEffective, hksj (耗卡时间) | |
| 27 | +-- 分组:DATE_FORMAT(hksj, '%Y%m') | |
| 28 | + | |
| 29 | +-- 索引:用于门店+时间范围查询 | |
| 30 | +CREATE INDEX IF NOT EXISTS idx_xh_hyhk_store_date_month | |
| 31 | +ON lq_xh_hyhk(md, F_IsEffective, hksj); | |
| 32 | + | |
| 33 | +-- ============================================ | |
| 34 | +-- 3. lq_hytk_hytk (退卡表) 索引 | |
| 35 | +-- ============================================ | |
| 36 | +-- 用于:退卡金额按月统计 | |
| 37 | +-- 查询条件:md (门店ID), F_IsEffective, tksj (退卡时间) | |
| 38 | +-- 分组:DATE_FORMAT(tksj, '%Y%m') | |
| 39 | + | |
| 40 | +-- 索引:用于门店+时间范围查询(如果已存在 idx_hytk_effective_date 可跳过) | |
| 41 | +-- CREATE INDEX IF NOT EXISTS idx_hytk_effective_date | |
| 42 | +-- ON lq_hytk_hytk(F_IsEffective, tksj); | |
| 43 | + | |
| 44 | +-- 补充索引:用于门店+时间范围查询(优化门店维度查询) | |
| 45 | +CREATE INDEX IF NOT EXISTS idx_hytk_store_date_month | |
| 46 | +ON lq_hytk_hytk(md, F_IsEffective, tksj); | |
| 47 | + | |
| 48 | +-- ============================================ | |
| 49 | +-- 4. 验证索引创建 | |
| 50 | +-- ============================================ | |
| 51 | +-- 验证索引是否创建成功 | |
| 52 | +-- SELECT | |
| 53 | +-- TABLE_NAME, | |
| 54 | +-- INDEX_NAME, | |
| 55 | +-- COLUMN_NAME, | |
| 56 | +-- SEQ_IN_INDEX | |
| 57 | +-- FROM INFORMATION_SCHEMA.STATISTICS | |
| 58 | +-- WHERE TABLE_SCHEMA = DATABASE() | |
| 59 | +-- AND TABLE_NAME IN ('lq_kd_kdjlb', 'lq_xh_hyhk', 'lq_hytk_hytk') | |
| 60 | +-- AND INDEX_NAME LIKE 'idx_%' | |
| 61 | +-- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX; | |
| 62 | + | ... | ... |