6e0c1bd7a4
- 创建选址分析7个SQL视图+5张物化表(38s→33ms) - 后端新增5个选址API端点 - 前端新增SiteSelectionPage页面(散点图+4Tab) - 侧边栏新增门店选址导航入口 - 修复SQL列引用错误(d.→a.,去掉重复列) - 创建v_store_action_priority_deep_april和v_store_area_efficiency_april基础视图
493 lines
25 KiB
SQL
493 lines
25 KiB
SQL
-- ============================================================
|
||
-- 10_materialize_slow_views.sql
|
||
-- 将门店详情页慢视图转为物化视图,加 store_code 索引
|
||
-- 预期:/stores/:code 从 ~15s 降至 <100ms
|
||
-- ============================================================
|
||
|
||
-- ============================================================
|
||
-- Step 1: 依赖视图按逆序 DROP
|
||
-- ============================================================
|
||
DROP VIEW IF EXISTS analytics.v_store_execution_priority;
|
||
DROP VIEW IF EXISTS analytics.v_store_deep_diagnosis_april;
|
||
DROP VIEW IF EXISTS analytics.v_store_benchmark_composite;
|
||
DROP VIEW IF EXISTS analytics.v_store_action_list;
|
||
|
||
-- ============================================================
|
||
-- Step 2: 基础视图 → 物化视图
|
||
-- ============================================================
|
||
|
||
-- 2a. v_store_platform_economics
|
||
DROP VIEW IF EXISTS analytics.v_store_platform_economics;
|
||
CREATE MATERIALIZED VIEW analytics.v_store_platform_economics AS
|
||
SELECT NULLIF(bill_records.c002, ''::text) AS store_code,
|
||
NULLIF(bill_records.c003, ''::text) AS store_name,
|
||
sum(COALESCE(NULLIF(bill_records.c151, ''::text)::numeric, 0::numeric)) AS meituan_received,
|
||
sum(COALESCE(NULLIF(bill_records.c101, ''::text)::numeric, 0::numeric)) AS meituan_discount,
|
||
sum(COALESCE(NULLIF(bill_records.c097, ''::text)::numeric, 0::numeric)) AS meituan_commission,
|
||
sum(COALESCE(NULLIF(bill_records.c152, ''::text)::numeric, 0::numeric)) AS taobao_received,
|
||
sum(COALESCE(NULLIF(bill_records.c102, ''::text)::numeric, 0::numeric)) AS taobao_discount,
|
||
sum(COALESCE(NULLIF(bill_records.c098, ''::text)::numeric, 0::numeric)) AS taobao_commission,
|
||
sum(COALESCE(NULLIF(bill_records.c150, ''::text)::numeric, 0::numeric)) AS jd_received,
|
||
sum(COALESCE(NULLIF(bill_records.c099, ''::text)::numeric, 0::numeric)) AS jd_discount,
|
||
sum(COALESCE(NULLIF(bill_records.c100, ''::text)::numeric, 0::numeric)) AS jd_commission,
|
||
round((sum(COALESCE(NULLIF(bill_records.c101, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c097, ''::text)::numeric, 0::numeric))) / NULLIF(sum(COALESCE(NULLIF(bill_records.c151, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c101, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c097, ''::text)::numeric, 0::numeric)), 0::numeric) * 100::numeric, 2) AS meituan_cost_rate_pct,
|
||
round((sum(COALESCE(NULLIF(bill_records.c102, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c098, ''::text)::numeric, 0::numeric))) / NULLIF(sum(COALESCE(NULLIF(bill_records.c152, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c102, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c098, ''::text)::numeric, 0::numeric)), 0::numeric) * 100::numeric, 2) AS taobao_cost_rate_pct,
|
||
round((sum(COALESCE(NULLIF(bill_records.c099, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c100, ''::text)::numeric, 0::numeric))) / NULLIF(sum(COALESCE(NULLIF(bill_records.c150, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c099, ''::text)::numeric, 0::numeric)) + sum(COALESCE(NULLIF(bill_records.c100, ''::text)::numeric, 0::numeric)), 0::numeric) * 100::numeric, 2) AS jd_cost_rate_pct
|
||
FROM bill_records
|
||
WHERE NULLIF(bill_records.c005, ''::text) IS NOT NULL
|
||
GROUP BY (NULLIF(bill_records.c002, ''::text)), (NULLIF(bill_records.c003, ''::text))
|
||
WITH DATA;
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_platform_economics_store ON analytics.v_store_platform_economics (store_code);
|
||
|
||
-- 2b. v_store_category_mix
|
||
DROP VIEW IF EXISTS analytics.v_store_category_mix;
|
||
CREATE MATERIALIZED VIEW analytics.v_store_category_mix AS
|
||
SELECT NULLIF(bill_records.c002, ''::text) AS store_code,
|
||
NULLIF(bill_records.c003, ''::text) AS store_name,
|
||
sum(COALESCE(NULLIF(bill_records.c009, ''::text)::numeric, 0::numeric)) AS consumption,
|
||
sum(COALESCE(NULLIF(bill_records.c010, ''::text)::numeric, 0::numeric)) AS lanzhou_noodle,
|
||
sum(COALESCE(NULLIF(bill_records.c016, ''::text)::numeric, 0::numeric)) AS western_staple,
|
||
sum(COALESCE(NULLIF(bill_records.c027, ''::text)::numeric, 0::numeric)) AS delivery_package,
|
||
sum(COALESCE(NULLIF(bill_records.c013, ''::text)::numeric, 0::numeric)) AS night_bbq,
|
||
sum(COALESCE(NULLIF(bill_records.c015, ''::text)::numeric, 0::numeric)) AS cold_dishes,
|
||
sum(COALESCE(NULLIF(bill_records.c019, ''::text)::numeric, 0::numeric)) AS silk_road_food,
|
||
round(sum(COALESCE(NULLIF(bill_records.c010, ''::text)::numeric, 0::numeric)) / NULLIF(sum(COALESCE(NULLIF(bill_records.c009, ''::text)::numeric, 0::numeric)), 0::numeric) * 100::numeric, 2) AS noodle_share_pct,
|
||
round(sum(COALESCE(NULLIF(bill_records.c027, ''::text)::numeric, 0::numeric)) / NULLIF(sum(COALESCE(NULLIF(bill_records.c009, ''::text)::numeric, 0::numeric)), 0::numeric) * 100::numeric, 2) AS delivery_package_share_pct,
|
||
round(GREATEST(sum(COALESCE(NULLIF(bill_records.c010, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c016, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c027, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c013, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c015, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c019, ''::text)::numeric, 0::numeric))) / NULLIF(sum(COALESCE(NULLIF(bill_records.c009, ''::text)::numeric, 0::numeric)), 0::numeric) * 100::numeric, 2) AS top_category_share_pct,
|
||
CASE GREATEST(sum(COALESCE(NULLIF(bill_records.c010, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c016, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c027, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c013, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c015, ''::text)::numeric, 0::numeric)), sum(COALESCE(NULLIF(bill_records.c019, ''::text)::numeric, 0::numeric)))
|
||
WHEN sum(COALESCE(NULLIF(bill_records.c010, ''::text)::numeric, 0::numeric)) THEN '兰州牛肉面'::text
|
||
WHEN sum(COALESCE(NULLIF(bill_records.c016, ''::text)::numeric, 0::numeric)) THEN '西部主食'::text
|
||
WHEN sum(COALESCE(NULLIF(bill_records.c027, ''::text)::numeric, 0::numeric)) THEN '外卖套餐'::text
|
||
WHEN sum(COALESCE(NULLIF(bill_records.c013, ''::text)::numeric, 0::numeric)) THEN '夜市烧烤'::text
|
||
WHEN sum(COALESCE(NULLIF(bill_records.c015, ''::text)::numeric, 0::numeric)) THEN '爽口凉菜'::text
|
||
ELSE '丝路美食'::text
|
||
END AS top_category
|
||
FROM bill_records
|
||
WHERE NULLIF(bill_records.c005, ''::text) IS NOT NULL
|
||
GROUP BY (NULLIF(bill_records.c002, ''::text)), (NULLIF(bill_records.c003, ''::text))
|
||
WITH DATA;
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_category_mix_store ON analytics.v_store_category_mix (store_code);
|
||
|
||
-- 2c. v_store_benchmark
|
||
DROP VIEW IF EXISTS analytics.v_store_benchmark;
|
||
CREATE MATERIALIZED VIEW analytics.v_store_benchmark AS
|
||
WITH eligible AS (
|
||
SELECT v_store_risk_rating.store_code,
|
||
v_store_risk_rating.store_name,
|
||
v_store_risk_rating.bill_count,
|
||
v_store_risk_rating.active_days,
|
||
v_store_risk_rating.received,
|
||
v_store_risk_rating.avg_daily_received,
|
||
v_store_risk_rating.avg_bill_value,
|
||
v_store_risk_rating.avg_guest_value,
|
||
v_store_risk_rating.discount_rate_pct,
|
||
v_store_risk_rating.theoretical_margin_pct,
|
||
v_store_risk_rating.member_bill_share_pct,
|
||
v_store_risk_rating.anomaly_rate_pct,
|
||
v_store_risk_rating.risk_level,
|
||
v_store_risk_rating.primary_issue
|
||
FROM analytics.v_store_risk_rating
|
||
WHERE v_store_risk_rating.active_days >= 25 AND v_store_risk_rating.bill_count >= 5000
|
||
), stats AS (
|
||
SELECT percentile_cont(0.25::double precision) WITHIN GROUP (ORDER BY (eligible.avg_daily_received::double precision)) AS revenue_p25,
|
||
percentile_cont(0.50::double precision) WITHIN GROUP (ORDER BY (eligible.avg_daily_received::double precision)) AS revenue_p50,
|
||
percentile_cont(0.75::double precision) WITHIN GROUP (ORDER BY (eligible.avg_daily_received::double precision)) AS revenue_p75,
|
||
percentile_cont(0.50::double precision) WITHIN GROUP (ORDER BY (eligible.theoretical_margin_pct::double precision)) AS margin_p50,
|
||
percentile_cont(0.75::double precision) WITHIN GROUP (ORDER BY (eligible.theoretical_margin_pct::double precision)) AS margin_p75,
|
||
percentile_cont(0.75::double precision) WITHIN GROUP (ORDER BY (eligible.discount_rate_pct::double precision)) AS discount_p75
|
||
FROM eligible
|
||
), base AS (
|
||
SELECT e.store_code,
|
||
e.store_name,
|
||
e.bill_count,
|
||
e.active_days,
|
||
e.received,
|
||
e.avg_daily_received,
|
||
e.avg_bill_value,
|
||
e.avg_guest_value,
|
||
e.discount_rate_pct,
|
||
e.theoretical_margin_pct,
|
||
e.member_bill_share_pct,
|
||
e.anomaly_rate_pct,
|
||
e.risk_level,
|
||
e.primary_issue,
|
||
s.revenue_p25,
|
||
s.revenue_p50,
|
||
s.revenue_p75,
|
||
s.margin_p50,
|
||
s.margin_p75,
|
||
s.discount_p75
|
||
FROM eligible e
|
||
CROSS JOIN stats s
|
||
)
|
||
SELECT base.store_code,
|
||
base.store_name,
|
||
base.bill_count,
|
||
base.active_days,
|
||
base.received,
|
||
base.avg_daily_received,
|
||
base.avg_bill_value,
|
||
base.avg_guest_value,
|
||
base.discount_rate_pct,
|
||
base.theoretical_margin_pct,
|
||
base.member_bill_share_pct,
|
||
base.anomaly_rate_pct,
|
||
base.risk_level,
|
||
base.primary_issue,
|
||
base.revenue_p25,
|
||
base.revenue_p50,
|
||
base.revenue_p75,
|
||
base.margin_p50,
|
||
base.margin_p75,
|
||
base.discount_p75,
|
||
CASE
|
||
WHEN base.avg_daily_received::double precision >= base.revenue_p75 AND base.theoretical_margin_pct::double precision >= base.margin_p50 THEN '明星门店'::text
|
||
WHEN base.avg_daily_received::double precision >= base.revenue_p75 AND base.theoretical_margin_pct::double precision < base.margin_p50 THEN '规模承压'::text
|
||
WHEN base.avg_daily_received::double precision < base.revenue_p75 AND base.theoretical_margin_pct::double precision >= base.margin_p75 THEN '高效潜力'::text
|
||
WHEN base.avg_daily_received::double precision <= base.revenue_p25 OR base.theoretical_margin_pct < 68::numeric OR base.discount_rate_pct::double precision > base.discount_p75 THEN '重点改善'::text
|
||
ELSE '稳健经营'::text
|
||
END AS management_quadrant,
|
||
round(GREATEST(base.received / NULLIF(1::numeric - base.discount_rate_pct / 100::numeric, 0::numeric) * (base.discount_rate_pct - 20.23) / 100::numeric, 0::numeric), 2) AS discount_saving_to_company_avg,
|
||
round(GREATEST(base.received * 0.7092 - base.received * base.theoretical_margin_pct / 100::numeric, 0::numeric), 2) AS profit_uplift_to_company_margin
|
||
FROM base
|
||
WITH DATA;
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_benchmark_store ON analytics.v_store_benchmark (store_code);
|
||
|
||
-- ============================================================
|
||
-- Step 3: 组合视图 → 物化视图
|
||
-- ============================================================
|
||
|
||
-- 3a. v_store_action_list (joins benchmark + category_mix + platform_economics)
|
||
CREATE MATERIALIZED VIEW analytics.v_store_action_list AS
|
||
SELECT b.store_code,
|
||
b.store_name,
|
||
b.management_quadrant,
|
||
b.risk_level,
|
||
b.primary_issue,
|
||
b.received,
|
||
b.avg_daily_received,
|
||
b.avg_bill_value,
|
||
b.discount_rate_pct,
|
||
b.theoretical_margin_pct,
|
||
b.anomaly_rate_pct,
|
||
b.member_bill_share_pct,
|
||
b.discount_saving_to_company_avg,
|
||
b.profit_uplift_to_company_margin,
|
||
c.top_category,
|
||
c.top_category_share_pct,
|
||
c.delivery_package_share_pct,
|
||
p.meituan_cost_rate_pct,
|
||
p.taobao_cost_rate_pct,
|
||
p.jd_cost_rate_pct
|
||
FROM analytics.v_store_benchmark b
|
||
LEFT JOIN analytics.v_store_category_mix c USING (store_code, store_name)
|
||
LEFT JOIN analytics.v_store_platform_economics p USING (store_code, store_name)
|
||
WITH DATA;
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_action_list_store ON analytics.v_store_action_list (store_code);
|
||
|
||
-- 3b. v_store_execution_priority (joins action_list + meal + member + zero_bill)
|
||
CREATE MATERIALIZED VIEW analytics.v_store_execution_priority AS
|
||
WITH meal AS (
|
||
SELECT v_store_meal_opportunity.store_code,
|
||
sum(v_store_meal_opportunity.avg_bill_uplift_scenario) AS meal_uplift_scenario
|
||
FROM analytics.v_store_meal_opportunity
|
||
WHERE v_store_meal_opportunity.bill_count >= 500
|
||
GROUP BY v_store_meal_opportunity.store_code
|
||
), zero_bill AS (
|
||
SELECT v_zero_received_detail.store_code,
|
||
count(*) AS zero_received_bills,
|
||
count(*) FILTER (WHERE v_zero_received_detail.zero_received_type = '优惠不足但无实收'::text) AS unexplained_zero_bills
|
||
FROM analytics.v_zero_received_detail
|
||
GROUP BY v_zero_received_detail.store_code
|
||
)
|
||
SELECT a.store_code,
|
||
a.store_name,
|
||
a.management_quadrant,
|
||
a.risk_level,
|
||
a.primary_issue,
|
||
a.received,
|
||
a.avg_daily_received,
|
||
a.avg_bill_value,
|
||
a.discount_rate_pct,
|
||
a.theoretical_margin_pct,
|
||
a.anomaly_rate_pct,
|
||
a.member_bill_share_pct,
|
||
a.discount_saving_to_company_avg,
|
||
a.profit_uplift_to_company_margin,
|
||
a.top_category,
|
||
a.top_category_share_pct,
|
||
a.delivery_package_share_pct,
|
||
a.meituan_cost_rate_pct,
|
||
a.taobao_cost_rate_pct,
|
||
a.jd_cost_rate_pct,
|
||
round(COALESCE(m.meal_uplift_scenario, 0::numeric), 2) AS meal_uplift_scenario,
|
||
mo.member_share_pct,
|
||
mo.conversion_bill_scenario,
|
||
mo.revenue_uplift_scenario AS member_revenue_uplift_scenario,
|
||
COALESCE(z.zero_received_bills, 0::bigint) AS zero_received_bills,
|
||
COALESCE(z.unexplained_zero_bills, 0::bigint) AS unexplained_zero_bills
|
||
FROM analytics.v_store_action_list a
|
||
LEFT JOIN meal m USING (store_code)
|
||
LEFT JOIN analytics.v_store_member_opportunity mo USING (store_code, store_name)
|
||
LEFT JOIN zero_bill z USING (store_code)
|
||
WITH DATA;
|
||
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_execution_priority_store ON analytics.v_store_execution_priority (store_code);
|
||
|
||
-- ============================================================
|
||
-- Step 4: 重建依赖普通视图(基于物化视图,查询会走索引)
|
||
-- ============================================================
|
||
|
||
-- 4a. v_store_benchmark_composite
|
||
CREATE OR REPLACE VIEW analytics.v_store_benchmark_composite AS
|
||
WITH eligible AS (
|
||
SELECT b.store_code,
|
||
b.store_name,
|
||
b.received,
|
||
b.avg_daily_received,
|
||
b.avg_bill_value,
|
||
b.discount_rate_pct,
|
||
b.theoretical_margin_pct,
|
||
b.anomaly_rate_pct,
|
||
b.member_bill_share_pct,
|
||
r.identified_members,
|
||
r.repeat_rate_pct,
|
||
r.avg_orders,
|
||
r.repeat_revenue_share_pct,
|
||
p.meituan_cost_rate_pct,
|
||
p.taobao_cost_rate_pct,
|
||
p.jd_cost_rate_pct
|
||
FROM analytics.v_store_benchmark b
|
||
JOIN analytics.v_store_repeat_summary_monthly r ON r.store_code = b.store_code AND r.month_start = '2026-04-01'::date
|
||
LEFT JOIN analytics.v_store_platform_economics p ON p.store_code = b.store_code
|
||
WHERE b.theoretical_margin_pct >= 60::numeric AND b.theoretical_margin_pct <= 80::numeric AND r.identified_members >= 500
|
||
), scored AS (
|
||
SELECT eligible.store_code,
|
||
eligible.store_name,
|
||
eligible.received,
|
||
eligible.avg_daily_received,
|
||
eligible.avg_bill_value,
|
||
eligible.discount_rate_pct,
|
||
eligible.theoretical_margin_pct,
|
||
eligible.anomaly_rate_pct,
|
||
eligible.member_bill_share_pct,
|
||
eligible.identified_members,
|
||
eligible.repeat_rate_pct,
|
||
eligible.avg_orders,
|
||
eligible.repeat_revenue_share_pct,
|
||
eligible.meituan_cost_rate_pct,
|
||
eligible.taobao_cost_rate_pct,
|
||
eligible.jd_cost_rate_pct,
|
||
percent_rank() OVER (ORDER BY eligible.avg_daily_received) AS revenue_score,
|
||
percent_rank() OVER (ORDER BY eligible.theoretical_margin_pct) AS margin_score,
|
||
1::double precision - percent_rank() OVER (ORDER BY eligible.discount_rate_pct) AS discount_score,
|
||
1::double precision - percent_rank() OVER (ORDER BY eligible.anomaly_rate_pct) AS anomaly_score,
|
||
percent_rank() OVER (ORDER BY eligible.repeat_rate_pct) AS repeat_score
|
||
FROM eligible
|
||
)
|
||
SELECT scored.store_code,
|
||
scored.store_name,
|
||
scored.received,
|
||
scored.avg_daily_received,
|
||
scored.avg_bill_value,
|
||
scored.discount_rate_pct,
|
||
scored.theoretical_margin_pct,
|
||
scored.anomaly_rate_pct,
|
||
scored.member_bill_share_pct,
|
||
scored.identified_members,
|
||
scored.repeat_rate_pct,
|
||
scored.avg_orders,
|
||
scored.repeat_revenue_share_pct,
|
||
scored.meituan_cost_rate_pct,
|
||
scored.taobao_cost_rate_pct,
|
||
scored.jd_cost_rate_pct,
|
||
scored.revenue_score,
|
||
scored.margin_score,
|
||
scored.discount_score,
|
||
scored.anomaly_score,
|
||
scored.repeat_score,
|
||
round(((scored.revenue_score * 0.25::double precision + scored.margin_score * 0.25::double precision + scored.discount_score * 0.20::double precision + scored.anomaly_score * 0.15::double precision + scored.repeat_score * 0.15::double precision) * 100::double precision)::numeric, 2) AS benchmark_score
|
||
FROM scored;
|
||
|
||
-- 4b. v_store_deep_diagnosis_april
|
||
CREATE OR REPLACE VIEW analytics.v_store_deep_diagnosis_april AS
|
||
WITH store_base AS (
|
||
SELECT s.store_code,
|
||
s.store_name,
|
||
s.bill_count,
|
||
s.active_days,
|
||
s.received,
|
||
s.avg_daily_received,
|
||
s.avg_bill_value,
|
||
s.discount_rate_pct,
|
||
s.theoretical_margin_pct,
|
||
s.member_bill_share_pct,
|
||
d.items_per_bill,
|
||
d.skus_per_bill,
|
||
d.delivery_bill_share_pct,
|
||
d.noodle_snack_attach_pct,
|
||
d.noodle_drink_attach_pct,
|
||
d.noodle_cold_attach_pct,
|
||
d.combo_bill_share_pct,
|
||
c.theoretical_cost,
|
||
c.actual_food_cost,
|
||
c.food_cost_variance,
|
||
c.theoretical_cost_rate_pct,
|
||
c.actual_food_cost_rate_pct,
|
||
c.variance_to_theoretical_pct,
|
||
c.comparison_status,
|
||
c.variance_level,
|
||
b.identified_members,
|
||
b.repeat_rate_pct,
|
||
b.repeat_revenue_share_pct,
|
||
b.benchmark_score,
|
||
p.meituan_received,
|
||
p.taobao_received,
|
||
p.jd_received,
|
||
round((COALESCE(p.meituan_discount, 0::numeric) + COALESCE(p.taobao_discount, 0::numeric) + COALESCE(p.jd_discount, 0::numeric) + COALESCE(p.meituan_commission, 0::numeric) + COALESCE(p.taobao_commission, 0::numeric) + COALESCE(p.jd_commission, 0::numeric)) / NULLIF(COALESCE(p.meituan_received, 0::numeric) + COALESCE(p.taobao_received, 0::numeric) + COALESCE(p.jd_received, 0::numeric) + COALESCE(p.meituan_discount, 0::numeric) + COALESCE(p.taobao_discount, 0::numeric) + COALESCE(p.jd_discount, 0::numeric) + COALESCE(p.meituan_commission, 0::numeric) + COALESCE(p.taobao_commission, 0::numeric) + COALESCE(p.jd_commission, 0::numeric), 0::numeric) * 100::numeric, 2) AS combined_platform_cost_rate_pct,
|
||
CASE
|
||
WHEN s.store_name ~ '机场|火锅|商城|快手|哈马尔罕'::text THEN '特殊业态'::text
|
||
ELSE '标准门店'::text
|
||
END AS business_type
|
||
FROM analytics.v_store_scorecard s
|
||
LEFT JOIN analytics.dish_store_summary_april d USING (store_code)
|
||
LEFT JOIN analytics.v_store_theoretical_actual_cost_april c USING (store_code)
|
||
LEFT JOIN analytics.v_store_benchmark_composite b USING (store_code)
|
||
LEFT JOIN analytics.v_store_platform_economics p USING (store_code)
|
||
), tiered AS (
|
||
SELECT store_base.store_code,
|
||
store_base.store_name,
|
||
store_base.bill_count,
|
||
store_base.active_days,
|
||
store_base.received,
|
||
store_base.avg_daily_received,
|
||
store_base.avg_bill_value,
|
||
store_base.discount_rate_pct,
|
||
store_base.theoretical_margin_pct,
|
||
store_base.member_bill_share_pct,
|
||
store_base.items_per_bill,
|
||
store_base.skus_per_bill,
|
||
store_base.delivery_bill_share_pct,
|
||
store_base.noodle_snack_attach_pct,
|
||
store_base.noodle_drink_attach_pct,
|
||
store_base.noodle_cold_attach_pct,
|
||
store_base.combo_bill_share_pct,
|
||
store_base.theoretical_cost,
|
||
store_base.actual_food_cost,
|
||
store_base.food_cost_variance,
|
||
store_base.theoretical_cost_rate_pct,
|
||
store_base.actual_food_cost_rate_pct,
|
||
store_base.variance_to_theoretical_pct,
|
||
store_base.comparison_status,
|
||
store_base.variance_level,
|
||
store_base.identified_members,
|
||
store_base.repeat_rate_pct,
|
||
store_base.repeat_revenue_share_pct,
|
||
store_base.benchmark_score,
|
||
store_base.meituan_received,
|
||
store_base.taobao_received,
|
||
store_base.jd_received,
|
||
store_base.combined_platform_cost_rate_pct,
|
||
store_base.business_type,
|
||
CASE
|
||
WHEN store_base.business_type = '特殊业态'::text THEN '特殊业态'::text
|
||
WHEN percent_rank() OVER (PARTITION BY store_base.business_type ORDER BY store_base.received) >= 0.67::double precision THEN '高规模'::text
|
||
WHEN percent_rank() OVER (PARTITION BY store_base.business_type ORDER BY store_base.received) >= 0.33::double precision THEN '中规模'::text
|
||
ELSE '低规模'::text
|
||
END AS scale_tier
|
||
FROM store_base
|
||
)
|
||
SELECT tiered.store_code,
|
||
tiered.store_name,
|
||
tiered.bill_count,
|
||
tiered.active_days,
|
||
tiered.received,
|
||
tiered.avg_daily_received,
|
||
tiered.avg_bill_value,
|
||
tiered.discount_rate_pct,
|
||
tiered.theoretical_margin_pct,
|
||
tiered.member_bill_share_pct,
|
||
tiered.items_per_bill,
|
||
tiered.skus_per_bill,
|
||
tiered.delivery_bill_share_pct,
|
||
tiered.noodle_snack_attach_pct,
|
||
tiered.noodle_drink_attach_pct,
|
||
tiered.noodle_cold_attach_pct,
|
||
tiered.combo_bill_share_pct,
|
||
tiered.theoretical_cost,
|
||
tiered.actual_food_cost,
|
||
tiered.food_cost_variance,
|
||
tiered.theoretical_cost_rate_pct,
|
||
tiered.actual_food_cost_rate_pct,
|
||
tiered.variance_to_theoretical_pct,
|
||
tiered.comparison_status,
|
||
tiered.variance_level,
|
||
tiered.identified_members,
|
||
tiered.repeat_rate_pct,
|
||
tiered.repeat_revenue_share_pct,
|
||
tiered.benchmark_score,
|
||
tiered.meituan_received,
|
||
tiered.taobao_received,
|
||
tiered.jd_received,
|
||
tiered.combined_platform_cost_rate_pct,
|
||
tiered.business_type,
|
||
tiered.scale_tier,
|
||
CASE
|
||
WHEN tiered.comparison_status = '可比'::text AND tiered.variance_to_theoretical_pct >= 20::numeric THEN 1
|
||
ELSE 0
|
||
END +
|
||
CASE
|
||
WHEN tiered.discount_rate_pct >= 23.02 THEN 1
|
||
ELSE 0
|
||
END +
|
||
CASE
|
||
WHEN tiered.theoretical_margin_pct < 70::numeric THEN 1
|
||
ELSE 0
|
||
END +
|
||
CASE
|
||
WHEN tiered.identified_members >= 500 AND tiered.repeat_rate_pct < 30::numeric THEN 1
|
||
ELSE 0
|
||
END +
|
||
CASE
|
||
WHEN tiered.combined_platform_cost_rate_pct >= 40::numeric THEN 1
|
||
ELSE 0
|
||
END +
|
||
CASE
|
||
WHEN tiered.noodle_drink_attach_pct < 12::numeric THEN 1
|
||
ELSE 0
|
||
END AS problem_count,
|
||
concat_ws('+'::text,
|
||
CASE
|
||
WHEN tiered.comparison_status = '理论成本口径异常'::text THEN '成本口径异常'::text
|
||
ELSE NULL::text
|
||
END,
|
||
CASE
|
||
WHEN tiered.comparison_status = '可比'::text AND tiered.variance_to_theoretical_pct >= 20::numeric THEN '实际成本严重超耗'::text
|
||
ELSE NULL::text
|
||
END,
|
||
CASE
|
||
WHEN tiered.discount_rate_pct >= 23.02 THEN '优惠偏高'::text
|
||
ELSE NULL::text
|
||
END,
|
||
CASE
|
||
WHEN tiered.theoretical_margin_pct < 70::numeric THEN '理论毛利偏低'::text
|
||
ELSE NULL::text
|
||
END,
|
||
CASE
|
||
WHEN tiered.identified_members >= 500 AND tiered.repeat_rate_pct < 30::numeric THEN '会员复购偏低'::text
|
||
ELSE NULL::text
|
||
END,
|
||
CASE
|
||
WHEN tiered.combined_platform_cost_rate_pct >= 40::numeric THEN '平台成本偏高'::text
|
||
ELSE NULL::text
|
||
END,
|
||
CASE
|
||
WHEN tiered.noodle_drink_attach_pct < 12::numeric THEN '饮品搭售偏低'::text
|
||
ELSE NULL::text
|
||
END) AS problem_combination
|
||
FROM tiered;
|
||
|
||
-- ============================================================
|
||
-- Step 5: 验证
|
||
-- ============================================================
|
||
SELECT 'Materialized views created successfully' AS status;
|