Files
medical-mall/pages/mall/analytics/test/07_custom_report_rpcs.sql
2026-01-30 16:17:13 +08:00

406 lines
11 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.
-- ============================================
-- 07_custom_report_rpcs.sql
-- 自定义报表创建与管理 RPC 定义
-- ============================================
-- 目标:
-- 1) 为 `pages/mall/analytics/custom-report.uvue` 提供安全的数据服务
-- 2) 确保用户记录存在,解决外键约束问题
-- 3) 创建自定义报表并生成初始数据metrics + rows
--
-- 依赖前置脚本:
-- - 01_create_tables.sqlusers 表)
-- - ANALYTICS_DB_SCHEMA.sqlanalytics_reports / analytics_report_metrics / analytics_report_rows
--
-- 使用说明:
-- - 前端通过 supabase-js / UTS 调用 `rpc()` 访问本文件中的函数
-- - 所有函数仅对 `authenticated` 角色开放执行权限
-- ============================================
-- --------------------------------------------
-- 1. 确保用户记录存在Upsert User
-- --------------------------------------------
-- 说明:
-- - 如果 users 表中不存在当前用户记录,则插入
-- - 如果已存在,则更新最后登录时间等信息
-- - 解决 analytics_reports.owner_user_id 外键约束问题
CREATE OR REPLACE FUNCTION public.rpc_ensure_user_record(
p_user_id uuid,
p_email text DEFAULT NULL,
p_phone text DEFAULT NULL,
p_nickname text DEFAULT NULL
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id uuid;
BEGIN
-- 检查用户是否存在
SELECT id INTO v_user_id
FROM public.users
WHERE id = p_user_id;
IF v_user_id IS NULL THEN
-- 用户不存在,插入新记录
INSERT INTO public.users (
id,
email,
phone,
nickname,
last_login_at,
created_at,
updated_at
) VALUES (
p_user_id,
p_email,
p_phone,
COALESCE(p_nickname, COALESCE(split_part(p_email, '@', 1), '用户')),
NOW(),
NOW(),
NOW()
)
RETURNING id INTO v_user_id;
ELSE
-- 用户已存在,更新信息
UPDATE public.users
SET
email = COALESCE(p_email, email),
phone = COALESCE(p_phone, phone),
nickname = COALESCE(p_nickname, nickname),
last_login_at = NOW(),
updated_at = NOW()
WHERE id = p_user_id;
v_user_id := p_user_id;
END IF;
RETURN v_user_id;
END;
$$;
REVOKE ALL ON FUNCTION public.rpc_ensure_user_record(uuid,text,text,text) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_ensure_user_record(uuid,text,text,text) TO authenticated;
-- --------------------------------------------
-- 2. 创建自定义报表(含初始数据生成)
-- --------------------------------------------
-- 说明:
-- - 创建 analytics_reports 记录
-- - 根据 period 和选中的指标,生成 analytics_report_metrics
-- - 根据 period 聚合 orders 数据,生成 analytics_report_rows
CREATE OR REPLACE FUNCTION public.rpc_create_custom_report(
p_title text,
p_description text DEFAULT '',
p_period text DEFAULT '30d', -- 7d/30d/90d/1y
p_metrics text[] DEFAULT ARRAY['gmv', 'orders', 'users'], -- 选中的指标列表
p_chart_type text DEFAULT 'line' -- 图表类型(暂不存储,仅用于后续扩展)
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id uuid;
v_report_id uuid;
v_start_date date;
v_end_date date;
v_metric_key text;
v_metric_label text;
v_metric_value numeric;
v_total_gmv numeric := 0;
v_total_orders integer := 0;
v_total_users integer := 0;
v_avg_order_amount numeric := 0;
BEGIN
-- 1. 获取当前用户 ID
v_user_id := auth.uid();
IF v_user_id IS NULL THEN
RAISE EXCEPTION '用户未登录';
END IF;
-- 2. 确保用户记录存在
PERFORM public.rpc_ensure_user_record(
v_user_id,
NULL, -- email 从 auth.users 获取,这里不传
NULL, -- phone 从 auth.users 获取,这里不传
NULL -- nickname 从 auth.users 获取,这里不传
);
-- 3. 计算时间范围
v_end_date := CURRENT_DATE;
CASE p_period
WHEN '7d' THEN v_start_date := v_end_date - INTERVAL '7 days';
WHEN '30d' THEN v_start_date := v_end_date - INTERVAL '30 days';
WHEN '90d' THEN v_start_date := v_end_date - INTERVAL '90 days';
WHEN '1y' THEN v_start_date := v_end_date - INTERVAL '1 year';
ELSE v_start_date := v_end_date - INTERVAL '30 days'; -- 默认 30 天
END CASE;
-- 4. 创建报表记录
INSERT INTO public.analytics_reports (
owner_user_id,
title,
description,
type,
period,
date_start,
date_end,
status,
generated_at,
created_at,
updated_at
) VALUES (
v_user_id,
p_title,
p_description,
'custom',
p_period,
v_start_date,
v_end_date,
'ready',
NOW(),
NOW(),
NOW()
)
RETURNING id INTO v_report_id;
-- 5. 聚合订单数据,计算总指标
SELECT
COALESCE(SUM(o.total_amount), 0),
COUNT(DISTINCT o.id),
COUNT(DISTINCT o.user_id),
CASE
WHEN COUNT(DISTINCT o.id) > 0
THEN COALESCE(SUM(o.total_amount), 0) / COUNT(DISTINCT o.id)
ELSE 0
END
INTO v_total_gmv, v_total_orders, v_total_users, v_avg_order_amount
FROM public.orders o
WHERE o.created_at >= v_start_date
AND o.created_at <= v_end_date
AND o.status = 2; -- 已完成订单
-- 6. 生成核心指标analytics_report_metrics
-- GMV
IF 'gmv' = ANY(p_metrics) THEN
INSERT INTO public.analytics_report_metrics (
report_id,
metric_key,
metric_label,
metric_value_num,
format,
icon,
color
) VALUES (
v_report_id,
'gmv_total',
'总GMV',
v_total_gmv,
'currency',
'💰',
'#4caf50'
);
END IF;
-- 订单数
IF 'orders' = ANY(p_metrics) THEN
INSERT INTO public.analytics_report_metrics (
report_id,
metric_key,
metric_label,
metric_value_num,
format,
icon,
color
) VALUES (
v_report_id,
'orders_total',
'总订单数',
v_total_orders,
'number',
'📦',
'#2196f3'
);
END IF;
-- 用户数
IF 'users' = ANY(p_metrics) THEN
INSERT INTO public.analytics_report_metrics (
report_id,
metric_key,
metric_label,
metric_value_num,
format,
icon,
color
) VALUES (
v_report_id,
'users_total',
'下单用户数',
v_total_users,
'number',
'👥',
'#ff9800'
);
END IF;
-- 客单价
IF 'avg_order' = ANY(p_metrics) THEN
INSERT INTO public.analytics_report_metrics (
report_id,
metric_key,
metric_label,
metric_value_num,
format,
icon,
color
) VALUES (
v_report_id,
'avg_order_amount',
'客单价',
v_avg_order_amount,
'currency',
'💵',
'#9c27b0'
);
END IF;
-- 7. 生成明细行analytics_report_rows- 按天聚合
INSERT INTO public.analytics_report_rows (
report_id,
row_date,
gmv,
orders,
users,
avg_order_amount
)
SELECT
v_report_id,
o.created_at::date AS row_date,
COALESCE(SUM(o.total_amount), 0) AS gmv,
COUNT(DISTINCT o.id) AS orders,
COUNT(DISTINCT o.user_id) AS users,
CASE
WHEN COUNT(DISTINCT o.id) > 0
THEN COALESCE(SUM(o.total_amount), 0) / COUNT(DISTINCT o.id)
ELSE 0
END AS avg_order_amount
FROM public.orders o
WHERE o.created_at >= v_start_date
AND o.created_at <= v_end_date
AND o.status = 2
GROUP BY o.created_at::date
ORDER BY o.created_at::date;
RETURN v_report_id;
END;
$$;
REVOKE ALL ON FUNCTION public.rpc_create_custom_report(text,text,text,text[],text) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_create_custom_report(text,text,text,text[],text) TO authenticated;
-- --------------------------------------------
-- 3. 更新自定义报表(仅更新基本信息)
-- --------------------------------------------
-- 说明:
-- - 更新报表的标题、描述、周期
-- - 不重新生成数据(如需重新生成,删除后重建)
CREATE OR REPLACE FUNCTION public.rpc_update_custom_report(
p_report_id uuid,
p_title text,
p_description text DEFAULT NULL,
p_period text DEFAULT NULL
)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id uuid;
BEGIN
-- 1. 获取当前用户 ID
v_user_id := auth.uid();
IF v_user_id IS NULL THEN
RAISE EXCEPTION '用户未登录';
END IF;
-- 2. 更新报表(仅限所有者)
UPDATE public.analytics_reports
SET
title = p_title,
description = COALESCE(p_description, description),
period = COALESCE(p_period, period),
updated_at = NOW()
WHERE id = p_report_id
AND owner_user_id = v_user_id;
-- FOUND 是 PostgreSQL 的特殊变量UPDATE 后自动设置
IF NOT FOUND THEN
RAISE EXCEPTION '报表不存在或无权限修改';
END IF;
RETURN true;
END;
$$;
REVOKE ALL ON FUNCTION public.rpc_update_custom_report(uuid,text,text,text) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_update_custom_report(uuid,text,text,text) TO authenticated;
-- --------------------------------------------
-- 4. 删除自定义报表(级联删除相关数据)
-- --------------------------------------------
-- 说明:
-- - 删除报表记录CASCADE 会自动删除 metrics 和 rows
-- - 仅允许所有者删除
CREATE OR REPLACE FUNCTION public.rpc_delete_custom_report(
p_report_id uuid
)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id uuid;
BEGIN
-- 1. 获取当前用户 ID
v_user_id := auth.uid();
IF v_user_id IS NULL THEN
RAISE EXCEPTION '用户未登录';
END IF;
-- 2. 删除报表仅限所有者CASCADE 会自动删除 metrics 和 rows
DELETE FROM public.analytics_reports
WHERE id = p_report_id
AND owner_user_id = v_user_id
AND type = 'custom';
-- FOUND 是 PostgreSQL 的特殊变量DELETE 后自动设置
IF NOT FOUND THEN
RAISE EXCEPTION '报表不存在或无权限删除';
END IF;
RETURN true;
END;
$$;
REVOKE ALL ON FUNCTION public.rpc_delete_custom_report(uuid) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delete_custom_report(uuid) TO authenticated;
-- ============================================
-- 完成提示
-- ============================================
DO $$
BEGIN
RAISE NOTICE 'Custom report RPCs created successfully.';
RAISE NOTICE 'Functions:';
RAISE NOTICE ' - rpc_ensure_user_record(uuid, text, text, text)';
RAISE NOTICE ' - rpc_create_custom_report(text, text, text, text[], text)';
RAISE NOTICE ' - rpc_update_custom_report(uuid, text, text, text)';
RAISE NOTICE ' - rpc_delete_custom_report(uuid)';
END $$;