mall数据库文件

This commit is contained in:
comlibmb
2026-01-30 16:17:13 +08:00
parent cfec4a16c0
commit 8f181b2b6a
42 changed files with 12758 additions and 2 deletions

View File

@@ -0,0 +1,388 @@
-- ============================================
-- 优惠券效果分析 RPC 集合Supabase / Postgres
-- 说明:
-- - 这些函数只依赖业务域表:
-- coupon_templates, user_coupons, coupon_usage_logs, orders
-- - 不创建/修改业务表结构,由业务侧 schema 负责。
-- - 仅提供 Analytics Dashboard 所需的聚合统计。
-- - 调用方:/pages/mall/analytics/coupon-analysis.uvue
-- ============================================
-- 安全注意:
-- - 函数使用 SECURITY DEFINER并将执行权限收敛到 authenticated 角色。
-- - 具体可见文件末尾的 REVOKE / GRANT 语句。
-- 1) 概览 KPI发放/使用/GMV 提升/ROI/整体到期情况
CREATE OR REPLACE FUNCTION public.rpc_coupon_effectiveness_overview(
p_start timestamptz,
p_end timestamptz,
p_merchant_id uuid DEFAULT NULL
)
RETURNS TABLE (
total_issued numeric,
total_used numeric,
usage_rate numeric,
gmv_increase numeric,
issued_growth numeric,
gmv_growth numeric,
roi numeric,
about_to_expire_cnt numeric
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_prev_start timestamptz;
v_prev_end timestamptz;
v_prev_issued numeric := 0;
v_prev_gmv numeric := 0;
v_discount_sum numeric := 0;
BEGIN
IF p_start IS NULL OR p_end IS NULL OR p_start >= p_end THEN
RAISE EXCEPTION 'invalid period';
END IF;
-- 上一周期窗口:长度与当前周期一致
v_prev_start := p_start - (p_end - p_start);
v_prev_end := p_start;
-- 当前周期发放数量user_coupons.received_at
SELECT
COALESCE(COUNT(uc.id), 0)::numeric
INTO total_issued
FROM user_coupons uc
JOIN coupon_templates ct ON ct.id = uc.template_id
WHERE uc.received_at >= p_start
AND uc.received_at < p_end
AND (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id);
-- 当前周期:使用记录 + GMV / 优惠额
SELECT
COALESCE(COUNT(cul.id), 0)::numeric,
COALESCE(SUM(COALESCE(cul.order_amount, o.total_amount)), 0)::numeric,
COALESCE(SUM(cul.discount_amount), 0)::numeric
INTO total_used, gmv_increase, v_discount_sum
FROM coupon_usage_logs cul
JOIN coupon_templates ct ON ct.id = cul.template_id
LEFT JOIN orders o ON o.id = cul.order_id
WHERE cul.used_at >= p_start
AND cul.used_at < p_end
AND (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id);
-- 使用率0-100
IF total_issued > 0 THEN
usage_rate := ROUND((total_used / total_issued) * 100.0, 2);
ELSE
usage_rate := 0;
END IF;
-- 上一周期:发放与 GMV用于增长率
SELECT
COALESCE(COUNT(uc.id), 0)::numeric
INTO v_prev_issued
FROM user_coupons uc
JOIN coupon_templates ct ON ct.id = uc.template_id
WHERE uc.received_at >= v_prev_start
AND uc.received_at < v_prev_end
AND (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id);
SELECT
COALESCE(SUM(COALESCE(cul.order_amount, o.total_amount)), 0)::numeric
INTO v_prev_gmv
FROM coupon_usage_logs cul
JOIN coupon_templates ct ON ct.id = cul.template_id
LEFT JOIN orders o ON o.id = cul.order_id
WHERE cul.used_at >= v_prev_start
AND cul.used_at < v_prev_end
AND (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id);
-- 发放增长率
IF v_prev_issued > 0 THEN
issued_growth := ROUND(((total_issued - v_prev_issued) / v_prev_issued) * 100.0, 2);
ELSE
issued_growth := CASE WHEN total_issued > 0 THEN 100.0 ELSE 0.0 END;
END IF;
-- GMV 增长率
IF v_prev_gmv > 0 THEN
gmv_growth := ROUND(((gmv_increase - v_prev_gmv) / v_prev_gmv) * 100.0, 2);
ELSE
gmv_growth := CASE WHEN gmv_increase > 0 THEN 100.0 ELSE 0.0 END;
END IF;
-- ROI = (GMV_with_coupon - discount_sum) / discount_sum
IF v_discount_sum > 0 THEN
roi := ROUND(((gmv_increase - v_discount_sum) / v_discount_sum) * 100.0, 2);
ELSE
roi := 0;
END IF;
-- 未来 7 天内即将到期且未使用的券数量(整体概览)
SELECT
COALESCE(COUNT(uc2.id), 0)::numeric
INTO about_to_expire_cnt
FROM user_coupons uc2
JOIN coupon_templates ct2 ON ct2.id = uc2.template_id
WHERE uc2.status = 1 -- 假设 1 = unused参考 mall.md
AND uc2.expire_at > now()
AND uc2.expire_at <= now() + interval '7 days'
AND (p_merchant_id IS NULL OR ct2.merchant_id = p_merchant_id);
RETURN;
END;
$$;
-- 2) 按券类型统计8 种券类型效果
CREATE OR REPLACE FUNCTION public.rpc_coupon_type_stats(
p_start timestamptz,
p_end timestamptz,
p_merchant_id uuid DEFAULT NULL
)
RETURNS TABLE (
coupon_type integer,
total_issued numeric,
total_used numeric,
usage_rate numeric,
gmv_with_coupon numeric,
amount_saved numeric
)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
AS $$
SELECT
ct.coupon_type::integer AS coupon_type,
COALESCE(COUNT(DISTINCT uc.id), 0)::numeric AS total_issued,
COALESCE(COUNT(DISTINCT cul.id), 0)::numeric AS total_used,
CASE
WHEN COUNT(DISTINCT uc.id) > 0
THEN ROUND(
(COUNT(DISTINCT cul.id)::numeric / COUNT(DISTINCT uc.id)::numeric) * 100.0,
2
)
ELSE 0
END AS usage_rate,
COALESCE(SUM(COALESCE(cul.order_amount, o.total_amount)), 0)::numeric AS gmv_with_coupon,
COALESCE(SUM(cul.discount_amount), 0)::numeric AS amount_saved
FROM coupon_templates ct
LEFT JOIN user_coupons uc
ON uc.template_id = ct.id
AND uc.received_at >= p_start
AND uc.received_at < p_end
LEFT JOIN coupon_usage_logs cul
ON cul.template_id = ct.id
AND cul.used_at >= p_start
AND cul.used_at < p_end
LEFT JOIN orders o
ON o.id = cul.order_id
WHERE (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id)
GROUP BY ct.coupon_type
ORDER BY total_issued DESC;
$$;
-- 3) 按发放渠道统计:主动领取/自动发放/活动/邀请/客服/积分
CREATE OR REPLACE FUNCTION public.rpc_coupon_channel_stats(
p_start timestamptz,
p_end timestamptz,
p_merchant_id uuid DEFAULT NULL
)
RETURNS TABLE (
channel text,
total_issued numeric,
total_used numeric,
usage_rate numeric,
gmv_with_coupon numeric
)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
AS $$
SELECT
uc.obtain_channel::text AS channel,
COALESCE(COUNT(DISTINCT uc.id), 0)::numeric AS total_issued,
COALESCE(COUNT(DISTINCT cul.id), 0)::numeric AS total_used,
CASE
WHEN COUNT(DISTINCT uc.id) > 0
THEN ROUND(
(COUNT(DISTINCT cul.id)::numeric / COUNT(DISTINCT uc.id)::numeric) * 100.0,
2
)
ELSE 0
END AS usage_rate,
COALESCE(SUM(COALESCE(cul.order_amount, o.total_amount)), 0)::numeric AS gmv_with_coupon
FROM user_coupons uc
JOIN coupon_templates ct
ON ct.id = uc.template_id
LEFT JOIN coupon_usage_logs cul
ON cul.user_coupon_id = uc.id
AND cul.used_at >= p_start
AND cul.used_at < p_end
LEFT JOIN orders o
ON o.id = cul.order_id
WHERE uc.received_at >= p_start
AND uc.received_at < p_end
AND (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id)
AND uc.obtain_channel IS NOT NULL
GROUP BY uc.obtain_channel
ORDER BY total_issued DESC;
$$;
-- 4) 使用趋势:按天发放 vs 使用
CREATE OR REPLACE FUNCTION public.rpc_coupon_trend_daily(
p_start timestamptz,
p_end timestamptz,
p_merchant_id uuid DEFAULT NULL
)
RETURNS TABLE (
day date,
issued numeric,
used numeric
)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
AS $$
WITH days AS (
SELECT generate_series(
date_trunc('day', p_start)::date,
date_trunc('day', p_end)::date,
interval '1 day'
)::date AS d
),
issued AS (
SELECT
uc_day::date AS d,
COUNT(*)::numeric AS cnt
FROM (
SELECT DATE(uc.received_at) AS uc_day
FROM user_coupons uc
JOIN coupon_templates ct ON ct.id = uc.template_id
WHERE uc.received_at >= p_start
AND uc.received_at < p_end
AND (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id)
) t
GROUP BY uc_day
),
used AS (
SELECT
cul_day::date AS d,
COUNT(*)::numeric AS cnt
FROM (
SELECT DATE(cul.used_at) AS cul_day
FROM coupon_usage_logs cul
JOIN coupon_templates ct ON ct.id = cul.template_id
WHERE cul.used_at >= p_start
AND cul.used_at < p_end
AND (p_merchant_id IS NULL OR ct.merchant_id = p_merchant_id)
) t
GROUP BY cul_day
)
SELECT
d.d AS day,
COALESCE(i.cnt, 0) AS issued,
COALESCE(u.cnt, 0) AS used
FROM days d
LEFT JOIN issued i ON i.d = d.d
LEFT JOIN used u ON u.d = d.d
ORDER BY d.d;
$$;
-- 5) 转化效果:有券 vs 无券GMV/订单数/客单价)
CREATE OR REPLACE FUNCTION public.rpc_coupon_conversion_effect(
p_start timestamptz,
p_end timestamptz,
p_merchant_id uuid DEFAULT NULL
)
RETURNS TABLE (
metric text,
with_coupon numeric,
without_coupon numeric
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_with_orders numeric := 0;
v_without_orders numeric := 0;
v_with_gmv numeric := 0;
v_without_gmv numeric := 0;
BEGIN
-- 有券订单集合
SELECT
COALESCE(COUNT(DISTINCT o.id), 0)::numeric,
COALESCE(SUM(o.total_amount), 0)::numeric
INTO v_with_orders, v_with_gmv
FROM orders o
WHERE o.created_at >= p_start
AND o.created_at < p_end
AND (p_merchant_id IS NULL OR o.merchant_id = p_merchant_id)
AND EXISTS (
SELECT 1
FROM coupon_usage_logs cul
WHERE cul.order_id = o.id
);
-- 无券订单集合
SELECT
COALESCE(COUNT(DISTINCT o.id), 0)::numeric,
COALESCE(SUM(o.total_amount), 0)::numeric
INTO v_without_orders, v_without_gmv
FROM orders o
WHERE o.created_at >= p_start
AND o.created_at < p_end
AND (p_merchant_id IS NULL OR o.merchant_id = p_merchant_id)
AND NOT EXISTS (
SELECT 1
FROM coupon_usage_logs cul
WHERE cul.order_id = o.id
);
-- GMV 行
metric := 'GMV';
with_coupon := v_with_gmv;
without_coupon := v_without_gmv;
RETURN NEXT;
-- 订单数 行
metric := 'orders';
with_coupon := v_with_orders;
without_coupon := v_without_orders;
RETURN NEXT;
-- 客单价 行
metric := 'avg_order_amount';
with_coupon := CASE WHEN v_with_orders > 0 THEN ROUND(v_with_gmv / v_with_orders, 2) ELSE 0 END;
without_coupon := CASE WHEN v_without_orders > 0 THEN ROUND(v_without_gmv / v_without_orders, 2) ELSE 0 END;
RETURN NEXT;
RETURN;
END;
$$;
-- ============================================
-- 权限收敛:只允许 authenticated 角色调用
-- ============================================
REVOKE ALL ON FUNCTION public.rpc_coupon_effectiveness_overview(timestamptz,timestamptz,uuid) FROM PUBLIC;
REVOKE ALL ON FUNCTION public.rpc_coupon_type_stats(timestamptz,timestamptz,uuid) FROM PUBLIC;
REVOKE ALL ON FUNCTION public.rpc_coupon_channel_stats(timestamptz,timestamptz,uuid) FROM PUBLIC;
REVOKE ALL ON FUNCTION public.rpc_coupon_trend_daily(timestamptz,timestamptz,uuid) FROM PUBLIC;
REVOKE ALL ON FUNCTION public.rpc_coupon_conversion_effect(timestamptz,timestamptz,uuid) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_coupon_effectiveness_overview(timestamptz,timestamptz,uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION public.rpc_coupon_type_stats(timestamptz,timestamptz,uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION public.rpc_coupon_channel_stats(timestamptz,timestamptz,uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION public.rpc_coupon_trend_daily(timestamptz,timestamptz,uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION public.rpc_coupon_conversion_effect(timestamptz,timestamptz,uuid) TO authenticated;
-- 完成
SELECT 'Coupon analysis RPCs created successfully!' AS message;