feat: 月度模式全面参数化 - 移除硬编码日期,前后端按月动态查询
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
-- ============================================================
|
||||
-- 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$;
|
||||
Reference in New Issue
Block a user