-- ===================================================================================== -- 数据分析模块(正式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) 渠道来源(按注册来源,统计周期内新增用户来源) CREATE OR REPLACE FUNCTION public.rpc_analytics_traffic_sources( p_start_date DATE, p_end_date DATE ) RETURNS TABLE ( name TEXT, value BIGINT ) LANGUAGE sql AS $$ SELECT COALESCE(registration_source, '未知') AS name, COUNT(id)::BIGINT AS value FROM public.ak_users WHERE created_at::DATE BETWEEN p_start_date AND p_end_date GROUP BY name ORDER BY value DESC; $$;