Files
leaudit-platform-frontend/docs/PostgREST嵌套查询故障排查.md
T
2025-12-05 00:09:32 +08:00

8.5 KiB
Raw Blame History

PostgREST 嵌套查询故障排查

🐛 问题现象

PostgREST 查询中包含了嵌套语法,但返回的数据中没有嵌套字段

// 查询语法
select: `
  id, code, name, ...,
  child_group:evaluation_point_groups!fk_evaluation_points_child_group(id, name),
  parent_group:evaluation_point_groups!fk_evaluation_points_parent_group(id, name)
`

// 实际返回(缺少 child_group 和 parent_group
{
  "id": 670,
  "code": "04formcheck-vlm-cz",
  "name": "表格抽取与评查(多模态)",
  "evaluation_point_groups_id": 61,
  "evaluation_point_groups_pid": 60,
  // ❌ 没有 child_group
  // ❌ 没有 parent_group
  ...
}

🔍 问题诊断

检查外键约束

SELECT
    tc.constraint_name AS "约束名",
    kcu.column_name AS "列名",
    ccu.table_name AS "引用表",
    ccu.column_name AS "引用列"
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
    ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
    ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
    AND tc.table_name = 'evaluation_points'
    AND kcu.column_name IN ('evaluation_point_groups_id', 'evaluation_point_groups_pid')
ORDER BY kcu.column_name;

发现问题

约束名                                    | 列名
-----------------------------------------|-------------------------------
fk_evaluation_points_group               | evaluation_point_groups_id   ← 旧的
fk_evaluation_points_child_group         | evaluation_point_groups_id   ← 新的
fk_evaluation_points_parent_group        | evaluation_point_groups_pid

根本原因

  • evaluation_point_groups_id 列有两个外键约束
  • PostgREST 不知道使用哪个,导致嵌套查询失败
  • 只返回主表字段,忽略嵌套部分

解决方案

步骤 1: 删除旧的外键约束

执行脚本:database/fix_duplicate_foreign_keys.sql

psql -h <host> -U <username> -d <database> -f database/fix_duplicate_foreign_keys.sql

或者直接执行:

ALTER TABLE evaluation_points
DROP CONSTRAINT IF EXISTS fk_evaluation_points_group;

步骤 2: 验证外键约束(应该只有 2 个)

SELECT
    tc.constraint_name AS "约束名",
    kcu.column_name AS "列名"
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
    ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
    AND tc.table_name = 'evaluation_points'
    AND kcu.column_name IN ('evaluation_point_groups_id', 'evaluation_point_groups_pid')
ORDER BY kcu.column_name;

预期结果(只有 2 个):

约束名                                    | 列名
-----------------------------------------|-------------------------------
fk_evaluation_points_child_group         | evaluation_point_groups_id    ✅
fk_evaluation_points_parent_group        | evaluation_point_groups_pid   ✅

步骤 3: 重启应用并测试

npm run dev  # 开发环境
# 或
npm run start:pm2:production:multi  # 生产环境

访问评查点列表页面:http://localhost:5173/rules/list

验证数据: 打开浏览器开发者工具 → Network → 找到 /evaluation_points 请求 → 查看响应

预期返回(应该包含嵌套字段):

{
  "id": 670,
  "code": "04formcheck-vlm-cz",
  "name": "表格抽取与评查(多模态)",
  "evaluation_point_groups_id": 61,
  "evaluation_point_groups_pid": 60,
  "child_group": {                          应该有
    "id": 61,
    "name": "多模态评查"
  },
  "parent_group": {                         应该有
    "id": 60,
    "name": "卷宗类"
  }
}

📊 为什么会有重复的外键?

原因分析

在之前的实现中,我们创建了外键约束的顺序:

  1. 第一次:执行 database/add_foreign_keys.sql

    • 创建了 fk_evaluation_points_group
  2. 第二次:执行 database/add_foreign_keys_simplified.sql

    • 创建了 fk_evaluation_points_child_group
    • 创建了 fk_evaluation_points_parent_group
    • 但没有删除旧的 fk_evaluation_points_group

结果:同一个列有两个外键约束!


🎓 PostgREST 嵌套查询的要求

1. 必须有外键约束

ALTER TABLE evaluation_points
ADD CONSTRAINT fk_evaluation_points_child_group
FOREIGN KEY (evaluation_point_groups_id)
REFERENCES evaluation_point_groups(id);

2. 外键名称必须唯一

  • 一个列不能有多个外键
  • 每个外键必须有唯一的名称

3. 查询语法必须指定外键名称

// 格式:别名:表名!外键名(字段列表)
child_group:evaluation_point_groups!fk_evaluation_points_child_group(id, name)

4. 外键名称必须匹配

  • 查询中的外键名:fk_evaluation_points_child_group
  • 数据库中的外键名:fk_evaluation_points_child_group
  • 必须完全一致

🔍 其他可能的问题

问题 1: select 参数包含换行符 ⚠️ 最常见

症状

  • 单独查询一个嵌套字段成功,但同时查询多个嵌套字段时全部失败
  • PostgREST 报错 PGRST100 语法解析错误
  • 错误信息包含 unexpected "\\r"unexpected "\\n"

原因PostgREST 的 select 参数不支持多行字符串。模板字符串中的换行符会被包含在 URL 查询参数中,导致解析失败。

错误示例

select: `
  id,
  name,
  child_group:evaluation_point_groups!fk_evaluation_points_group(
    id,
    name
  ),
  parent_group:evaluation_point_groups!fk_evaluation_points_parent_group(
    id,
    name
  )
`  // ❌ 包含换行符,导致解析失败

正确写法

select: `
  id,
  name,
  child_group:evaluation_point_groups!fk_evaluation_points_group(id,name),
  parent_group:evaluation_point_groups!fk_evaluation_points_parent_group(id,name)
`.replace(/\s+/g, ' ').trim()  // ✅ 转换为单行字符串

解决方案

  1. 嵌套查询的字段列表写成单行:(id,name) 而不是多行
  2. 使用 .replace(/\s+/g, ' ').trim() 将所有换行符和多余空格转换为单个空格
  3. 或者直接写成完整的单行字符串(牺牲可读性)

问题 2: 外键不存在

症状PostgREST 报错 could not find foreign key relationship

解决:创建外键约束

问题 3: 外键名称不匹配

症状:嵌套字段不返回,无报错

原因:代码中使用的外键名称与数据库中的不一致

示例

// 代码中使用
child_group:evaluation_point_groups!fk_evaluation_points_child_group(...)

// 但数据库中实际是
fk_evaluation_points_group   名称不匹配

解决

  1. 检查数据库中的实际外键名称
  2. 重命名外键约束或修改代码
  3. 推荐:重命名外键以匹配代码(见 database/rename_foreign_key.sql

问题 3: 引用的表不存在或无权限

症状PostgREST 报错 permission denied

解决:检查表权限

问题 4: 数据中关联ID为 NULL

症状:部分记录的嵌套字段为 null

解决:正常现象,表示没有关联数据


📋 完整检查清单

  • 检查 select 字符串是否包含换行符(最重要!)
    • 嵌套查询字段列表应为单行:(id,name) 不是 (\\n id,\\n name\\n)
    • 使用 .replace(/\s+/g, ' ').trim() 清理字符串
  • 检查外键约束是否存在
  • 确认每个列只有一个外键约束
  • 验证外键名称与查询语法一致
  • 检查引用表是否存在
  • 确认数据库用户有查询权限
  • 删除旧的或重复的外键约束
  • 重启应用
  • 测试 API 响应是否包含嵌套字段

🚀 快速修复命令

# 1. 删除重复的外键
psql -h <host> -U <username> -d <database> -c "
  ALTER TABLE evaluation_points
  DROP CONSTRAINT IF EXISTS fk_evaluation_points_group;
"

# 2. 验证结果
psql -h <host> -U <username> -d <database> -c "
  SELECT constraint_name, column_name
  FROM information_schema.key_column_usage
  WHERE table_name = 'evaluation_points'
    AND column_name IN ('evaluation_point_groups_id', 'evaluation_point_groups_pid')
  ORDER BY column_name;
"

# 3. 重启应用
npm run dev

📚 参考资料