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

323 lines
8.5 KiB
Markdown
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.
# PostgREST 嵌套查询故障排查
## 🐛 问题现象
PostgREST 查询中包含了嵌套语法,但返回的数据中**没有嵌套字段**:
```javascript
// 查询语法
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
...
}
```
---
## 🔍 问题诊断
### 检查外键约束
```sql
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`
```bash
psql -h <host> -U <username> -d <database> -f database/fix_duplicate_foreign_keys.sql
```
或者直接执行:
```sql
ALTER TABLE evaluation_points
DROP CONSTRAINT IF EXISTS fk_evaluation_points_group;
```
---
### 步骤 2: 验证外键约束(应该只有 2 个)
```sql
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: 重启应用并测试
```bash
npm run dev # 开发环境
# 或
npm run start:pm2:production:multi # 生产环境
```
访问评查点列表页面:`http://localhost:5173/rules/list`
**验证数据**
打开浏览器开发者工具 → Network → 找到 `/evaluation_points` 请求 → 查看响应
**预期返回**(应该包含嵌套字段):
```json
{
"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. 必须有外键约束
```sql
ALTER TABLE evaluation_points
ADD CONSTRAINT fk_evaluation_points_child_group
FOREIGN KEY (evaluation_point_groups_id)
REFERENCES evaluation_point_groups(id);
```
### 2. 外键名称必须唯一
- ❌ 一个列不能有多个外键
- ✅ 每个外键必须有唯一的名称
### 3. 查询语法必须指定外键名称
```javascript
// 格式:别名:表名!外键名(字段列表)
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 查询参数中,导致解析失败。
**错误示例**
```javascript
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
)
` // ❌ 包含换行符,导致解析失败
```
**正确写法**
```javascript
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: 外键名称不匹配
**症状**:嵌套字段不返回,无报错
**原因**:代码中使用的外键名称与数据库中的不一致
**示例**
```javascript
// 代码中使用
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 响应是否包含嵌套字段
---
## 🚀 快速修复命令
```bash
# 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
```
---
## 📚 参考资料
- [PostgREST 嵌套查询文档](https://postgrest.org/en/stable/references/api/resource_embedding.html)
- [PostgreSQL 外键约束文档](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-FK)