# 2202E — array_subscript_error（数组下标错误）

> PostgreSQL 使用 SQLSTATE 2202E 表示数组下标或形状错误。应区分越界读取返回 NULL 的情况，以及会校验数组维度的赋值、切片、构造和拼接路径。
---

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

`2202E` 是类别 22 `Data Exception` 中的 `array_subscript_error` 条件。目录同时保留 `ERRCODE_ARRAY_ELEMENT_ERROR` 这一兼容别名，并使用 `ERRCODE_ARRAY_SUBSCRIPT_ERROR` 作为带条件名的宏。两个宏编码的是同一个 SQLSTATE。

不要把所有看起来越界的表达式都当成错误。PostgreSQL 文档明确说明，读取当前边界之外的数组下标会返回 `NULL`；提供错误数量的下标同样返回 `NULL`。数组切片还有独立的历史规则：完全位于边界外的切片可以产生空的零维数组，部分重叠的切片则缩减为重叠部分。

当其他路径校验形状或下标并拒绝它时才会抛出 `2202E`。核心源码中的这类路径包括数组拼接或构造时维度不兼容、无效切片边界，以及部分带下标赋值检查。诊断时，操作本身和下标数值同样重要。

可执行的代表性案例是 `incompatible_array_dimensions`。它在 PostgreSQL 18.6 和 10.21 上均通过：不兼容的拼接抛出 `2202E`，随后同一自动提交连接保持 `IDLE`，并成功执行有效的后续拼接。下面的 SQL 摘录是共享注册表中的完整有序语句对；测试执行器仍是建表和清理的唯一来源。

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

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `2202E` |
| 条件名 | `array_subscript_error` |
| 状态 | `有效` |
| 已知存在于 | `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_ARRAY_ELEMENT_ERROR, ERRCODE_ARRAY_SUBSCRIPT_ERROR` |
| 别名 | `ERRCODE_ARRAY_ELEMENT_ERROR` |

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

## 含义与触发路径 {#meaning}

18.6 目录中的定义是类别 22 下的 `2202E E ERRCODE_ARRAY_SUBSCRIPT_ERROR array_subscript_error`。前面的别名行是 `2202E E ERRCODE_ARRAY_ELEMENT_ERROR`；源码注释解释 SQL99 的 “array element error” 实际上就是数组下标错误。因此，仍使用别名的代码指向同一 SQLSTATE，而不是另一种条件。

数组具有秩、每个维度的长度以及下界。PostgreSQL 不要求数组从 1 开始，所以诊断时应读取实际下界，而不是作这个假设。文档中的 `array_ndims`、`array_dims`、`array_lower`、`array_upper` 和 `cardinality` 函数可以查看所需的元数据。需要诊断错误时，应在同一会话中实际调用这些检查函数；它们是检查工具，不代表已经触发了某个特定错误。

核心路径主要包括：

1. **数组元素或切片赋值。** `arrayfuncs.c` 会校验下标和切片边界。一维数组可以通过给新元素赋值来扩展，中间位置填入 `NULL`；多维数组不支持这种扩展。给空数组赋切片时必须提供两个边界。因此，赋值访问当前读取边界之外的位置时，应按赋值操作分析，不能从 `SELECT a[n]` 的结果推断。
2. **数组拼接。** 当同秩数组的非拼接维度的长度或下界不同时，`array_cat` 抛出 `2202E`。代表性案例覆盖的就是这条路径。
3. **多维构造。** 表达式执行器在用于构造多维数组的非空数组表达式维度不兼容时，也会抛出同一条件。

源码中还存在其他调用方，包括数据类型辅助路径。类别 22 是宽泛的数据异常类别；具体是哪个形状或下标契约失败，要由 `2202E` 条目、源码函数和报文共同确定。

## 报文与诊断 {#messages}

可执行摘录如下：

<!-- BEGIN SQLSTATE SNIPPET: incompatible_array_dimensions -->
```sql
SELECT ARRAY[[1,2]] || ARRAY[[3]];
SELECT ARRAY[1,2] || ARRAY[3,4];
```
<!-- END SQLSTATE SNIPPET -->

第一条语句的两个二维数组内层维度不同。PostgreSQL 18.6 报告：

```text
SQLSTATE: 2202E
severity: ERROR
message_primary: cannot concatenate incompatible arrays
message_detail: Arrays with differing element dimensions are not compatible for concatenation.
source: array_userfuncs.c / array_cat / line 450
```

PostgreSQL 10.21 的同一案例给出相同的主报文和详细信息文本，历史源码行号是 356。第二条语句返回 `{1,2,3,4}`。这些报文和详细信息属于拼接路径，不是所有 `2202E` 调用方都必然使用的措辞。

其他源码确认的模板包括 `array subscript out of range`、`array slice subscript must provide both boundaries`（详细信息会解释给空数组赋值的要求），以及 `upper bound cannot be less than lower bound`。如果驱动程序提供这些字段，请保留 `message_detail`、`message_hint`、`source_file`、`source_function` 和 `source_line`；它们往往能区分切片校验与拼接校验。

## 诊断 {#diagnosis}

先按表达式分类：

- 普通元素读取如 `a[999]` 可以合法返回 `NULL`。在称为服务器错误前，检查数组本身、下标表达式以及存储的边界。
- 切片读取根据文档规则可能返回 `NULL`、空的零维数组或缩减后的重叠部分。没有错误响应时不要把这些值映射成 `2202E`。
- 赋值、数组构造和拼接会执行校验代码。记录数组秩、维度、下界、提供的下标数量，以及语句是否在修改值。

遇到真实错误时，记录 SQLSTATE、严重级别、主报文、详细信息、提示、语句位置以及关系对象或函数上下文。使用同一个值或源表达式配合 `array_ndims`、`array_dims`、`array_lower` 和 `array_upper` 对照。拼接时比较每一个非拼接维度和下界；切片时检查两个边界及其顺序；构造时检查每个子数组的形状。

在 PL/pgSQL 中，处理器可以捕获 `array_subscript_error` 或 `SQLSTATE '2202E'`，但处理器的事务行为取决于代码块。带 `EXCEPTION` 子句的代码块会让受保护主体在子事务中运行；主体出错后，主体内对持久数据库状态的修改会先回滚，再运行处理器。`OTHERS` 也有文档规定的排除项，应用要修复数组操作时应使用具体条件。

## 处理 {#response}

修复违反形状契约的操作。拼接前统一维度和下界；提供完整且顺序正确的切片边界；只有在确实需要 `NULL` 填充语义时才使用一维扩展；或者使用形状匹配的子数组重新构造多维值。如果读取返回 `NULL` 是合法结果，应先按值处理，不要在判断应用是否需要区分“缺少元素”和“存储的 NULL 元素”前就用 `COALESCE` 掩盖它。

代表性错误在自动提交下运行。失败后连接状态为 `IDLE`，有效拼接成功。在显式事务中，`ERROR` 通常会让事务进入中止状态，直到 `ROLLBACK` 或回滚到保存点；连接本身不一定需要关闭。带异常子句的 PL/pgSQL 代码块可以让受保护主体在子事务中封装失败，主体内的修改会在处理器运行前回滚。应读取客户端的实际事务状态，不要仅凭 `2202E` 推断连接结局。

## 版本 {#versions}

目录记录 `2202E` 存在于锁定的 7.4–8.4.22 pre-9.0 正式源码、9.0.23 至 18.6 的全部正式快照及 19 Beta 3 预览快照。同 tag 的 `REL8_1_4` `errcodes.sgml` 表已经列出 `2202E` 和条件名 `array_subscript_error`，因此至少可以确认 8.1.4 已有该条件名。7.0–7.3 仍有候选源码缺口，因此 7.4 观察结果只是存在边界，不是精确引入版本。18.6 固定源码 commit 为 `724edf9bde9d356724ad384a2e196edc3c9f80f7`；其 `errcodes.txt` 同时保留别名宏和带条件名的宏。

不兼容维度案例在 PostgreSQL 18.6 和 10.21 上均通过，主报文和详细信息文本相同而源码行号不同。这个跨版本结果不保证所有旧小版本或其他 `2202E` 调用路径的措辞都不变。

## 相关 {#related}

[`22000` — `data_exception`](../22000/) 是宽泛的类别 22 代码。[`22005` — `error_in_assignment`](../22005/) 是不同的赋值条件，不应替代数组专用的 `2202E`。[`22P02` — `invalid_text_representation`](../22p02/) 处理输入文本解析。`2202E` 也不同于成功的越界读取，后者按数组文档规则返回 `NULL`。

## 来源 {#sources}

结构化证据记录在[公开证据 JSON](../../data/evidence/2202e.json)中。源码记录固定到 PostgreSQL commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`；运行记录保留共享注册表、两个目标版本和 `2202E-snippet-registry-final-20260909` 运行 ID。

- `src.errcodes.18.6` — [`errcodes.txt`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt#L159-L164)
- `src.array-userfuncs.18.6` — [`array_userfuncs.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/adt/array_userfuncs.c#L443-L450)
- `src.arrayfuncs.18.6` — [`arrayfuncs.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/adt/arrayfuncs.c#L2644-L2653)
- `src.exec-expr.18.6` — [`execExprInterp.c`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/executor/execExprInterp.c#L3549-L3565)
- `doc.array.18` 与 `doc.func.18` — [Arrays](https://www.postgresql.org/docs/18/arrays.html) 与 [Array Functions and Operators](https://www.postgresql.org/docs/18/functions-array.html)
- `doc.transactions.18` — [Transactions](https://www.postgresql.org/docs/18/tutorial-transactions.html)
