perf: 全量物化视图优化 — 按月预计算所有慢函数,API响应从106秒降至0.09秒

- 创建12个按月物化视图替代实时函数调用
- phase10: mv_dish_sku_summary_monthly, mv_dish_sku_abc_monthly
- phase11: mv_store_risk_rating/platform_economics/category_mix/member_opportunity/benchmark_composite/deep_diagnosis/dish_basket/dish_store_summary/theoretical_actual_cost (全部_monthly)
- 后端所有路由从 fn_xxx() 改为 mv_xxx_monthly WHERE month_start =
- 新增 analytics.refresh_all_monthly_views() 刷新函数
- 优化 fn_dish_sku_summary: CTE物化避免重复调用
- 优化 fn_dish_sku_abc: CTE物化 fn_dish_sku_summary 只调用一次
- 优化 fn_dish_sales: 添加 dish_name IS NOT NULL 提前过滤
This commit is contained in:
freedakgmail
2026-08-01 14:05:12 +08:00
parent dab06ba109
commit 25db0f3854
22 changed files with 2761 additions and 71 deletions
+43 -19
View File
@@ -21,23 +21,45 @@ RETURNS TABLE (
realized_unit_price numeric,
revenue_share_pct numeric
) AS $$
WITH sales AS MATERIALIZED (
SELECT * FROM analytics.fn_dish_sales(p_month)
),
dish_agg AS (
SELECT
s.dish_name,
min(s.category_level1) AS category_level1,
min(s.category_level2) AS category_level2,
count(*) AS detail_rows,
sum(s.sales_quantity) AS sales_quantity,
sum(s.gross_amount) AS gross_amount,
sum(s.received_amount) AS received_amount,
sum(s.dish_discount_amount) AS discount_amount
FROM sales s
GROUP BY s.dish_name
),
bill_counts AS (
SELECT s.dish_name,
count(DISTINCT s.store_code) AS store_count,
count(DISTINCT s.store_code || '|' || s.bill_no) AS bill_count
FROM sales s
GROUP BY s.dish_name
)
SELECT
s.dish_name,
min(s.category_level1) AS category_level1,
min(s.category_level2) AS category_level2,
count(*) AS detail_rows,
count(DISTINCT ROW(s.store_code, s.bill_no)) AS bill_count,
count(DISTINCT s.store_code) AS store_count,
sum(s.sales_quantity) AS sales_quantity,
sum(s.gross_amount) AS gross_amount,
sum(s.received_amount) AS received_amount,
sum(s.dish_discount_amount) AS discount_amount,
round(sum(s.dish_discount_amount) / NULLIF(sum(s.gross_amount), 0) * 100, 2) AS discount_rate_pct,
round(sum(s.received_amount) / NULLIF(sum(s.sales_quantity), 0), 2) AS realized_unit_price,
round(sum(s.received_amount) / NULLIF(sum(sum(s.received_amount)) OVER (), 0) * 100, 4) AS revenue_share_pct
FROM analytics.fn_dish_sales(p_month) s
WHERE s.dish_name IS NOT NULL
GROUP BY s.dish_name
a.dish_name,
a.category_level1,
a.category_level2,
a.detail_rows,
bc.bill_count,
bc.store_count,
a.sales_quantity,
a.gross_amount,
a.received_amount,
a.discount_amount,
round(a.discount_amount / NULLIF(a.gross_amount, 0) * 100, 2) AS discount_rate_pct,
round(a.received_amount / NULLIF(a.sales_quantity, 0), 2) AS realized_unit_price,
round(a.received_amount / NULLIF(sum(a.received_amount) OVER (), 0) * 100, 4) AS revenue_share_pct
FROM dish_agg a
JOIN bill_counts bc ON a.dish_name = bc.dish_name
$$ LANGUAGE SQL STABLE;
-- 2. fn_dish_sku_abc(p_month) — 替换 v_dish_sku_abc_april
@@ -62,11 +84,13 @@ RETURNS TABLE (
abc_class text,
sales_quadrant text
) AS $$
WITH medians AS (
WITH sku_summary AS MATERIALIZED (
SELECT * FROM analytics.fn_dish_sku_summary(p_month)
), medians AS (
SELECT
percentile_cont(0.5) WITHIN GROUP (ORDER BY s.sales_quantity::double precision) AS median_quantity,
percentile_cont(0.5) WITHIN GROUP (ORDER BY s.received_amount::double precision) AS median_revenue
FROM analytics.fn_dish_sku_summary(p_month) s
FROM sku_summary s
), ranked AS (
SELECT
s.dish_name,
@@ -86,7 +110,7 @@ RETURNS TABLE (
/ NULLIF(sum(s.received_amount) OVER (), 0) AS cumulative_revenue_share,
m.median_quantity,
m.median_revenue
FROM analytics.fn_dish_sku_summary(p_month) s
FROM sku_summary s
CROSS JOIN medians m
)
SELECT