This commit is contained in:
2025-12-05 00:09:32 +08:00
parent bb3d22eabf
commit 3d1dbb3f97
214 changed files with 113060 additions and 1232 deletions
+322
View File
@@ -0,0 +1,322 @@
# 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)