首页 / PostgreSQL 入门教程 / INSERT 插入数据

PostgreSQL 入门教程

INSERT 插入数据

本教程共 50 篇 · 第 18 篇 · 更新于 2026-07-31 · 约 8 分钟阅读

PostgreSQLPostgreSQL 入门教程INSERTRETURNING自增主键日期插入

18. INSERT 插入数据

本节目标:学完你能用 INSERT 把一行或多行数据写进表,知道怎么指定列、怎么取回刚插入的那行的 id,也知道日期该怎么写,还会避开几种常见的插入报错。

插入(INSERT)是往表里写数据的动作。先准备两张示例表,后面所有例子都用它们。注意 id 用了 PostgreSQL 18 推荐的写法 GENERATED ALWAYS AS IDENTITY,它会自动生成递增的整数主键(primary key),你平时插入时不用管它。

CREATE TABLE users (
  id        INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  username  VARCHAR(50) NOT NULL,
  email     VARCHAR(120),
  age       SMALLINT,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE orders (
  id        INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id   INT NOT NULL,
  amount    NUMERIC(10,2) NOT NULL,
  status    VARCHAR(20) DEFAULT 'pending',
  created_at TIMESTAMPTZ DEFAULT now()
);

在 psql 里执行插入时,提示符是这样的:

postgres=# INSERT INTO users (username, email, age) VALUES ('xiaoming', 'ming@example.com', 28);
INSERT 0 1

一次插入一行

最基础的写法,先写列名,再写对应的值。列和值的顺序要对应,数量也要一样。

INSERT INTO users (username, email, age)
VALUES ('xiaoming', 'ming@example.com', 28);

执行后 psql 会回一句:

INSERT 0 1

回显 INSERT 0 1 里,最后的 1 是成功插入的行数;前面的 0 在现今的 PostgreSQL 中基本固定为 0(它原本表示插入行的对象标识 OID,而现代表默认不存 OID),你平时只需看最后那个数字。

因为 id 是自增的、没写,created_at 有默认值,这两列 PostgreSQL 会自己填。

Tip

字符串和日期都要用单引号包起来,比如 'xiaoming''2026-07-31'。数字和布尔值不用加引号。布尔值直接写 truefalse 即可。

只写部分列

INSERT 不要求写满所有列。没写的列,如果有默认值就填默认值,允许为空(NULL)就填空。

INSERT INTO users (username)
VALUES ('xiaohong');

这条只给了用户名,邮箱和年龄就是 NULL,注册时间用当前时间。

Note

如果某列既不允许 NULL,又没有默认值,你又没给它写值,INSERT 会直接报错。比如把 email 改成 NOT NULL 后,上面这条语句就会失败。所以「能省着写」的前提是:列允许为空,或者有默认值。

一次插入多行

要插好几行时,没必要写多条 INSERT。在 VALUES 后面用逗号隔开多组括号即可,效率也更高。

INSERT INTO users (username, email, age)
VALUES
  ('zhangsan', 'zhang@example.com', 31),
  ('lisi',     'li@example.com',    24),
  ('wangwu',   'wang@example.com',   35);

回显会变成 INSERT 0 3,表示插了 3 行。

Note

一条语句里插多行,要么全部成功,要么一行都不插(原子性)。中途出错会整体回滚,不会出现插了一半的情况。

用 RETURNING 取回插入的行

有时候插完想知道刚生成的 id 是多少,比如下单后要拿着订单 id 去支付。这时用 RETURNING 子句,让 INSERT 顺手把结果返回来。

INSERT INTO orders (user_id, amount, status)
VALUES (1, 99.90, 'paid')
RETURNING id, user_id, amount;

输出类似:

 id | user_id | amount
----+---------+--------
  1 |       1 |  99.90
(1 row)

RETURNING * 会返回整行;写 RETURNING id 就只返回 id。多行插入时,RETURNING 会把所有插入的行都列出来。

INSERT INTO orders (user_id, amount)
VALUES (1, 10.00), (2, 20.50)
RETURNING id, amount;

插入日期时间

日期类型的列,值用 'YYYY-MM-DD' 格式;时间用 'YYYY-MM-DD HH:MI:SS' 格式。下面这条显式写了注册时间。

INSERT INTO users (username, age, created_at)
VALUES ('laoliu', 40, '2026-07-31 09:30:00');

如果你只想用当前时间,可以直接写 now(),或者干脆不写让默认值生效。

INSERT INTO users (username, created_at)
VALUES ('xinqiang', now());
Warning

字符串里本身带单引号时,要写两个单引号来转义。比如 'O''Reilly' 才表示 O’Reilly。这是新手常踩的坑,尤其从 Excel 复制带撇号的名字时容易中招。

显式使用 DEFAULT 和 NULL

有些列你其实「想用默认值」,但又因为列的位置不能不写,这时可以显式写 DEFAULT 占位。

INSERT INTO users (username, created_at)
VALUES ('xiaozhao', DEFAULT);

这条里 created_at 会取表里定义的 now() 默认值,效果和不写这一列一样。

同理,想明确插入一个空值,就写 NULL

INSERT INTO users (username, email)
VALUES ('xiaosun', NULL);
Tip

DEFAULTNULL 是关键字,不是字符串,不要加引号。写成 'DEFAULT' 会被当成普通文本存进去,那就错了。

列的顺序由你决定

VALUES 里的值顺序只和你写的「列清单」对应,不要求和表定义一致。下面把 age 写在 username 前面也完全合法:

INSERT INTO users (age, username, email)
VALUES (26, 'xiaozhou', 'zhou@example.com');

只要「列清单」和「值清单」一一对应,顺序随你排。我习惯按表定义的顺序写,别人读起来更顺。

从另一张表批量插入

INSERT 的值不只能手写在 VALUES 里,还能来自一条 SELECT 查询的结果。比如给所有 30 岁以上的用户,每人自动生成一张「会员礼包」订单:

INSERT INTO orders (user_id, amount, status)
SELECT id, 0.00, 'gift'
FROM users
WHERE age > 30;

这条语句会按 SELECT 查出的每一行,往 orders 里插一行。它常用来做数据初始化、归档和批量生成。

Note

INSERT … SELECT 时,SELECT 返回的列数、列顺序和类型,要和目标列匹配。插入的行数取决于 SELECT 查出了几行,可能一次插很多行。

几个常见的插入错误

写好 INSERT 后如果报错,先对照下面这几种:

  1. 列数和值数不一致INSERT INTO users (username, email) VALUES ('a') 会报「列数与值数不匹配」。
  2. 类型对不上:往 age(整数)里写 'abc' 会报类型转换错误。记住数字别加引号。
  3. 违反非空约束:列设了 NOT NULL 又没给值、也没默认值,会报「空值违反非空约束」。
ERROR:  列 "email" 中的空值违反非空约束
DETAIL:  失败的行包含 (5, xiaoli, null, null, null)

看见这类错误,先检查是不是漏写了某列,或者该列确实不允许为空。

关于自增主键的写法

id 列用了 GENERATED ALWAYS AS IDENTITY。这是 PostgreSQL 18 推荐的写法,语义清晰、可控。以前的老教程常写 SERIAL,那是历史兼容写法,新项目不建议再用。

如果想手动指定自增列的值(一般不建议),要加 OVERRIDING

INSERT INTO users (id, username)
OVERRIDING SYSTEM VALUE
VALUES (100, 'tester');
Note

日常插入数据不用管 id,让数据库自己分配最省心,也能避免主键冲突。除非你在做数据迁移,否则别去手动指定它。

插入时的类型转换

有时候你拿到的数据带引号,比如从别的系统导出成 '28'。PostgreSQL 比较聪明,多数情况下会自动把字符串转成数字或日期,所以下面这样也能插进去:

INSERT INTO users (username, age)
VALUES ('xiaozheng', '33');

'33' 是字符串,但 age 是整数列,PostgreSQL 会尝试把它转成 33。不过我建议还是按列的真实类型写值:数字就写数字,别依赖隐式转换。一旦字符串里混了非数字字符,就会报类型转换错误。

Tip

依赖数据库帮你「猜类型」容易埋坑。明确按列类型写值,代码可读性也更好,出了问题更容易定位。

小批量还是大批量

插几行、几十行,用本文的 INSERT 写法就够了。但如果要灌几万、几十万行,请直接用第 21 章的 COPY,速度会快一个数量级。INSERT 一行一事务开销大,COPY 是批量提交,差别很明显。

小结

  • INSERT 基本结构:INSERT INTO 表 (列...) VALUES (值...)
  • 一次插多行用逗号分隔的多组括号,比多次单插快,且整体原子。
  • RETURNING 拿回刚插入的行,特别适合要拿到自增 id 的场景。
  • 日期用 'YYYY-MM-DD' 格式;字符串里的单引号要写成两个。
  • 自增主键优先用 GENERATED AS IDENTITY;列不够时可用 DEFAULTNULL 占位。