178 lines
8.9 KiB
SQL
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$;
|