INSERT 插入数据
本教程共 50 篇 · 第 18 篇 · 更新于 2026-07-31 · 约 8 分钟阅读
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'。数字和布尔值不用加引号。布尔值直接写true或false即可。
只写部分列
INSERT 不要求写满所有列。没写的列,如果有默认值就填默认值,允许为空(NULL)就填空。
INSERT INTO users (username)
VALUES ('xiaohong');
这条只给了用户名,邮箱和年龄就是 NULL,注册时间用当前时间。
Note如果某列既不允许 NULL,又没有默认值,你又没给它写值,INSERT 会直接报错。比如把
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 里插一行。它常用来做数据初始化、归档和批量生成。
NoteINSERT … SELECT 时,SELECT 返回的列数、列顺序和类型,要和目标列匹配。插入的行数取决于 SELECT 查出了几行,可能一次插很多行。
几个常见的插入错误
写好 INSERT 后如果报错,先对照下面这几种:
- 列数和值数不一致:
INSERT INTO users (username, email) VALUES ('a')会报「列数与值数不匹配」。 - 类型对不上:往
age(整数)里写'abc'会报类型转换错误。记住数字别加引号。 - 违反非空约束:列设了
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占位。