233 lines
9.0 KiB
PL/PgSQL
233 lines
9.0 KiB
PL/PgSQL
-- =====================================================================================
|
||
-- 数据分析模块(正式RPC,主库 ml_* 口径)
|
||
-- 文件: 01_ml_analytics_rpcs_user.sql
|
||
-- 主题: 用户分析(KPI/趋势/分群/渠道)
|
||
-- 依赖: public.ak_users, public.ml_orders, public.ml_browse_history
|
||
-- =====================================================================================
|
||
|
||
-- 1) 用户分析核心 KPI(含上期对比)
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_user_kpis(
|
||
p_start_date DATE,
|
||
p_end_date DATE
|
||
)
|
||
RETURNS TABLE (
|
||
total_users BIGINT,
|
||
user_growth FLOAT,
|
||
new_users BIGINT,
|
||
new_user_growth FLOAT,
|
||
active_users BIGINT,
|
||
active_growth FLOAT,
|
||
ordering_users BIGINT,
|
||
ordering_growth FLOAT,
|
||
paid_users BIGINT,
|
||
paid_growth FLOAT,
|
||
new_user_conversion_rate FLOAT,
|
||
repurchase_rate FLOAT,
|
||
repurchase_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
|
||
current_period AS (
|
||
SELECT
|
||
(SELECT COUNT(*) FROM public.ak_users WHERE created_at <= p_end_date::timestamp) AS total_users,
|
||
COUNT(DISTINCT CASE WHEN u.created_at::DATE BETWEEN p_start_date AND p_end_date THEN u.id END)::BIGINT AS new_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) AS active_users,
|
||
(SELECT COUNT(DISTINCT o.user_id) FROM public.ml_orders o WHERE o.created_at::DATE BETWEEN p_start_date AND p_end_date) AS ordering_users,
|
||
(SELECT COUNT(DISTINCT o.user_id) FROM public.ml_orders o WHERE o.created_at::DATE BETWEEN p_start_date AND p_end_date AND o.payment_status = 2) AS paid_users,
|
||
(
|
||
SELECT COUNT(DISTINCT o.user_id) * 100.0 / NULLIF(COUNT(DISTINCT u_new.id), 0)
|
||
FROM public.ak_users u_new
|
||
LEFT JOIN public.ml_orders o
|
||
ON u_new.id = o.user_id
|
||
AND o.payment_status = 2
|
||
AND o.created_at::DATE BETWEEN p_start_date AND p_end_date
|
||
WHERE u_new.created_at::DATE BETWEEN p_start_date AND p_end_date
|
||
) AS new_user_conversion_rate,
|
||
(
|
||
SELECT
|
||
COUNT(DISTINCT CASE WHEN user_orders.order_count > 1 THEN user_orders.user_id END) * 100.0
|
||
/ NULLIF(COUNT(DISTINCT user_orders.user_id), 0)
|
||
FROM (
|
||
SELECT user_id, COUNT(id) as order_count
|
||
FROM public.ml_orders
|
||
WHERE created_at::DATE <= p_end_date AND payment_status = 2
|
||
GROUP BY user_id
|
||
) user_orders
|
||
WHERE user_orders.user_id IN (
|
||
SELECT user_id
|
||
FROM public.ml_orders
|
||
WHERE created_at::DATE BETWEEN p_start_date AND p_end_date
|
||
AND payment_status = 2
|
||
)
|
||
) AS repurchase_rate
|
||
FROM public.ak_users u
|
||
),
|
||
previous_period AS (
|
||
SELECT
|
||
(SELECT COUNT(*) FROM public.ak_users WHERE created_at <= prev_end_date::timestamp) AS total_users,
|
||
COUNT(DISTINCT CASE WHEN u.created_at::DATE BETWEEN prev_start_date AND prev_end_date THEN u.id END)::BIGINT AS new_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) AS active_users,
|
||
(SELECT COUNT(DISTINCT o.user_id) FROM public.ml_orders o WHERE o.created_at::DATE BETWEEN prev_start_date AND prev_end_date) AS ordering_users,
|
||
(SELECT COUNT(DISTINCT o.user_id) FROM public.ml_orders o WHERE o.created_at::DATE BETWEEN prev_start_date AND prev_end_date AND o.payment_status = 2) AS paid_users,
|
||
(
|
||
SELECT
|
||
COUNT(DISTINCT CASE WHEN user_orders.order_count > 1 THEN user_orders.user_id END) * 100.0
|
||
/ NULLIF(COUNT(DISTINCT user_orders.user_id), 0)
|
||
FROM (
|
||
SELECT user_id, COUNT(id) as order_count
|
||
FROM public.ml_orders
|
||
WHERE created_at::DATE <= prev_end_date AND payment_status = 2
|
||
GROUP BY user_id
|
||
) user_orders
|
||
WHERE user_orders.user_id IN (
|
||
SELECT user_id
|
||
FROM public.ml_orders
|
||
WHERE created_at::DATE BETWEEN prev_start_date AND prev_end_date
|
||
AND payment_status = 2
|
||
)
|
||
) AS repurchase_rate
|
||
FROM public.ak_users u
|
||
)
|
||
SELECT
|
||
cp.total_users,
|
||
ROUND(((cp.total_users - pp.total_users) * 100.0 / NULLIF(pp.total_users, 0))::numeric, 2)::FLOAT AS user_growth,
|
||
cp.new_users,
|
||
ROUND(((cp.new_users - pp.new_users) * 100.0 / NULLIF(pp.new_users, 0))::numeric, 2)::FLOAT AS new_user_growth,
|
||
cp.active_users,
|
||
ROUND(((cp.active_users - pp.active_users) * 100.0 / NULLIF(pp.active_users, 0))::numeric, 2)::FLOAT AS active_growth,
|
||
cp.ordering_users,
|
||
ROUND(((cp.ordering_users - pp.ordering_users) * 100.0 / NULLIF(pp.ordering_users, 0))::numeric, 2)::FLOAT AS ordering_growth,
|
||
cp.paid_users,
|
||
ROUND(((cp.paid_users - pp.paid_users) * 100.0 / NULLIF(pp.paid_users, 0))::numeric, 2)::FLOAT AS paid_growth,
|
||
ROUND(cp.new_user_conversion_rate::numeric, 2)::FLOAT,
|
||
ROUND(cp.repurchase_rate::numeric, 2)::FLOAT AS repurchase_rate,
|
||
ROUND((COALESCE(cp.repurchase_rate, 0) - COALESCE(pp.repurchase_rate, 0))::numeric, 2)::FLOAT AS repurchase_growth
|
||
FROM current_period cp, previous_period pp;
|
||
END;
|
||
$$;
|
||
|
||
|
||
-- 2) 用户增长与活跃趋势(日维度)
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_user_growth_trend(
|
||
p_start_date DATE,
|
||
p_end_date DATE
|
||
)
|
||
RETURNS TABLE (
|
||
date DATE,
|
||
new_users BIGINT,
|
||
active_users 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,
|
||
(SELECT COUNT(u.id) FROM public.ak_users u WHERE u.created_at::DATE = ds.date)::BIGINT AS new_users,
|
||
(SELECT COUNT(DISTINCT bh.user_id) FROM public.ml_browse_history bh WHERE bh.created_at::DATE = ds.date)::BIGINT AS active_users
|
||
FROM date_series ds
|
||
ORDER BY ds.date;
|
||
$$;
|
||
|
||
|
||
-- 3) 用户分群(简版:新客/复购/老客;以支付订单为准)
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_user_segments(
|
||
p_start_date DATE,
|
||
p_end_date DATE
|
||
)
|
||
RETURNS TABLE (
|
||
name TEXT,
|
||
value BIGINT
|
||
)
|
||
LANGUAGE sql
|
||
AS $$
|
||
WITH user_orders_stats AS (
|
||
SELECT
|
||
user_id,
|
||
MIN(created_at) as first_order_time,
|
||
COUNT(id) as total_orders
|
||
FROM public.ml_orders
|
||
WHERE payment_status = 2
|
||
GROUP BY user_id
|
||
),
|
||
users_in_period AS (
|
||
SELECT DISTINCT user_id
|
||
FROM public.ml_orders
|
||
WHERE created_at::DATE BETWEEN p_start_date AND p_end_date
|
||
)
|
||
SELECT
|
||
segment as name,
|
||
COUNT(user_id) as value
|
||
FROM (
|
||
SELECT
|
||
uip.user_id,
|
||
CASE
|
||
WHEN uos.first_order_time::DATE BETWEEN p_start_date AND p_end_date THEN '新客'
|
||
WHEN uos.total_orders > 1 THEN '复购客户'
|
||
ELSE '老客'
|
||
END as segment
|
||
FROM users_in_period uip
|
||
JOIN user_orders_stats uos ON uip.user_id = uos.user_id
|
||
) segments
|
||
GROUP BY segment;
|
||
$$;
|
||
|
||
|
||
-- 4) 渠道来源(按注册来源,统计周期内新增用户来源)
|
||
-- 兼容性说明:部分环境的 ak_users 可能不存在 registration_source 字段。
|
||
-- 为避免 RPC 报错导致首页整体加载失败,这里做“字段存在则分组统计,不存在则全部归为未知”的兼容。
|
||
CREATE OR REPLACE FUNCTION public.rpc_analytics_traffic_sources(
|
||
p_start_date DATE,
|
||
p_end_date DATE
|
||
)
|
||
RETURNS TABLE (
|
||
name TEXT,
|
||
value BIGINT
|
||
)
|
||
LANGUAGE plpgsql
|
||
AS $
|
||
DECLARE
|
||
has_registration_source BOOLEAN := FALSE;
|
||
BEGIN
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'public'
|
||
AND table_name = 'ak_users'
|
||
AND column_name = 'registration_source'
|
||
) INTO has_registration_source;
|
||
|
||
IF has_registration_source THEN
|
||
RETURN QUERY
|
||
EXECUTE '
|
||
SELECT
|
||
COALESCE(registration_source, ''未知'') AS name,
|
||
COUNT(id)::BIGINT AS value
|
||
FROM public.ak_users
|
||
WHERE created_at::DATE BETWEEN $1 AND $2
|
||
GROUP BY name
|
||
ORDER BY value DESC
|
||
'
|
||
USING p_start_date, p_end_date;
|
||
ELSE
|
||
RETURN QUERY
|
||
SELECT '未知'::TEXT AS name,
|
||
COUNT(id)::BIGINT AS value
|
||
FROM public.ak_users
|
||
WHERE created_at::DATE BETWEEN p_start_date AND p_end_date;
|
||
END IF;
|
||
END;
|
||
$;
|