feat: 新增门店选址分析功能
- 创建选址分析7个SQL视图+5张物化表(38s→33ms) - 后端新增5个选址API端点 - 前端新增SiteSelectionPage页面(散点图+4Tab) - 侧边栏新增门店选址导航入口 - 修复SQL列引用错误(d.→a.,去掉重复列) - 创建v_store_action_priority_deep_april和v_store_area_efficiency_april基础视图
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
-- ============================================================
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user