1056 lines
39 KiB
PL/PgSQL
1056 lines
39 KiB
PL/PgSQL
-- ============================================================
|
||
-- Phase 2 Migration: 参数化函数替换 _april 后缀视图
|
||
-- 核心利润链第1批:9个对象
|
||
--
|
||
-- 依赖链:
|
||
-- fn_inventory_cost_classified (base)
|
||
-- ├── fn_inventory_abnormal_items
|
||
-- ├── fn_inventory_finance_category
|
||
-- └── fn_store_theoretical_actual_cost (also uses bill_fact)
|
||
-- └── fn_store_deep_diagnosis (also uses dish_store_summary, benchmark_composite)
|
||
-- └── fn_store_action_priority_deep
|
||
-- └── fn_store_area_efficiency
|
||
--
|
||
-- fn_dish_sales (base, uses bill_fact + dish_sales_details)
|
||
-- └── fn_dish_basket
|
||
-- └── fn_dish_store_summary
|
||
--
|
||
-- fn_store_benchmark_composite (uses v_store_repeat_summary_monthly)
|
||
--
|
||
-- 向后兼容:保留原 _april 视图,新增函数可并行使用
|
||
-- ============================================================
|
||
|
||
-- 辅助函数:获取月份的起始时间戳(带时区,+08:00)
|
||
CREATE OR REPLACE FUNCTION analytics.fn_month_start_ts(p_month date)
|
||
RETURNS timestamptz AS $$
|
||
BEGIN
|
||
RETURN (p_month::text || ' 00:00:00+08')::timestamptz;
|
||
END;
|
||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||
|
||
-- 辅助函数:获取下月起始时间戳
|
||
CREATE OR REPLACE FUNCTION analytics.fn_next_month_start_ts(p_month date)
|
||
RETURNS timestamptz AS $$
|
||
BEGIN
|
||
RETURN ((p_month + INTERVAL '1 month')::date::text || ' 00:00:00+08')::timestamptz;
|
||
END;
|
||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||
|
||
-- ============================================================
|
||
-- 1. fn_inventory_cost_classified(p_month)
|
||
-- 替换 v_inventory_cost_classified_april
|
||
-- 依赖: v_inventory_cost_operating
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_inventory_cost_classified(p_month date)
|
||
RETURNS TABLE (
|
||
record_id bigint,
|
||
report_month date,
|
||
source_file text,
|
||
source_row integer,
|
||
brand text,
|
||
region text,
|
||
cost_unit_name text,
|
||
cost_unit_code text,
|
||
item_name text,
|
||
specification text,
|
||
major_category text,
|
||
minor_category text,
|
||
finance_category text,
|
||
item_code text,
|
||
unit text,
|
||
opening_quantity numeric,
|
||
opening_unit_cost numeric,
|
||
opening_amount numeric,
|
||
opening_tax numeric,
|
||
opening_tax_inclusive numeric,
|
||
purchase_quantity numeric,
|
||
purchase_unit_cost numeric,
|
||
purchase_amount numeric,
|
||
purchase_tax numeric,
|
||
purchase_tax_inclusive numeric,
|
||
ending_quantity numeric,
|
||
ending_unit_cost numeric,
|
||
ending_amount numeric,
|
||
ending_tax numeric,
|
||
ending_tax_inclusive numeric,
|
||
consumption_quantity numeric,
|
||
consumption_unit_cost numeric,
|
||
consumption_amount numeric,
|
||
consumption_tax numeric,
|
||
consumption_tax_inclusive numeric,
|
||
conversion_precision_amount numeric,
|
||
conversion_precision_tax_inclusive numeric,
|
||
return_loss_amount numeric,
|
||
return_loss_tax_inclusive numeric,
|
||
imported_at timestamptz,
|
||
calculated_consumption_amount numeric,
|
||
is_negative_consumption boolean,
|
||
sales_store_code text,
|
||
sales_store_name text,
|
||
unit_type text,
|
||
mapping_method text,
|
||
management_cost_scope text
|
||
) AS $$
|
||
SELECT
|
||
i.record_id,
|
||
i.report_month,
|
||
i.source_file,
|
||
i.source_row,
|
||
i.brand,
|
||
i.region,
|
||
i.cost_unit_name,
|
||
i.cost_unit_code,
|
||
i.item_name,
|
||
i.specification,
|
||
i.major_category,
|
||
i.minor_category,
|
||
i.finance_category,
|
||
i.item_code,
|
||
i.unit,
|
||
i.opening_quantity,
|
||
i.opening_unit_cost,
|
||
i.opening_amount,
|
||
i.opening_tax,
|
||
i.opening_tax_inclusive,
|
||
i.purchase_quantity,
|
||
i.purchase_unit_cost,
|
||
i.purchase_amount,
|
||
i.purchase_tax,
|
||
i.purchase_tax_inclusive,
|
||
i.ending_quantity,
|
||
i.ending_unit_cost,
|
||
i.ending_amount,
|
||
i.ending_tax,
|
||
i.ending_tax_inclusive,
|
||
i.consumption_quantity,
|
||
i.consumption_unit_cost,
|
||
i.consumption_amount,
|
||
i.consumption_tax,
|
||
i.consumption_tax_inclusive,
|
||
i.conversion_precision_amount,
|
||
i.conversion_precision_tax_inclusive,
|
||
i.return_loss_amount,
|
||
i.return_loss_tax_inclusive,
|
||
i.imported_at,
|
||
i.calculated_consumption_amount,
|
||
i.is_negative_consumption,
|
||
i.sales_store_code,
|
||
i.sales_store_name,
|
||
i.unit_type,
|
||
i.mapping_method,
|
||
CASE
|
||
WHEN i.major_category = ANY (ARRAY['原料类', '半成品类', '烟酒饮料类', '(新)原料类']) THEN '可比食材成本'
|
||
WHEN i.major_category = '包材类' OR i.finance_category = '食品包装' THEN '食品包装'
|
||
WHEN i.major_category = '易耗品类' THEN '易耗及营运物料'
|
||
WHEN i.major_category = ANY (ARRAY['维修配件类', '电子信息类']) THEN '维修及设备'
|
||
WHEN i.major_category = '新零售' THEN '新零售货品'
|
||
ELSE '其他成本'
|
||
END AS management_cost_scope
|
||
FROM analytics.v_inventory_cost_operating i
|
||
WHERE i.report_month = p_month;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 2. fn_store_theoretical_actual_cost(p_month)
|
||
-- 替换 v_store_theoretical_actual_cost_april
|
||
-- 依赖: bill_fact, fn_inventory_cost_classified
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_store_theoretical_actual_cost(p_month date)
|
||
RETURNS TABLE (
|
||
store_code text,
|
||
store_name text,
|
||
bill_count bigint,
|
||
sales_consumption numeric,
|
||
sales_received numeric,
|
||
theoretical_cost numeric,
|
||
theoretical_profit numeric,
|
||
actual_food_cost numeric,
|
||
actual_total_cost numeric,
|
||
packaging_cost numeric,
|
||
operating_supply_cost numeric,
|
||
repair_equipment_cost numeric,
|
||
new_retail_cost numeric,
|
||
ending_inventory_amount numeric,
|
||
negative_item_lines bigint,
|
||
negative_consumption_amount numeric,
|
||
food_cost_variance numeric,
|
||
theoretical_cost_rate_pct numeric,
|
||
actual_food_cost_rate_pct numeric,
|
||
variance_to_theoretical_pct numeric,
|
||
cost_rate_gap_pct numeric,
|
||
comparison_status text,
|
||
variance_level text
|
||
) AS $$
|
||
WITH theoretical AS (
|
||
SELECT
|
||
b.store_code,
|
||
min(b.store_name) AS store_name,
|
||
count(*) AS bill_count,
|
||
sum(b.consumption) AS sales_consumption,
|
||
sum(b.received_total) AS sales_received,
|
||
sum(b.theoretical_cost) AS theoretical_cost,
|
||
sum(b.theoretical_profit) AS theoretical_profit
|
||
FROM analytics.bill_fact b
|
||
WHERE b.closed_at >= analytics.fn_month_start_ts(p_month)
|
||
AND b.closed_at < analytics.fn_next_month_start_ts(p_month)
|
||
GROUP BY b.store_code
|
||
), actual AS (
|
||
SELECT
|
||
ic.sales_store_code AS store_code,
|
||
sum(ic.consumption_amount) AS actual_total_cost,
|
||
sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '可比食材成本') AS actual_food_cost,
|
||
sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '食品包装') AS packaging_cost,
|
||
sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '易耗及营运物料') AS operating_supply_cost,
|
||
sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '维修及设备') AS repair_equipment_cost,
|
||
sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '新零售货品') AS new_retail_cost,
|
||
count(*) FILTER (WHERE ic.is_negative_consumption) AS negative_item_lines,
|
||
sum(ic.consumption_amount) FILTER (WHERE ic.is_negative_consumption) AS negative_consumption_amount,
|
||
sum(ic.ending_amount) AS ending_inventory_amount
|
||
FROM analytics.fn_inventory_cost_classified(p_month) ic
|
||
GROUP BY ic.sales_store_code
|
||
)
|
||
SELECT
|
||
t.store_code,
|
||
t.store_name,
|
||
t.bill_count,
|
||
t.sales_consumption,
|
||
t.sales_received,
|
||
t.theoretical_cost,
|
||
t.theoretical_profit,
|
||
COALESCE(a.actual_food_cost, 0) AS actual_food_cost,
|
||
COALESCE(a.actual_total_cost, 0) AS actual_total_cost,
|
||
COALESCE(a.packaging_cost, 0) AS packaging_cost,
|
||
COALESCE(a.operating_supply_cost, 0) AS operating_supply_cost,
|
||
COALESCE(a.repair_equipment_cost, 0) AS repair_equipment_cost,
|
||
COALESCE(a.new_retail_cost, 0) AS new_retail_cost,
|
||
COALESCE(a.ending_inventory_amount, 0) AS ending_inventory_amount,
|
||
COALESCE(a.negative_item_lines, 0) AS negative_item_lines,
|
||
COALESCE(a.negative_consumption_amount, 0) AS negative_consumption_amount,
|
||
COALESCE(a.actual_food_cost, 0) - t.theoretical_cost AS food_cost_variance,
|
||
round(t.theoretical_cost / NULLIF(t.sales_consumption, 0) * 100, 2) AS theoretical_cost_rate_pct,
|
||
round(COALESCE(a.actual_food_cost, 0) / NULLIF(t.sales_consumption, 0) * 100, 2) AS actual_food_cost_rate_pct,
|
||
round((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / NULLIF(t.theoretical_cost, 0) * 100, 2) AS variance_to_theoretical_pct,
|
||
round((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / NULLIF(t.sales_consumption, 0) * 100, 2) AS cost_rate_gap_pct,
|
||
CASE
|
||
WHEN t.sales_consumption > 0 AND (t.theoretical_cost / t.sales_consumption) >= 0.10 THEN '可比'
|
||
ELSE '理论成本口径异常'
|
||
END AS comparison_status,
|
||
CASE
|
||
WHEN t.sales_consumption = 0 OR (t.theoretical_cost / t.sales_consumption) < 0.10 THEN '灰色-口径异常'
|
||
WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) >= 0.20 THEN '红色-严重超耗'
|
||
WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) >= 0.10 THEN '橙色-明显超耗'
|
||
WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) <= -0.20 THEN '蓝色-倒挤偏低'
|
||
WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) <= -0.10 THEN '黄色-低于理论'
|
||
ELSE '绿色-基本正常'
|
||
END AS variance_level
|
||
FROM theoretical t
|
||
JOIN actual a USING (store_code);
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 3. fn_inventory_abnormal_items(p_month)
|
||
-- 替换 v_inventory_abnormal_items_april
|
||
-- 依赖: fn_inventory_cost_classified
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_inventory_abnormal_items(p_month date)
|
||
RETURNS TABLE (
|
||
record_id bigint,
|
||
report_month date,
|
||
source_file text,
|
||
source_row integer,
|
||
brand text,
|
||
region text,
|
||
cost_unit_name text,
|
||
cost_unit_code text,
|
||
item_name text,
|
||
specification text,
|
||
major_category text,
|
||
minor_category text,
|
||
finance_category text,
|
||
item_code text,
|
||
unit text,
|
||
opening_quantity numeric,
|
||
opening_unit_cost numeric,
|
||
opening_amount numeric,
|
||
opening_tax numeric,
|
||
opening_tax_inclusive numeric,
|
||
purchase_quantity numeric,
|
||
purchase_unit_cost numeric,
|
||
purchase_amount numeric,
|
||
purchase_tax numeric,
|
||
purchase_tax_inclusive numeric,
|
||
ending_quantity numeric,
|
||
ending_unit_cost numeric,
|
||
ending_amount numeric,
|
||
ending_tax numeric,
|
||
ending_tax_inclusive numeric,
|
||
consumption_quantity numeric,
|
||
consumption_unit_cost numeric,
|
||
consumption_amount numeric,
|
||
consumption_tax numeric,
|
||
consumption_tax_inclusive numeric,
|
||
conversion_precision_amount numeric,
|
||
conversion_precision_tax_inclusive numeric,
|
||
return_loss_amount numeric,
|
||
return_loss_tax_inclusive numeric,
|
||
imported_at timestamptz,
|
||
calculated_consumption_amount numeric,
|
||
is_negative_consumption boolean,
|
||
sales_store_code text,
|
||
sales_store_name text,
|
||
unit_type text,
|
||
mapping_method text,
|
||
management_cost_scope text,
|
||
observed_stores bigint,
|
||
median_unit_cost double precision,
|
||
median_abs_quantity double precision,
|
||
abnormal_reason text,
|
||
unit_cost_vs_median numeric,
|
||
quantity_vs_median numeric
|
||
) AS $$
|
||
WITH stats AS (
|
||
SELECT
|
||
ic.item_code,
|
||
count(DISTINCT ic.sales_store_code) AS observed_stores,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY (ic.consumption_unit_cost::double precision))
|
||
FILTER (WHERE ic.consumption_unit_cost > 0) AS median_unit_cost,
|
||
percentile_cont(0.5) WITHIN GROUP (ORDER BY (abs(ic.consumption_quantity)::double precision))
|
||
FILTER (WHERE ic.consumption_quantity <> 0) AS median_abs_quantity
|
||
FROM analytics.fn_inventory_cost_classified(p_month) ic
|
||
GROUP BY ic.item_code
|
||
), flagged AS (
|
||
SELECT
|
||
ic.*,
|
||
s.observed_stores,
|
||
s.median_unit_cost,
|
||
s.median_abs_quantity,
|
||
CASE
|
||
WHEN ic.consumption_amount < 0 THEN '负倒挤成本'
|
||
WHEN ic.consumption_quantity < 0 THEN '负倒挤数量'
|
||
WHEN s.observed_stores >= 5 AND s.median_unit_cost > 0 AND ic.consumption_unit_cost::double precision > (s.median_unit_cost * 1.5) THEN '成本单价显著偏高'
|
||
WHEN s.observed_stores >= 5 AND s.median_unit_cost > 0 AND ic.consumption_unit_cost > 0 AND ic.consumption_unit_cost::double precision < (s.median_unit_cost * 0.5) THEN '成本单价显著偏低'
|
||
WHEN s.observed_stores >= 5 AND s.median_abs_quantity > 0 AND ic.consumption_amount > 1000 AND abs(ic.consumption_quantity)::double precision > (s.median_abs_quantity * 3) THEN '耗用数量显著偏高'
|
||
ELSE NULL
|
||
END AS abnormal_reason
|
||
FROM analytics.fn_inventory_cost_classified(p_month) ic
|
||
JOIN stats s USING (item_code)
|
||
)
|
||
SELECT
|
||
f.record_id, f.report_month, f.source_file, f.source_row,
|
||
f.brand, f.region, f.cost_unit_name, f.cost_unit_code,
|
||
f.item_name, f.specification, f.major_category, f.minor_category,
|
||
f.finance_category, f.item_code, f.unit,
|
||
f.opening_quantity, f.opening_unit_cost, f.opening_amount,
|
||
f.opening_tax, f.opening_tax_inclusive,
|
||
f.purchase_quantity, f.purchase_unit_cost, f.purchase_amount,
|
||
f.purchase_tax, f.purchase_tax_inclusive,
|
||
f.ending_quantity, f.ending_unit_cost, f.ending_amount,
|
||
f.ending_tax, f.ending_tax_inclusive,
|
||
f.consumption_quantity, f.consumption_unit_cost, f.consumption_amount,
|
||
f.consumption_tax, f.consumption_tax_inclusive,
|
||
f.conversion_precision_amount, f.conversion_precision_tax_inclusive,
|
||
f.return_loss_amount, f.return_loss_tax_inclusive,
|
||
f.imported_at, f.calculated_consumption_amount, f.is_negative_consumption,
|
||
f.sales_store_code, f.sales_store_name, f.unit_type, f.mapping_method,
|
||
f.management_cost_scope,
|
||
f.observed_stores, f.median_unit_cost, f.median_abs_quantity,
|
||
f.abnormal_reason,
|
||
round((f.consumption_unit_cost::double precision / NULLIF(f.median_unit_cost, 0))::numeric, 2) AS unit_cost_vs_median,
|
||
round((abs(f.consumption_quantity)::double precision / NULLIF(f.median_abs_quantity, 0))::numeric, 2) AS quantity_vs_median
|
||
FROM flagged f
|
||
WHERE f.abnormal_reason IS NOT NULL;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 4. fn_inventory_finance_category(p_month)
|
||
-- 替换 v_inventory_finance_category_april
|
||
-- 依赖: fn_inventory_cost_classified
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_inventory_finance_category(p_month date)
|
||
RETURNS TABLE (
|
||
management_cost_scope text,
|
||
finance_category text,
|
||
detail_lines bigint,
|
||
store_count bigint,
|
||
item_count bigint,
|
||
consumption_quantity numeric,
|
||
consumption_amount numeric,
|
||
all_cost_share_pct numeric,
|
||
negative_lines bigint,
|
||
negative_amount numeric,
|
||
ending_inventory_amount numeric
|
||
) AS $$
|
||
SELECT
|
||
ic.management_cost_scope,
|
||
ic.finance_category,
|
||
count(*) AS detail_lines,
|
||
count(DISTINCT ic.sales_store_code) AS store_count,
|
||
count(DISTINCT ic.item_code) AS item_count,
|
||
sum(ic.consumption_quantity) AS consumption_quantity,
|
||
sum(ic.consumption_amount) AS consumption_amount,
|
||
round(sum(ic.consumption_amount) / NULLIF(sum(sum(ic.consumption_amount)) OVER (), 0) * 100, 2) AS all_cost_share_pct,
|
||
count(*) FILTER (WHERE ic.is_negative_consumption) AS negative_lines,
|
||
sum(ic.consumption_amount) FILTER (WHERE ic.is_negative_consumption) AS negative_amount,
|
||
sum(ic.ending_amount) AS ending_inventory_amount
|
||
FROM analytics.fn_inventory_cost_classified(p_month) ic
|
||
GROUP BY ic.management_cost_scope, ic.finance_category;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 5. fn_dish_sales(p_month)
|
||
-- 替换 dish_sales_april
|
||
-- 依赖: dish_sales_details (public), bill_fact
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_dish_sales(p_month date)
|
||
RETURNS TABLE (
|
||
detail_id bigint,
|
||
source_file text,
|
||
source_row integer,
|
||
store_code text,
|
||
store_name text,
|
||
bill_no text,
|
||
business_date date,
|
||
closed_at timestamptz,
|
||
ordered_at timestamp without time zone,
|
||
meal_period text,
|
||
business_type text,
|
||
dish_name text,
|
||
category_level1 text,
|
||
category_level2 text,
|
||
item_type text,
|
||
production_department text,
|
||
unit text,
|
||
preparation_method text,
|
||
sales_quantity numeric,
|
||
unit_price numeric,
|
||
gross_amount numeric,
|
||
received_amount numeric,
|
||
dish_discount_amount numeric,
|
||
bill_guest_count integer,
|
||
bill_consumption numeric,
|
||
bill_received_total numeric,
|
||
bill_discount_total numeric,
|
||
member_id text,
|
||
member_level text,
|
||
marketing_plan text,
|
||
order_tag text,
|
||
bill_theoretical_cost numeric,
|
||
bill_theoretical_profit numeric,
|
||
bill_theoretical_margin numeric,
|
||
meituan_delivery_received numeric,
|
||
taobao_delivery_received numeric,
|
||
jd_delivery_received numeric
|
||
) AS $$
|
||
SELECT
|
||
d.detail_id,
|
||
d.source_file,
|
||
d.source_row,
|
||
d.store_code,
|
||
d.store_name,
|
||
d.bill_no,
|
||
b.closed_at::date AS business_date,
|
||
b.closed_at,
|
||
d.ordered_at,
|
||
COALESCE(NULLIF(b.meal_period, ''), d.business_type) AS meal_period,
|
||
d.business_type,
|
||
d.dish_name,
|
||
d.category_level1,
|
||
d.category_level2,
|
||
d.item_type,
|
||
d.production_department,
|
||
d.unit,
|
||
d.preparation_method,
|
||
d.sales_quantity,
|
||
d.unit_price,
|
||
d.gross_amount,
|
||
d.received_amount,
|
||
d.gross_amount - d.received_amount AS dish_discount_amount,
|
||
b.guest_count AS bill_guest_count,
|
||
b.consumption AS bill_consumption,
|
||
b.received_total AS bill_received_total,
|
||
b.discount_total AS bill_discount_total,
|
||
b.member_id,
|
||
b.member_level,
|
||
b.marketing_plan,
|
||
b.order_tag,
|
||
b.theoretical_cost AS bill_theoretical_cost,
|
||
b.theoretical_profit AS bill_theoretical_profit,
|
||
b.theoretical_margin AS bill_theoretical_margin,
|
||
b.meituan_delivery_received,
|
||
b.taobao_delivery_received,
|
||
b.jd_delivery_received
|
||
FROM public.dish_sales_details d
|
||
JOIN analytics.bill_fact b ON b.store_code = d.store_code AND b.bill_no = d.bill_no
|
||
WHERE b.closed_at >= analytics.fn_month_start_ts(p_month)
|
||
AND b.closed_at < analytics.fn_next_month_start_ts(p_month);
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 6. fn_dish_basket(p_month)
|
||
-- 替换 dish_basket_april
|
||
-- 依赖: fn_dish_sales
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_dish_basket(p_month date)
|
||
RETURNS TABLE (
|
||
store_code text,
|
||
store_name text,
|
||
bill_no text,
|
||
business_date date,
|
||
closed_at timestamptz,
|
||
meal_period text,
|
||
member_id text,
|
||
member_level text,
|
||
guest_count integer,
|
||
bill_consumption numeric,
|
||
bill_received_total numeric,
|
||
bill_discount_total numeric,
|
||
sku_count bigint,
|
||
item_quantity numeric,
|
||
dish_gross_amount numeric,
|
||
dish_received_amount numeric,
|
||
has_noodle boolean,
|
||
has_snack boolean,
|
||
has_drink boolean,
|
||
has_cold_dish boolean,
|
||
has_combo boolean,
|
||
is_delivery boolean
|
||
) AS $$
|
||
SELECT
|
||
ds.store_code,
|
||
min(ds.store_name) AS store_name,
|
||
ds.bill_no,
|
||
min(ds.business_date) AS business_date,
|
||
min(ds.closed_at) AS closed_at,
|
||
min(ds.meal_period) AS meal_period,
|
||
min(ds.member_id) AS member_id,
|
||
min(ds.member_level) AS member_level,
|
||
max(ds.bill_guest_count) AS guest_count,
|
||
max(ds.bill_consumption) AS bill_consumption,
|
||
max(ds.bill_received_total) AS bill_received_total,
|
||
max(ds.bill_discount_total) AS bill_discount_total,
|
||
count(DISTINCT ds.dish_name) AS sku_count,
|
||
sum(ds.sales_quantity) AS item_quantity,
|
||
sum(ds.gross_amount) AS dish_gross_amount,
|
||
sum(ds.received_amount) AS dish_received_amount,
|
||
bool_or(ds.category_level1 = '兰州牛肉面') AS has_noodle,
|
||
bool_or(ds.category_level1 = '特色小吃') AS has_snack,
|
||
bool_or(ds.category_level1 = ANY (ARRAY['酒水饮料', '丝路茶饮'])) AS has_drink,
|
||
bool_or(ds.category_level1 = '爽口凉菜') AS has_cold_dish,
|
||
bool_or(ds.item_type ~~ '%套餐%' OR (ds.category_level1 = ANY (ARRAY['堂食套餐', '外卖套餐']))) AS has_combo,
|
||
bool_or(ds.business_type ~~ '%外卖%' OR COALESCE(ds.order_tag, '') ~~ '%外卖%' OR ds.meituan_delivery_received <> 0 OR ds.taobao_delivery_received <> 0 OR ds.jd_delivery_received <> 0) AS is_delivery
|
||
FROM analytics.fn_dish_sales(p_month) ds
|
||
GROUP BY ds.store_code, ds.bill_no;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 7. fn_dish_store_summary(p_month)
|
||
-- 替换 dish_store_summary_april
|
||
-- 依赖: fn_dish_basket
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_dish_store_summary(p_month date)
|
||
RETURNS TABLE (
|
||
store_code text,
|
||
store_name text,
|
||
bill_count bigint,
|
||
active_days bigint,
|
||
received_amount numeric,
|
||
avg_bill_value numeric,
|
||
items_per_bill numeric,
|
||
skus_per_bill numeric,
|
||
avg_guest_value numeric,
|
||
bill_discount_rate_pct numeric,
|
||
member_bill_share_pct numeric,
|
||
delivery_bill_share_pct numeric,
|
||
noodle_snack_attach_pct numeric,
|
||
noodle_drink_attach_pct numeric,
|
||
noodle_cold_attach_pct numeric,
|
||
combo_bill_share_pct numeric
|
||
) AS $$
|
||
SELECT
|
||
db.store_code,
|
||
min(db.store_name) AS store_name,
|
||
count(*) AS bill_count,
|
||
count(DISTINCT db.business_date) AS active_days,
|
||
sum(db.bill_received_total) AS received_amount,
|
||
round(avg(db.bill_received_total), 2) AS avg_bill_value,
|
||
round(sum(db.item_quantity) / NULLIF(count(*), 0), 2) AS items_per_bill,
|
||
round(avg(db.sku_count), 2) AS skus_per_bill,
|
||
round(sum(db.bill_received_total) / NULLIF(sum(db.guest_count), 0), 2) AS avg_guest_value,
|
||
round(sum(db.bill_discount_total) / NULLIF(sum(db.bill_consumption), 0) * 100, 2) AS bill_discount_rate_pct,
|
||
round(count(*) FILTER (WHERE db.member_id IS NOT NULL)::numeric / NULLIF(count(*), 0) * 100, 2) AS member_bill_share_pct,
|
||
round(count(*) FILTER (WHERE db.is_delivery)::numeric / NULLIF(count(*), 0) * 100, 2) AS delivery_bill_share_pct,
|
||
round(count(*) FILTER (WHERE db.has_noodle AND db.has_snack)::numeric / NULLIF(count(*) FILTER (WHERE db.has_noodle), 0) * 100, 2) AS noodle_snack_attach_pct,
|
||
round(count(*) FILTER (WHERE db.has_noodle AND db.has_drink)::numeric / NULLIF(count(*) FILTER (WHERE db.has_noodle), 0) * 100, 2) AS noodle_drink_attach_pct,
|
||
round(count(*) FILTER (WHERE db.has_noodle AND db.has_cold_dish)::numeric / NULLIF(count(*) FILTER (WHERE db.has_noodle), 0) * 100, 2) AS noodle_cold_attach_pct,
|
||
round(count(*) FILTER (WHERE db.has_combo)::numeric / NULLIF(count(*), 0) * 100, 2) AS combo_bill_share_pct
|
||
FROM analytics.fn_dish_basket(p_month) db
|
||
GROUP BY db.store_code;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 8. fn_store_benchmark_composite(p_month)
|
||
-- 替换 v_store_benchmark_composite(硬编码 '2026-04-01')
|
||
-- 依赖: v_store_benchmark, v_store_repeat_summary_monthly, fn_store_platform_economics
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_store_benchmark_composite(p_month date)
|
||
RETURNS TABLE (
|
||
store_code text,
|
||
store_name text,
|
||
received numeric,
|
||
avg_daily_received numeric,
|
||
avg_bill_value numeric,
|
||
discount_rate_pct numeric,
|
||
theoretical_margin_pct numeric,
|
||
anomaly_rate_pct numeric,
|
||
member_bill_share_pct numeric,
|
||
identified_members bigint,
|
||
repeat_rate_pct numeric,
|
||
avg_orders numeric,
|
||
repeat_revenue_share_pct numeric,
|
||
meituan_cost_rate_pct numeric,
|
||
taobao_cost_rate_pct numeric,
|
||
jd_cost_rate_pct numeric,
|
||
revenue_score double precision,
|
||
margin_score double precision,
|
||
discount_score double precision,
|
||
anomaly_score double precision,
|
||
repeat_score double precision,
|
||
benchmark_score numeric
|
||
) 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 = p_month
|
||
LEFT JOIN analytics.fn_store_platform_economics(p_month) p ON p.store_code = b.store_code
|
||
WHERE b.theoretical_margin_pct >= 60 AND b.theoretical_margin_pct <= 80 AND r.identified_members >= 500
|
||
), scored AS (
|
||
SELECT
|
||
e.*,
|
||
percent_rank() OVER (ORDER BY e.avg_daily_received) AS revenue_score,
|
||
percent_rank() OVER (ORDER BY e.theoretical_margin_pct) AS margin_score,
|
||
1 - percent_rank() OVER (ORDER BY e.discount_rate_pct) AS discount_score,
|
||
1 - percent_rank() OVER (ORDER BY e.anomaly_rate_pct) AS anomaly_score,
|
||
percent_rank() OVER (ORDER BY e.repeat_rate_pct) AS repeat_score
|
||
FROM eligible e
|
||
)
|
||
SELECT
|
||
s.store_code,
|
||
s.store_name,
|
||
s.received,
|
||
s.avg_daily_received,
|
||
s.avg_bill_value,
|
||
s.discount_rate_pct,
|
||
s.theoretical_margin_pct,
|
||
s.anomaly_rate_pct,
|
||
s.member_bill_share_pct,
|
||
s.identified_members,
|
||
s.repeat_rate_pct,
|
||
s.avg_orders,
|
||
s.repeat_revenue_share_pct,
|
||
s.meituan_cost_rate_pct,
|
||
s.taobao_cost_rate_pct,
|
||
s.jd_cost_rate_pct,
|
||
s.revenue_score,
|
||
s.margin_score,
|
||
s.discount_score,
|
||
s.anomaly_score,
|
||
s.repeat_score,
|
||
round(((s.revenue_score * 0.25 + s.margin_score * 0.25 + s.discount_score * 0.20 + s.anomaly_score * 0.15 + s.repeat_score * 0.15) * 100)::numeric, 2) AS benchmark_score
|
||
FROM scored s;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 9. fn_store_deep_diagnosis(p_month)
|
||
-- 替换 v_store_deep_diagnosis_april
|
||
-- 依赖: v_store_scorecard, fn_dish_store_summary, fn_store_theoretical_actual_cost,
|
||
-- fn_store_benchmark_composite, fn_store_platform_economics
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_store_deep_diagnosis(p_month date)
|
||
RETURNS TABLE (
|
||
store_code text,
|
||
store_name text,
|
||
bill_count bigint,
|
||
active_days bigint,
|
||
received numeric,
|
||
avg_daily_received numeric,
|
||
avg_bill_value numeric,
|
||
discount_rate_pct numeric,
|
||
theoretical_margin_pct numeric,
|
||
member_bill_share_pct numeric,
|
||
items_per_bill numeric,
|
||
skus_per_bill numeric,
|
||
delivery_bill_share_pct numeric,
|
||
noodle_snack_attach_pct numeric,
|
||
noodle_drink_attach_pct numeric,
|
||
noodle_cold_attach_pct numeric,
|
||
combo_bill_share_pct numeric,
|
||
theoretical_cost numeric,
|
||
actual_food_cost numeric,
|
||
food_cost_variance numeric,
|
||
theoretical_cost_rate_pct numeric,
|
||
actual_food_cost_rate_pct numeric,
|
||
variance_to_theoretical_pct numeric,
|
||
comparison_status text,
|
||
variance_level text,
|
||
identified_members bigint,
|
||
repeat_rate_pct numeric,
|
||
repeat_revenue_share_pct numeric,
|
||
benchmark_score numeric,
|
||
meituan_received numeric,
|
||
taobao_received numeric,
|
||
jd_received numeric,
|
||
combined_platform_cost_rate_pct numeric,
|
||
business_type text,
|
||
scale_tier text,
|
||
problem_count integer,
|
||
problem_combination text
|
||
) 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) + COALESCE(p.taobao_discount, 0) + COALESCE(p.jd_discount, 0) +
|
||
COALESCE(p.meituan_commission, 0) + COALESCE(p.taobao_commission, 0) + COALESCE(p.jd_commission, 0)) /
|
||
NULLIF(COALESCE(p.meituan_received, 0) + COALESCE(p.taobao_received, 0) + COALESCE(p.jd_received, 0) +
|
||
COALESCE(p.meituan_discount, 0) + COALESCE(p.taobao_discount, 0) + COALESCE(p.jd_discount, 0) +
|
||
COALESCE(p.meituan_commission, 0) + COALESCE(p.taobao_commission, 0) + COALESCE(p.jd_commission, 0), 0) * 100, 2) AS combined_platform_cost_rate_pct,
|
||
CASE
|
||
WHEN s.store_name ~ '机场|火锅|商城|快手|哈马尔罕' THEN '特殊业态'
|
||
ELSE '标准门店'
|
||
END AS business_type
|
||
FROM analytics.v_store_scorecard s
|
||
LEFT JOIN analytics.fn_dish_store_summary(p_month) d USING (store_code)
|
||
LEFT JOIN analytics.fn_store_theoretical_actual_cost(p_month) c USING (store_code)
|
||
LEFT JOIN analytics.fn_store_benchmark_composite(p_month) b USING (store_code)
|
||
LEFT JOIN analytics.fn_store_platform_economics(p_month) p USING (store_code)
|
||
), tiered AS (
|
||
SELECT
|
||
sb.*,
|
||
CASE
|
||
WHEN sb.business_type = '特殊业态' THEN '特殊业态'
|
||
WHEN percent_rank() OVER (PARTITION BY sb.business_type ORDER BY sb.received) >= 0.67 THEN '高规模'
|
||
WHEN percent_rank() OVER (PARTITION BY sb.business_type ORDER BY sb.received) >= 0.33 THEN '中规模'
|
||
ELSE '低规模'
|
||
END AS scale_tier
|
||
FROM store_base sb
|
||
)
|
||
SELECT
|
||
t.store_code,
|
||
t.store_name,
|
||
t.bill_count,
|
||
t.active_days,
|
||
t.received,
|
||
t.avg_daily_received,
|
||
t.avg_bill_value,
|
||
t.discount_rate_pct,
|
||
t.theoretical_margin_pct,
|
||
t.member_bill_share_pct,
|
||
t.items_per_bill,
|
||
t.skus_per_bill,
|
||
t.delivery_bill_share_pct,
|
||
t.noodle_snack_attach_pct,
|
||
t.noodle_drink_attach_pct,
|
||
t.noodle_cold_attach_pct,
|
||
t.combo_bill_share_pct,
|
||
t.theoretical_cost,
|
||
t.actual_food_cost,
|
||
t.food_cost_variance,
|
||
t.theoretical_cost_rate_pct,
|
||
t.actual_food_cost_rate_pct,
|
||
t.variance_to_theoretical_pct,
|
||
t.comparison_status,
|
||
t.variance_level,
|
||
t.identified_members,
|
||
t.repeat_rate_pct,
|
||
t.repeat_revenue_share_pct,
|
||
t.benchmark_score,
|
||
t.meituan_received,
|
||
t.taobao_received,
|
||
t.jd_received,
|
||
t.combined_platform_cost_rate_pct,
|
||
t.business_type,
|
||
t.scale_tier,
|
||
(CASE WHEN t.comparison_status = '可比' AND t.variance_to_theoretical_pct >= 20 THEN 1 ELSE 0 END +
|
||
CASE WHEN t.discount_rate_pct >= 23.02 THEN 1 ELSE 0 END +
|
||
CASE WHEN t.theoretical_margin_pct < 70 THEN 1 ELSE 0 END +
|
||
CASE WHEN t.identified_members >= 500 AND t.repeat_rate_pct < 30 THEN 1 ELSE 0 END +
|
||
CASE WHEN t.combined_platform_cost_rate_pct >= 40 THEN 1 ELSE 0 END +
|
||
CASE WHEN t.noodle_drink_attach_pct < 12 THEN 1 ELSE 0 END) AS problem_count,
|
||
concat_ws('+',
|
||
CASE WHEN t.comparison_status = '理论成本口径异常' THEN '成本口径异常' ELSE NULL END,
|
||
CASE WHEN t.comparison_status = '可比' AND t.variance_to_theoretical_pct >= 20 THEN '实际成本严重超耗' ELSE NULL END,
|
||
CASE WHEN t.discount_rate_pct >= 23.02 THEN '优惠偏高' ELSE NULL END,
|
||
CASE WHEN t.theoretical_margin_pct < 70 THEN '理论毛利偏低' ELSE NULL END,
|
||
CASE WHEN t.identified_members >= 500 AND t.repeat_rate_pct < 30 THEN '会员复购偏低' ELSE NULL END,
|
||
CASE WHEN t.combined_platform_cost_rate_pct >= 40 THEN '平台成本偏高' ELSE NULL END,
|
||
CASE WHEN t.noodle_drink_attach_pct < 12 THEN '饮品搭售偏低' ELSE NULL END
|
||
) AS problem_combination
|
||
FROM tiered t;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 10. fn_store_action_priority_deep(p_month)
|
||
-- 替换 v_store_action_priority_deep_april
|
||
-- 依赖: fn_store_deep_diagnosis
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_store_action_priority_deep(p_month date)
|
||
RETURNS TABLE (
|
||
store_code text,
|
||
store_name text,
|
||
bill_count bigint,
|
||
active_days bigint,
|
||
received numeric,
|
||
avg_daily_received numeric,
|
||
avg_bill_value numeric,
|
||
discount_rate_pct numeric,
|
||
theoretical_margin_pct numeric,
|
||
member_bill_share_pct numeric,
|
||
items_per_bill numeric,
|
||
skus_per_bill numeric,
|
||
delivery_bill_share_pct numeric,
|
||
noodle_snack_attach_pct numeric,
|
||
noodle_drink_attach_pct numeric,
|
||
noodle_cold_attach_pct numeric,
|
||
combo_bill_share_pct numeric,
|
||
theoretical_cost numeric,
|
||
actual_food_cost numeric,
|
||
food_cost_variance numeric,
|
||
theoretical_cost_rate_pct numeric,
|
||
actual_food_cost_rate_pct numeric,
|
||
variance_to_theoretical_pct numeric,
|
||
comparison_status text,
|
||
variance_level text,
|
||
identified_members bigint,
|
||
repeat_rate_pct numeric,
|
||
repeat_revenue_share_pct numeric,
|
||
benchmark_score numeric,
|
||
meituan_received numeric,
|
||
taobao_received numeric,
|
||
jd_received numeric,
|
||
combined_platform_cost_rate_pct numeric,
|
||
business_type text,
|
||
scale_tier text,
|
||
problem_count integer,
|
||
problem_combination text,
|
||
action_priority text
|
||
) AS $$
|
||
SELECT
|
||
d.store_code,
|
||
d.store_name,
|
||
d.bill_count,
|
||
d.active_days,
|
||
d.received,
|
||
d.avg_daily_received,
|
||
d.avg_bill_value,
|
||
d.discount_rate_pct,
|
||
d.theoretical_margin_pct,
|
||
d.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,
|
||
d.theoretical_cost,
|
||
d.actual_food_cost,
|
||
d.food_cost_variance,
|
||
d.theoretical_cost_rate_pct,
|
||
d.actual_food_cost_rate_pct,
|
||
d.variance_to_theoretical_pct,
|
||
d.comparison_status,
|
||
d.variance_level,
|
||
d.identified_members,
|
||
d.repeat_rate_pct,
|
||
d.repeat_revenue_share_pct,
|
||
d.benchmark_score,
|
||
d.meituan_received,
|
||
d.taobao_received,
|
||
d.jd_received,
|
||
d.combined_platform_cost_rate_pct,
|
||
d.business_type,
|
||
d.scale_tier,
|
||
d.problem_count,
|
||
d.problem_combination,
|
||
CASE
|
||
WHEN d.business_type = '特殊业态' THEN '特殊业态'
|
||
WHEN d.problem_count >= 4 OR (d.comparison_status = '理论成本口径异常' AND d.problem_count >= 2) THEN 'P0-综合专项整改'
|
||
WHEN d.comparison_status = '理论成本口径异常' THEN 'P0-修复数据口径'
|
||
WHEN d.problem_count >= 2 THEN 'P1-重点整改'
|
||
WHEN d.problem_count = 1 THEN 'P2-单项改善'
|
||
WHEN d.benchmark_score >= 70 THEN '标杆候选'
|
||
ELSE '持续跟踪'
|
||
END AS action_priority
|
||
FROM analytics.fn_store_deep_diagnosis(p_month) d;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 11. fn_store_area_efficiency(p_month)
|
||
-- 替换 v_store_area_efficiency_april
|
||
-- 依赖: fn_store_action_priority_deep, v_store_location_operating
|
||
-- ============================================================
|
||
CREATE OR REPLACE FUNCTION analytics.fn_store_area_efficiency(p_month date)
|
||
RETURNS TABLE (
|
||
store_code text,
|
||
store_name text,
|
||
bill_count bigint,
|
||
active_days bigint,
|
||
received numeric,
|
||
avg_daily_received numeric,
|
||
avg_bill_value numeric,
|
||
discount_rate_pct numeric,
|
||
theoretical_margin_pct numeric,
|
||
member_bill_share_pct numeric,
|
||
items_per_bill numeric,
|
||
skus_per_bill numeric,
|
||
delivery_bill_share_pct numeric,
|
||
noodle_snack_attach_pct numeric,
|
||
noodle_drink_attach_pct numeric,
|
||
noodle_cold_attach_pct numeric,
|
||
combo_bill_share_pct numeric,
|
||
theoretical_cost numeric,
|
||
actual_food_cost numeric,
|
||
food_cost_variance numeric,
|
||
theoretical_cost_rate_pct numeric,
|
||
actual_food_cost_rate_pct numeric,
|
||
variance_to_theoretical_pct numeric,
|
||
comparison_status text,
|
||
variance_level text,
|
||
identified_members bigint,
|
||
repeat_rate_pct numeric,
|
||
repeat_revenue_share_pct numeric,
|
||
benchmark_score numeric,
|
||
meituan_received numeric,
|
||
taobao_received numeric,
|
||
jd_received numeric,
|
||
combined_platform_cost_rate_pct numeric,
|
||
business_type text,
|
||
scale_tier text,
|
||
problem_count integer,
|
||
problem_combination text,
|
||
action_priority text,
|
||
area_sqm numeric,
|
||
business_address text,
|
||
city text,
|
||
district text,
|
||
latitude_gcj02 double precision,
|
||
longitude_gcj02 double precision,
|
||
open_date date,
|
||
lease_expiry_date date,
|
||
store_age_years numeric,
|
||
monthly_received_per_sqm numeric,
|
||
daily_received_per_sqm numeric,
|
||
estimated_inventory_days numeric
|
||
) AS $$
|
||
SELECT
|
||
d.store_code,
|
||
d.store_name,
|
||
d.bill_count,
|
||
d.active_days,
|
||
d.received,
|
||
d.avg_daily_received,
|
||
d.avg_bill_value,
|
||
d.discount_rate_pct,
|
||
d.theoretical_margin_pct,
|
||
d.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,
|
||
d.theoretical_cost,
|
||
d.actual_food_cost,
|
||
d.food_cost_variance,
|
||
d.theoretical_cost_rate_pct,
|
||
d.actual_food_cost_rate_pct,
|
||
d.variance_to_theoretical_pct,
|
||
d.comparison_status,
|
||
d.variance_level,
|
||
d.identified_members,
|
||
d.repeat_rate_pct,
|
||
d.repeat_revenue_share_pct,
|
||
d.benchmark_score,
|
||
d.meituan_received,
|
||
d.taobao_received,
|
||
d.jd_received,
|
||
d.combined_platform_cost_rate_pct,
|
||
d.business_type,
|
||
d.scale_tier,
|
||
d.problem_count,
|
||
d.problem_combination,
|
||
d.action_priority,
|
||
loc.area_sqm,
|
||
loc.business_address,
|
||
loc.city,
|
||
loc.district,
|
||
loc.latitude_gcj02,
|
||
loc.longitude_gcj02,
|
||
loc.opened_date AS open_date,
|
||
loc.lease_expiry_date,
|
||
round(EXTRACT(year FROM age(p_month::timestamp with time zone, loc.opened_date::timestamp with time zone)) +
|
||
EXTRACT(day FROM age(p_month::timestamp with time zone, loc.opened_date::timestamp with time zone)) / 365.25, 2) AS store_age_years,
|
||
round(d.received / NULLIF(loc.area_sqm, 0), 2) AS monthly_received_per_sqm,
|
||
round(d.avg_daily_received / NULLIF(loc.area_sqm, 0), 2) AS daily_received_per_sqm,
|
||
NULL::numeric AS estimated_inventory_days
|
||
FROM analytics.fn_store_action_priority_deep(p_month) d
|
||
LEFT JOIN analytics.v_store_location_operating loc ON loc.sales_store_code = d.store_code;
|
||
$$ LANGUAGE sql STABLE;
|
||
|
||
-- ============================================================
|
||
-- 验证:对比函数结果与原视图(以2026-04为例)
|
||
-- ============================================================
|
||
-- SELECT count(*) FROM analytics.fn_inventory_cost_classified('2026-04-01');
|
||
-- SELECT count(*) FROM analytics.v_inventory_cost_classified_april;
|
||
-- SELECT count(*) FROM analytics.fn_store_theoretical_actual_cost('2026-04-01');
|
||
-- SELECT count(*) FROM analytics.v_store_theoretical_actual_cost_april;
|
||
-- SELECT count(*) FROM analytics.fn_store_action_priority_deep('2026-04-01');
|
||
-- SELECT count(*) FROM analytics.v_store_action_priority_deep_april;
|