177 lines
6.1 KiB
PL/PgSQL
177 lines
6.1 KiB
PL/PgSQL
-- =====================================================================================
|
||
-- 数据分析模块(正式RPC,主库 ml_* 口径)
|
||
-- 文件: 02_ml_analytics_rpcs_sales.sql
|
||
-- 主题: 仪表盘/销售报表(KPI/趋势/TOP)
|
||
-- 口径约定:
|
||
-- - GMV: paid_amount 汇总(若为 0 则用 total_amount 兜底)
|
||
-- - 订单量: ml_orders created_at 期间内订单数(可按需要切换为支付订单数)
|
||
-- - 支付用户数: payment_status=2 的 distinct user_id
|
||
-- - 活跃用户数: ml_browse_history created_at 期间内 distinct user_id(弱口径)
|
||
-- - 转化率(A): 支付用户数 / 活跃用户数(*100)
|
||
-- =====================================================================================
|
||
|
||
-- 1) 销售核心 KPI(含上期对比)
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_sales_kpis(
|
||
p_start_date DATE,
|
||
p_end_date DATE
|
||
)
|
||
RETURNS TABLE (
|
||
gmv NUMERIC,
|
||
gmv_growth FLOAT,
|
||
orders BIGINT,
|
||
order_growth FLOAT,
|
||
conversion_rate FLOAT,
|
||
conversion_growth FLOAT,
|
||
avg_order_amount NUMERIC,
|
||
avg_order_growth FLOAT
|
||
)
|
||
LANGUAGE plpgsql
|
||
AS $$
|
||
DECLARE
|
||
prev_start_date DATE;
|
||
prev_end_date DATE;
|
||
period_days INT;
|
||
BEGIN
|
||
period_days := p_end_date - p_start_date + 1;
|
||
prev_start_date := p_start_date - period_days;
|
||
prev_end_date := p_start_date - 1;
|
||
|
||
RETURN QUERY
|
||
WITH
|
||
-- 当前周期
|
||
cur AS (
|
||
SELECT
|
||
COALESCE(
|
||
SUM(CASE WHEN o.payment_status = 2 THEN COALESCE(NULLIF(o.paid_amount, 0), o.total_amount) ELSE 0 END),
|
||
0
|
||
) AS gmv,
|
||
COUNT(*)::BIGINT AS orders,
|
||
COUNT(DISTINCT CASE WHEN o.payment_status = 2 THEN o.user_id END)::BIGINT AS paid_users,
|
||
(SELECT COUNT(DISTINCT bh.user_id) FROM public.ml_browse_history bh WHERE bh.created_at::DATE BETWEEN p_start_date AND p_end_date)::BIGINT AS active_users
|
||
FROM public.ml_orders o
|
||
WHERE o.created_at::DATE BETWEEN p_start_date AND p_end_date
|
||
),
|
||
-- 上一周期
|
||
prev AS (
|
||
SELECT
|
||
COALESCE(
|
||
SUM(CASE WHEN o.payment_status = 2 THEN COALESCE(NULLIF(o.paid_amount, 0), o.total_amount) ELSE 0 END),
|
||
0
|
||
) AS gmv,
|
||
COUNT(*)::BIGINT AS orders,
|
||
COUNT(DISTINCT CASE WHEN o.payment_status = 2 THEN o.user_id END)::BIGINT AS paid_users,
|
||
(SELECT COUNT(DISTINCT bh.user_id) FROM public.ml_browse_history bh WHERE bh.created_at::DATE BETWEEN prev_start_date AND prev_end_date)::BIGINT AS active_users
|
||
FROM public.ml_orders o
|
||
WHERE o.created_at::DATE BETWEEN prev_start_date AND prev_end_date
|
||
),
|
||
calc AS (
|
||
SELECT
|
||
cur.gmv AS gmv,
|
||
cur.orders AS orders,
|
||
CASE WHEN cur.active_users > 0 THEN (cur.paid_users::NUMERIC / cur.active_users::NUMERIC) * 100 ELSE 0 END AS conversion_rate,
|
||
CASE WHEN cur.orders > 0 THEN cur.gmv / cur.orders ELSE 0 END AS avg_order_amount,
|
||
|
||
prev.gmv AS prev_gmv,
|
||
prev.orders AS prev_orders,
|
||
CASE WHEN prev.active_users > 0 THEN (prev.paid_users::NUMERIC / prev.active_users::NUMERIC) * 100 ELSE 0 END AS prev_conversion_rate,
|
||
CASE WHEN prev.orders > 0 THEN prev.gmv / prev.orders ELSE 0 END AS prev_avg_order_amount
|
||
FROM cur, prev
|
||
)
|
||
SELECT
|
||
ROUND(calc.gmv, 2) AS gmv,
|
||
ROUND(((calc.gmv - calc.prev_gmv) * 100.0 / NULLIF(calc.prev_gmv, 0))::numeric, 2)::FLOAT AS gmv_growth,
|
||
calc.orders,
|
||
ROUND(((calc.orders - calc.prev_orders) * 100.0 / NULLIF(calc.prev_orders, 0))::numeric, 2)::FLOAT AS order_growth,
|
||
ROUND(calc.conversion_rate::numeric, 2)::FLOAT AS conversion_rate,
|
||
ROUND((calc.conversion_rate - calc.prev_conversion_rate)::numeric, 2)::FLOAT AS conversion_growth,
|
||
ROUND(calc.avg_order_amount, 2) AS avg_order_amount,
|
||
ROUND(((calc.avg_order_amount - calc.prev_avg_order_amount) * 100.0 / NULLIF(calc.prev_avg_order_amount, 0))::numeric, 2)::FLOAT AS avg_order_growth
|
||
FROM calc;
|
||
END;
|
||
$$;
|
||
|
||
|
||
-- 2) 销售趋势(日维度:GMV + 订单数)
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_sales_trend(
|
||
p_start_date DATE,
|
||
p_end_date DATE
|
||
)
|
||
RETURNS TABLE (
|
||
date DATE,
|
||
gmv NUMERIC,
|
||
orders BIGINT
|
||
)
|
||
LANGUAGE sql
|
||
AS $$
|
||
WITH date_series AS (
|
||
SELECT generate_series(p_start_date, p_end_date, '1 day'::interval)::DATE AS date
|
||
)
|
||
SELECT
|
||
ds.date,
|
||
COALESCE(
|
||
SUM(CASE WHEN o.payment_status = 2 THEN COALESCE(NULLIF(o.paid_amount, 0), o.total_amount) ELSE 0 END),
|
||
0
|
||
) AS gmv,
|
||
COUNT(o.id)::BIGINT AS orders
|
||
FROM date_series ds
|
||
LEFT JOIN public.ml_orders o
|
||
ON o.created_at::DATE = ds.date
|
||
GROUP BY ds.date
|
||
ORDER BY ds.date;
|
||
$$;
|
||
|
||
|
||
-- 3) 热销商品 TOP(按销量:sum(quantity))
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_top_products(
|
||
p_start_date DATE,
|
||
p_end_date DATE,
|
||
p_limit INT DEFAULT 50
|
||
)
|
||
RETURNS TABLE (
|
||
id UUID,
|
||
name TEXT,
|
||
sales BIGINT
|
||
)
|
||
LANGUAGE sql
|
||
AS $$
|
||
SELECT
|
||
p.id,
|
||
p.name::TEXT,
|
||
COALESCE(SUM(oi.quantity), 0)::BIGINT AS sales
|
||
FROM public.ml_order_items oi
|
||
JOIN public.ml_orders o ON o.id = oi.order_id
|
||
JOIN public.ml_products p ON p.id = oi.product_id
|
||
WHERE o.created_at::DATE BETWEEN p_start_date AND p_end_date
|
||
AND o.payment_status = 2
|
||
GROUP BY p.id, p.name
|
||
ORDER BY sales DESC
|
||
LIMIT p_limit;
|
||
$$;
|
||
|
||
|
||
-- 4) 商家排行 TOP(按 GMV:支付 GMV)
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_top_merchants(
|
||
p_start_date DATE,
|
||
p_end_date DATE,
|
||
p_limit INT DEFAULT 50
|
||
)
|
||
RETURNS TABLE (
|
||
id UUID,
|
||
name TEXT,
|
||
sales NUMERIC
|
||
)
|
||
LANGUAGE sql
|
||
AS $$
|
||
SELECT
|
||
m.id,
|
||
COALESCE(NULLIF(m.username, ''), '未知商家')::TEXT AS name,
|
||
COALESCE(SUM(COALESCE(NULLIF(o.paid_amount, 0), o.total_amount)), 0) AS sales
|
||
FROM public.ml_orders o
|
||
JOIN public.ak_users m ON m.id = o.merchant_id
|
||
WHERE o.created_at::DATE BETWEEN p_start_date AND p_end_date
|
||
AND o.payment_status = 2
|
||
GROUP BY m.id, m.username
|
||
ORDER BY sales DESC
|
||
LIMIT p_limit;
|
||
$$;
|