# 21000 — 基数冲突（cardinality_violation）

> PostgreSQL SQLSTATE 21000：基数冲突的来源与诊断参考。
---

# 21000 — 基数冲突

## 速览 {#at-a-glance}

`21000` 表示操作得到的行数不符合基数契约。最常见的是标量子查询返回多行；它与 `23505` 不同，不要求存在唯一索引冲突。

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `21000` |
| 条件名 | `cardinality_violation` |
| 状态 | `有效` |
| 已知存在于 | `7.4` |
| 锁定快照 | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| 宏 | `ERRCODE_CARDINALITY_VIOLATION` |
| 别名 | `—` |

<!-- source facts: data/errcodes/21000.json -->
<!-- END SQLSTATE FACTS -->

## 含义 {#meaning}

把标量子查询当作表达式时，最多只能返回一行；执行器看到第二行就报告 `21000`，零行则产生 `NULL`。同一错误也用于命令级基数冲突：`ON CONFLICT DO UPDATE` 的多个候选行可能再次命中同一目标行，`MERGE` 的多个源行也可能匹配同一目标行；这些路径有各自的报文和提示。

## 诊断 {#diagnosis}

先保存 `sqlstate`、`message_primary`、`hint` 和语句上下文。根据业务键增加确定性谓词，或在确实要把多行合成一个值时使用聚合；不要随意加 `LIMIT 1`，否则可能静默选择任意行。对 `ON CONFLICT`，按仲裁索引或唯一键去重候选源行；对 `MERGE`，保证源到目标的匹配对每个目标至多一行。应检查实际源行和键映射，不能把它泛化成普通重复键错误。

## 处理 {#response}

显式事务中先回滚失败事务，再执行修正后的完整操作。`ON CONFLICT` 或 `MERGE` 的多行来源必须先确定基数；原样重放同一批数据仍会重复触发确定性的冲突。自动提交下本案例错误后连接仍为 `IDLE`，这不能替代外层事务或 PL/pgSQL 处理器的边界。

## 实测诊断 {#messages}

`18.6 (Homebrew) / latest`：SQLSTATE `21000`；primary `more than one row returned by a subquery used as an expression`；status_after_error `IDLE`。
`10.21 (Debian 10.21-1.pgdg90+1) / pg10`：SQLSTATE `21000`；primary `more than one row returned by a subquery used as an expression`；status_after_error `IDLE`。

## 代表案例 {#case}

运行器从 `verify/cases/21000/snippets.json`（SHA-256 `6d820e94518ffca97f407956fdc2df104d47b14263d3117ff59dbc4409774dc2`）读取下列片段，并为临时 schema 替换表名；完整 setup、断言与清理见 [案例导出](../../data/cases/21000.json)。

<!-- BEGIN SQLSTATE SNIPPET: scalar_subquery_cardinality -->
```sql
-- create
CREATE TABLE source_rows(id integer PRIMARY KEY);
-- seed
INSERT INTO source_rows VALUES (1), (2);
-- trigger
SELECT (SELECT id FROM source_rows ORDER BY id) AS only_id;
-- valid
SELECT (SELECT id FROM source_rows WHERE id = 1) AS only_id;
```
<!-- END SQLSTATE SNIPPET -->

本案例对应的 SQLSTATE、诊断、事务状态和修复断言均来自上述共享 registry；[结构化证据](../../data/evidence/21000.json) · [案例导出](../../data/cases/21000.json)。

作者证据 ID：`identity`, `scalar-subquery`, `dml-conflict`, `runtime`。选定运行记录：`runtime.21000-batch1-latest-20260909.latest`, `runtime.21000-batch1-pg10-20260909.pg10`。

## 版本与边界 {#versions}

锁定目录在 `7.4` 已观察到该条件，并在列出的 9.0–18.6 正式快照中均存在。固定源码证据确认 18.6 的标量子查询、`ON CONFLICT` 和 `MERGE` 路径；选定运行只覆盖 18.6 与 10.21 的标量子查询。

## 相关 {#related}

对比 [23505 唯一约束冲突](../23505/)、[23503 外键冲突](../23503/) 和 [23000 完整性约束总类](../23000/)。

## 来源 {#sources}

- [`src.errcodes.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt) (SHA-256 `6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba`)
- `src.calls.REL_18_6` (SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf`)
- [`src.nodeSubplan.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/executor/nodeSubplan.c#L296-L298) (SHA-256 `c356a9812691f875974c1f476efa987015ba19e70a4be833a43b692f3554dc47`)
- [`src.nodeModifyTable.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/executor/nodeModifyTable.c#L2804-L2809) (SHA-256 `0fc3cb180b3443f216142954e43d8ed5f84713d75094ce8cd2785ad8f604bd68`)
- [`doc.syntax.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/doc/src/sgml/syntax.sgml#L2232-L2238) (SHA-256 `449b0c500fccca068d0f8a1db2a5ebb5430eb83e33f12c6cb310a01a7437b635`) · [官方文档](https://www.postgresql.org/docs/18/functions-subquery.html)
