Files
SBrainCO/db/migration_phase9_view_parameterization.sql
T

178 lines
8.9 KiB
SQL

-- ============================================================
-- Phase 9: 全量视图参数化
-- 替换 v_store_platform_economics, v_store_category_mix, v_store_member_opportunity
-- 为按月参数化函数
-- ============================================================
-- ============================================================
-- 1. fn_store_platform_economics(p_month)
-- 替换 v_store_platform_economics(无月份过滤的全量聚合)
-- 数据源: bill_records, 按月份过滤 c175
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_platform_economics(p_month date)
RETURNS TABLE (
store_code text,
store_name text,
meituan_received numeric,
meituan_discount numeric,
meituan_commission numeric,
taobao_received numeric,
taobao_discount numeric,
taobao_commission numeric,
jd_received numeric,
jd_discount numeric,
jd_commission numeric,
meituan_cost_rate_pct numeric,
taobao_cost_rate_pct numeric,
jd_cost_rate_pct numeric
)
LANGUAGE sql
STABLE
AS $function$
SELECT
NULLIF(bill_records.c002, '') AS store_code,
NULLIF(bill_records.c003, '') AS store_name,
sum(COALESCE(NULLIF(bill_records.c151, '')::numeric, 0)) AS meituan_received,
sum(COALESCE(NULLIF(bill_records.c101, '')::numeric, 0)) AS meituan_discount,
sum(COALESCE(NULLIF(bill_records.c097, '')::numeric, 0)) AS meituan_commission,
sum(COALESCE(NULLIF(bill_records.c152, '')::numeric, 0)) AS taobao_received,
sum(COALESCE(NULLIF(bill_records.c102, '')::numeric, 0)) AS taobao_discount,
sum(COALESCE(NULLIF(bill_records.c098, '')::numeric, 0)) AS taobao_commission,
sum(COALESCE(NULLIF(bill_records.c150, '')::numeric, 0)) AS jd_received,
sum(COALESCE(NULLIF(bill_records.c099, '')::numeric, 0)) AS jd_discount,
sum(COALESCE(NULLIF(bill_records.c100, '')::numeric, 0)) AS jd_commission,
round((sum(COALESCE(NULLIF(bill_records.c101, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c097, '')::numeric, 0)))
/ NULLIF(sum(COALESCE(NULLIF(bill_records.c151, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c101, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c097, '')::numeric, 0)), 0) * 100, 2) AS meituan_cost_rate_pct,
round((sum(COALESCE(NULLIF(bill_records.c102, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c098, '')::numeric, 0)))
/ NULLIF(sum(COALESCE(NULLIF(bill_records.c152, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c102, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c098, '')::numeric, 0)), 0) * 100, 2) AS taobao_cost_rate_pct,
round((sum(COALESCE(NULLIF(bill_records.c099, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c100, '')::numeric, 0)))
/ NULLIF(sum(COALESCE(NULLIF(bill_records.c150, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c099, '')::numeric, 0)) + sum(COALESCE(NULLIF(bill_records.c100, '')::numeric, 0)), 0) * 100, 2) AS jd_cost_rate_pct
FROM bill_records
WHERE NULLIF(bill_records.c005, '') IS NOT NULL
AND bill_records.c175 IS NOT NULL AND bill_records.c175 != ''
AND bill_records.c175::timestamp >= analytics.fn_month_start_ts(p_month)
AND bill_records.c175::timestamp < analytics.fn_next_month_start_ts(p_month)
GROUP BY NULLIF(bill_records.c002, ''), NULLIF(bill_records.c003, '');
$function$;
-- ============================================================
-- 2. fn_store_category_mix(p_month)
-- 替换 v_store_category_mix(无月份过滤的全量聚合)
-- 数据源: bill_records, 按月份过滤 c175
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_category_mix(p_month date)
RETURNS TABLE (
store_code text,
store_name text,
consumption numeric,
lanzhou_noodle numeric,
western_staple numeric,
delivery_package numeric,
night_bbq numeric,
cold_dishes numeric,
silk_road_food numeric,
noodle_share_pct numeric,
delivery_package_share_pct numeric,
top_category_share_pct numeric,
top_category text
)
LANGUAGE sql
STABLE
AS $function$
SELECT
NULLIF(bill_records.c002, '') AS store_code,
NULLIF(bill_records.c003, '') AS store_name,
sum(COALESCE(NULLIF(bill_records.c009, '')::numeric, 0)) AS consumption,
sum(COALESCE(NULLIF(bill_records.c010, '')::numeric, 0)) AS lanzhou_noodle,
sum(COALESCE(NULLIF(bill_records.c016, '')::numeric, 0)) AS western_staple,
sum(COALESCE(NULLIF(bill_records.c027, '')::numeric, 0)) AS delivery_package,
sum(COALESCE(NULLIF(bill_records.c013, '')::numeric, 0)) AS night_bbq,
sum(COALESCE(NULLIF(bill_records.c015, '')::numeric, 0)) AS cold_dishes,
sum(COALESCE(NULLIF(bill_records.c019, '')::numeric, 0)) AS silk_road_food,
round(sum(COALESCE(NULLIF(bill_records.c010, '')::numeric, 0))
/ NULLIF(sum(COALESCE(NULLIF(bill_records.c009, '')::numeric, 0)), 0) * 100, 2) AS noodle_share_pct,
round(sum(COALESCE(NULLIF(bill_records.c027, '')::numeric, 0))
/ NULLIF(sum(COALESCE(NULLIF(bill_records.c009, '')::numeric, 0)), 0) * 100, 2) AS delivery_package_share_pct,
round(GREATEST(
sum(COALESCE(NULLIF(bill_records.c010, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c016, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c027, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c013, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c015, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c019, '')::numeric, 0))
) / NULLIF(sum(COALESCE(NULLIF(bill_records.c009, '')::numeric, 0)), 0) * 100, 2) AS top_category_share_pct,
CASE GREATEST(
sum(COALESCE(NULLIF(bill_records.c010, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c016, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c027, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c013, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c015, '')::numeric, 0)),
sum(COALESCE(NULLIF(bill_records.c019, '')::numeric, 0))
)
WHEN sum(COALESCE(NULLIF(bill_records.c010, '')::numeric, 0)) THEN '兰州牛肉面'
WHEN sum(COALESCE(NULLIF(bill_records.c016, '')::numeric, 0)) THEN '西部主食'
WHEN sum(COALESCE(NULLIF(bill_records.c027, '')::numeric, 0)) THEN '外卖套餐'
WHEN sum(COALESCE(NULLIF(bill_records.c013, '')::numeric, 0)) THEN '夜市烧烤'
WHEN sum(COALESCE(NULLIF(bill_records.c015, '')::numeric, 0)) THEN '爽口凉菜'
ELSE '丝路美食'
END AS top_category
FROM bill_records
WHERE NULLIF(bill_records.c005, '') IS NOT NULL
AND bill_records.c175 IS NOT NULL AND bill_records.c175 != ''
AND bill_records.c175::timestamp >= analytics.fn_month_start_ts(p_month)
AND bill_records.c175::timestamp < analytics.fn_next_month_start_ts(p_month)
GROUP BY NULLIF(bill_records.c002, ''), NULLIF(bill_records.c003, '');
$function$;
-- ============================================================
-- 3. fn_store_member_opportunity(p_month)
-- 替换 v_store_member_opportunity(无月份过滤的全量聚合)
-- 数据源: analytics.bill_fact, 按月份过滤 closed_at
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_member_opportunity(p_month date)
RETURNS TABLE (
store_code text,
store_name text,
bill_count bigint,
received numeric,
member_share_pct numeric,
company_member_share_pct numeric,
conversion_bill_scenario numeric,
revenue_uplift_scenario numeric
)
LANGUAGE sql
STABLE
AS $function$
WITH company AS (
SELECT
count(*) FILTER (WHERE bill_fact.member_id IS NOT NULL)::numeric / count(*)::numeric AS member_share,
avg(bill_fact.received_total) FILTER (WHERE bill_fact.member_id IS NOT NULL) AS member_avg_bill,
avg(bill_fact.received_total) FILTER (WHERE bill_fact.member_id IS NULL) AS nonmember_avg_bill
FROM analytics.bill_fact
WHERE bill_fact.closed_at >= analytics.fn_month_start_ts(p_month)
AND bill_fact.closed_at < analytics.fn_next_month_start_ts(p_month)
), stores AS (
SELECT
bill_fact.store_code,
bill_fact.store_name,
count(*) AS bill_count,
count(*) FILTER (WHERE bill_fact.member_id IS NOT NULL)::numeric / count(*)::numeric AS member_share,
sum(bill_fact.received_total) AS received
FROM analytics.bill_fact
WHERE bill_fact.closed_at >= analytics.fn_month_start_ts(p_month)
AND bill_fact.closed_at < analytics.fn_next_month_start_ts(p_month)
GROUP BY bill_fact.store_code, bill_fact.store_name
)
SELECT
s.store_code,
s.store_name,
s.bill_count,
round(s.received, 2) AS received,
round(s.member_share * 100, 2) AS member_share_pct,
round(c.member_share * 100, 2) AS company_member_share_pct,
round(GREATEST(c.member_share - s.member_share, 0) * s.bill_count::numeric, 0) AS conversion_bill_scenario,
round(GREATEST(c.member_share - s.member_share, 0) * s.bill_count::numeric * (c.member_avg_bill - c.nonmember_avg_bill), 2) AS revenue_uplift_scenario
FROM stores s
CROSS JOIN company c;
$function$;