perf(selfop): aggregate daily facts before SKU coverage calculations
This commit is contained in:
+24
-1
@@ -33,7 +33,10 @@ public final class AnalyticsReadSql {
|
||||
String localEvidence="CONVERT_TZ(evidence_at_utc,'+00:00','+08:00')";
|
||||
String observedStart="CONVERT_TZ(GREATEST(evidence_at_utc,#{p.coveredFrom}),'+00:00','+08:00')";
|
||||
String observedEnd="CONVERT_TZ(#{p.coveredUntil},'+00:00','+08:00')";
|
||||
String sql=candidates("SUMMARY".equals(kind))+" bounds AS (SELECT evidence.*,"
|
||||
// Candidate identity is already unique. DISTINCT preserves that identity while
|
||||
// preventing MySQL from merging these per-SKU date calculations into every fact row.
|
||||
String boundaryProjection="SUMMARY".equals(kind)?"SELECT DISTINCT evidence.*,":"SELECT evidence.*,";
|
||||
String sql=candidates("SUMMARY".equals(kind))+" bounds AS ("+boundaryProjection
|
||||
+"CASE WHEN evidence_at_utc IS NOT NULL AND #{p.completeFrom} IS NOT NULL AND #{p.completeUntil} IS NOT NULL THEN "
|
||||
+"GREATEST(#{p.from},#{p.completeFrom},TIMESTAMPADD(DAY,CASE WHEN CAST("+localEvidence+" AS TIME)='00:00:00' THEN 0 ELSE 1 END,DATE("+localEvidence+"))) END full_start,"
|
||||
+"LEAST(#{p.until},#{p.completeUntil},#{p.anchor}) full_end,"
|
||||
@@ -84,6 +87,26 @@ public final class AnalyticsReadSql {
|
||||
+"FROM counted c LEFT JOIN ll_selfop_inventory_snapshot st ON st.snapshot_run_id=#{p.inventoryRun} AND st.source_account=#{p.account} "
|
||||
+"AND st.warehouse_code=#{p.warehouse} AND st.customer_code=c.customer_code AND st.sku_code=c.sku_code AND st.product_quality=0) ";
|
||||
if("SUMMARY".equals(kind)){
|
||||
// Aggregate facts before joining SKU evidence. Repeating boundary expressions
|
||||
// inside every fact's SUM dominated the long-window MySQL execution profile.
|
||||
int rangeStart=sql.indexOf(" ranged AS (");int rangeEnd=sql.indexOf(" metric_sales AS (",rangeStart);
|
||||
sql=sql.substring(0,rangeStart)+" ranged AS (SELECT d.customer_code,d.sku_code,SUM(d.outbound_quantity) observed_sum,"
|
||||
+"COUNT(*) actual_days,SUM(d.outbound_order_count) order_sum,MIN(d.quantity_unit) min_unit,MAX(d.quantity_unit) max_unit,"
|
||||
+"SUM(CASE WHEN d.business_date>=#{p.completeFrom} AND d.business_date<#{p.completeUntil} AND d.business_date<#{p.anchor} THEN 1 ELSE 0 END) actual_complete "
|
||||
+"FROM ll_selfop_sku_daily_sales d WHERE d.sales_run_id=#{p.salesRun} AND"+KEY_FILTER
|
||||
+" AND d.business_date>=#{p.from} AND d.business_date<#{p.until} GROUP BY d.customer_code,d.sku_code),"+sql.substring(rangeEnd);
|
||||
String scopedCount="SELECT COUNT(*) FROM ll_selfop_sku_daily_sales d2 WHERE d2.sales_run_id=#{p.salesRun} "
|
||||
+"AND d2.source_account=#{p.account} AND d2.warehouse_code=#{p.warehouse} "
|
||||
+"AND d2.customer_code=b.customer_code AND d2.sku_code=b.sku_code ";
|
||||
String fullSpan="CASE WHEN r.actual_days IS NULL THEN 0 "
|
||||
+"WHEN b.full_start<=GREATEST(#{p.from},#{p.completeFrom}) AND b.full_end>=LEAST(#{p.until},#{p.completeUntil},#{p.anchor}) "
|
||||
+"THEN r.actual_complete ELSE ("+scopedCount+"AND d2.business_date>=GREATEST(#{p.from},b.full_start) "
|
||||
+"AND d2.business_date<LEAST(#{p.until},b.full_end)) END";
|
||||
String observedSpan="CASE WHEN r.actual_days IS NULL THEN 0 "
|
||||
+"WHEN b.observed_start<=#{p.from} AND b.observed_end>=#{p.until} THEN r.actual_days ELSE ("+scopedCount
|
||||
+"AND d2.business_date>=GREATEST(#{p.from},b.observed_start) AND d2.business_date<LEAST(#{p.until},b.observed_end)) END";
|
||||
sql=sql.replace("COALESCE(r.actual_full_span,0)","("+fullSpan+")")
|
||||
.replace("COALESCE(r.actual_observed_span,0)","("+observedSpan+")");
|
||||
int start=sql.indexOf(" metric_sales AS (");int end=sql.indexOf(" counted AS (",start);
|
||||
// An empty aggregate joined back through bounds can still be merged into
|
||||
// another candidate cross product by MySQL. Summary has no metric join at all.
|
||||
|
||||
+7
-2
@@ -57,11 +57,16 @@ class AnalyticsReadMapperTest {
|
||||
assertFalse(count.contains("ll_selfop_sku_daily_sales")); assertFalse(count.contains("ll_selfop_inventory_snapshot"));
|
||||
String page=bound(c,"page",parameters).getSql();
|
||||
assertTrue(page.contains("d.sales_run_id=?")); assertTrue(page.contains("d.business_date>=CAST(? AS DATE) AND d.business_date<CAST(? AS DATE)"));
|
||||
assertTrue(page.contains("GROUP BY b.customer_code,b.sku_code")); assertFalse(page.contains("business_date>=DATE_SUB"));
|
||||
assertTrue(page.contains("GROUP BY d.customer_code,d.sku_code")); assertFalse(page.contains("business_date>=DATE_SUB"));
|
||||
assertFalse(page.contains("ll_selfop_inventory_snapshot"));
|
||||
assertFalse(page.contains("metric_sales"));assertFalse(page.contains("FROM bounds WHERE 1=0"));
|
||||
assertTrue(page.contains("bounds AS (SELECT DISTINCT evidence.*,"));
|
||||
assertTrue(page.endsWith("ORDER BY observed_quantity IS NULL,observed_quantity DESC,sku_code ASC,customer_code ASC LIMIT ? OFFSET ?"));
|
||||
assertTrue(page.indexOf("GROUP BY b.customer_code,b.sku_code")<page.lastIndexOf("LIMIT")); immutableOnly(page);
|
||||
assertTrue(page.indexOf("GROUP BY d.customer_code,d.sku_code")<page.lastIndexOf("LIMIT"));
|
||||
assertFalse(page.contains("FROM bounds b JOIN ll_selfop_sku_daily_sales"));
|
||||
assertTrue(page.contains("d2.customer_code=b.customer_code AND d2.sku_code=b.sku_code"));
|
||||
assertTrue(page.contains("d2.sales_run_id=? AND d2.source_account=? AND d2.warehouse_code=?"));
|
||||
immutableOnly(page);
|
||||
}
|
||||
@Test void metricsPreFilterQualityAndStableSortAggregatesBeforePagination(){
|
||||
var c=config();
|
||||
|
||||
+21
@@ -163,6 +163,27 @@ public class AnalyticsSqlSemanticTest {
|
||||
var totals=totals(request("INVENTORY",D.minusDays(30),D,"C",null,utc(D,8),0,20),new AnalyticsInventoryOptions(null,null,null,30));
|
||||
assertDecimal("12",(BigDecimal)totals.get("owner_outbound_known"));assertEquals(0L,((Number)totals.get("owner_outbound_unknown")).longValue());
|
||||
}
|
||||
@Test void yearlySparseSummaryPreservesOwnerIdentityAndCompletePartialUnknownSpanCounts() throws Exception {
|
||||
for(String owner:List.of("A","B")){
|
||||
profile(11,owner,"SAME","VALID_OUTBOUND",utc(D.minusDays(400),0),"PCS");
|
||||
profile(22,owner,"SAME","INVENTORY_OBSERVATION",utc(D.minusDays(400),0),"PCS");
|
||||
}
|
||||
profile(22,"A","NONE","NONE",null,"PCS");
|
||||
profile(22,"A","LATE","INVENTORY_OBSERVATION",utc(D.plusDays(1),0),"PCS");
|
||||
daily(11,"W","A","SAME",D.minusDays(364),"1");
|
||||
daily(11,"W","A","SAME",D.minusDays(1),"2");daily(11,"W","A","SAME",D,"5");
|
||||
daily(11,"W","B","SAME",D.minusDays(2),"9");
|
||||
var p=new AnalyticsSqlRequest("demo","W",null,null,11,22,D.minusDays(365),D.plusDays(1),D,D.minusDays(30),
|
||||
D.minusDays(365),D,utc(D.minusDays(365),0),utc(D,8),true,true,false,true,true,0,20,"SUMMARY");
|
||||
assertEquals(4,mapper.count(p));var rows=page(p);assertEquals(4,rows.size());
|
||||
for(var row:rows)if("SAME".equals(row.skuCode())){
|
||||
assertDecimal("A".equals(row.customerCode())?"8":"9",row.observedQuantity());
|
||||
assertEquals(365,row.completeDayCount());assertEquals(1,row.partialDayCount());assertEquals(0,row.unknownDayCount());
|
||||
}else{
|
||||
assertNull(row.observedQuantity());assertEquals(0,row.completeDayCount());
|
||||
assertEquals(0,row.partialDayCount());assertEquals(366,row.unknownDayCount());
|
||||
}
|
||||
}
|
||||
@Test void rankingStatisticsPreserveFullEligibilityTopCapExclusionsAndSharedSkuOwners() throws Exception {
|
||||
for(int i=0;i<110;i++){
|
||||
String sku=String.format("SKU-%03d",i);profile(11,"C",sku,"VALID_OUTBOUND",utc(D.minusDays(40),0),"PCS");
|
||||
|
||||
Reference in New Issue
Block a user