Files
medical-mall/pages/mall/analytics/test/01_ml_analytics_rpcs_user.sql

233 lines
9.0 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =====================================================================================
-- 数据分析模块正式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;
$;