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

PostgreSQL 入门教程

INSERT 插入数据

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

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

本节目标:学完你能用 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'。数字和布尔值不用加引号。布尔值直接写 true 或 false 即可。

只写部分列

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

DEFAULT 和 NULL 是关键字,不是字符串,不要加引号。写成 '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;列不够时可用 DEFAULT、NULL 占位。