# INSERT

> 插入单行、批量数据和查询结果，并明确写出目标列。

---

LLMS 索引： [llms.txt](/llms.txt)

---

`INSERT` 会向表中添加行。基本形式是 `INSERT INTO table (columns) VALUES (values)`。你可以插入一行、在一条语句中插入多行，或插入 `SELECT` 的结果。通过单条 `INSERT ... VALUES (...), (...), ...` 进行批量插入，比执行成千上万条独立语句快得多，因为后者每条语句都要承担一次网络往返和一次重新规划的代价。

## 本章要点 {#learn}

- 单行和多行 `INSERT`
- 为什么明确列出列名是正确的默认做法
- 从查询插入（`INSERT INTO ... SELECT`）
- 使用 `DEFAULT` 并省略列
- 状态消息：`INSERT 0 5` 究竟意味着什么

## 示例 {#example}

```sql {num="24.1" caption="INSERT"}
-- One row at a time (most explicit, often fine for app code)
INSERT INTO fruits (id, name) VALUES (1, 'apple');
INSERT INTO fruits (id, name) VALUES (2, 'banana');
INSERT INTO fruits (id, name) VALUES (3, 'cherry');

-- Many rows in one statement (much faster for bulk loads)
INSERT INTO fruits (id, name) VALUES
  (4, 'date'),
  (5, 'elderberry');

-- INSERT ... SELECT: copy rows from another query
-- (here a generator, to avoid duplicating fruits we just inserted)
INSERT INTO fruits (id, name)
SELECT n + 100, 'fruit_' || n
FROM generate_series(1, 3) AS n;

-- Confirm
SELECT count(*) AS row_count FROM fruits;
```

在 `create-table.sql` 之后运行此脚本——表必须已经存在。前 3 条语句各插入一行；第 4 条插入两行；第 5 条通过 `generate_series` 生成并插入三行。psql 输出的状态消息 `INSERT 0 5` 意味着*已插入，旧 OID 为 0，影响 5 行*——OID 属于遗留机制，可以忽略第一个数字；第二个数字才是行数。

为什么要列出列名？有两个原因。第一，如果表后来增加一列，那么你的 `INSERT INTO fruits VALUES (...)`（不带列清单）会失败，而 `INSERT INTO fruits (id, name) VALUES (...)` 仍然可以工作。译注：这里的“会失败”是原文的概括，并非对所有新增列情形都成立。PostgreSQL 在省略列清单且只提供 N 个值时，会按声明顺序将这些值映射到前 N 列，省略的列使用默认值或 NULL；因此，新增到末尾且可以省略的列未必导致失败，最终还取决于默认值和约束。第二，列出列名能让任何阅读代码的人都清楚意图——值不再是靠位置猜测的谜题。

从文件（CSV 等）批量装载时，优先使用 `COPY`（后文会介绍）——它的速度明显快于 `INSERT`。应用代码应将行批量组织到多行 `INSERT` 语句中，或使用驱动程序级的预备语句。

## 运行示例 {#run}

运行方式：

```text
$ psql -f source/insert.sql postgres
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 2
INSERT 0 3
 row_count
-----------
         8
(1 row)
```

## 常见陷阱 {#pitfalls}

- 表中已有行时重复运行脚本，会导致主键或唯一约束冲突（添加主键后）。解决办法取决于意图：删除并重新创建表、使用 `TRUNCATE`，或者使用 `INSERT ... ON CONFLICT` 执行插入或更新（UPSERT），详见下一课。
- 按位置执行 `INSERT` 时列顺序错误，会悄悄破坏数据——只要类型兼容，数据库就会把错误的值插入错误的列。始终列出列名。
- 在生产环境中，使用错误时区插入时间戳，是“时间差了 8 小时”的最常见原因。使用 `timestamptz`，让 PostgreSQL 处理转换。

> [!TIP] 提示
> `INSERT INTO t DEFAULT VALUES;` 只使用列的默认值插入一行——当每一列都有 `DEFAULT` 时很有用（例如，`id` 的值来自序列，`created_at` 的值来自 `now()`）。

> [!NOTE] 动手练习
> 再用 `(6, 'fig')` 添加一行，然后再次运行文件。（一旦 `id` 具有唯一约束，这次运行就会失败——我们会在 UPSERT 这一课中修复它。）然后尝试 `INSERT INTO fruits (id, name) SELECT n, 'gen_' || n FROM generate_series(200, 205) n;`，看看查询驱动的插入方式。

## 下载 {#downloads}

SQL 文件：[insert.sql](/examples/source/insert.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
