mall数据库文件
This commit is contained in:
388
pages/mall/analytics/test/COUPON_ANALYSIS_RPCS.sql
Normal file
388
pages/mall/analytics/test/COUPON_ANALYSIS_RPCS.sql
Normal 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;
|
||||
|
||||
Reference in New Issue
Block a user