Files
medical-mall/mall_sql/migrations/ml_analytics_rpcs.sql

247 lines
11 KiB
PL/PgSQL
Raw Permalink 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_* 系列表, ak_users 表
-- 目标: 为数据分析模块提供核心数据接口,优先使用现有表,不新建表。
-- =====================================================================================
-- =====================================================================================
-- 1. 函数: rpc_analytics_user_kpis
-- 描述: 获取用户分析核心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. 函数: rpc_analytics_user_growth_trend
-- 描述: 获取用户增长与活跃趋势(日维度)
-- -------------------------------------------------------------------------------------
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. 函数: rpc_analytics_user_segments
-- 描述: 获取用户分群(基于新老客、复购的简单模型)
-- -------------------------------------------------------------------------------------
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
),
active_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
auip.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 active_users_in_period auip
JOIN user_orders_stats uos ON auip.user_id = uos.user_id
) segments
GROUP BY segment;
$$;
-- -------------------------------------------------------------------------------------
-- 4. 函数: rpc_analytics_traffic_sources
-- 描述: 获取流量来源分布 (基于注册来源)
-- 兼容性说明:部分环境的 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;
$;
-- =====================================================================================
-- 5. 完成提示
-- =====================================================================================
DO $$
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE '商城分析模块 RPC 函数创建/更新完成!';
RAISE NOTICE '=======================================================';
RAISE NOTICE '✓ RPC 函数创建/更新完成:';
RAISE NOTICE ' - rpc_analytics_user_kpis';
RAISE NOTICE ' - rpc_analytics_user_growth_trend';
RAISE NOTICE ' - rpc_analytics_user_segments';
RAISE NOTICE ' - rpc_analytics_traffic_sources';
RAISE NOTICE '=======================================================';
RAISE NOTICE '下一步:';
RAISE NOTICE '1. 在数据库中执行此脚本。';
RAISE NOTICE '2. 确认前端 user-analysis.uvue 页面已调用这些 RPC。';
RAISE NOTICE '3. 如果缺少活跃数据,请确保 ml_browse_history 表有数据写入。';
RAISE NOTICE '=======================================================';
END $$;