Files
SBrainCO/server/sql/10_materialize_slow_views.sql
T
freedakgmail 6e0c1bd7a4 feat: 新增门店选址分析功能
- 创建选址分析7个SQL视图+5张物化表(38s→33ms)
- 后端新增5个选址API端点
- 前端新增SiteSelectionPage页面(散点图+4Tab)
- 侧边栏新增门店选址导航入口
- 修复SQL列引用错误(d.→a.,去掉重复列)
- 创建v_store_action_priority_deep_april和v_store_area_efficiency_april基础视图
2026-07-27 08:33:42 +08:00

493 lines
25 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================
-- 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;