SQL 语法入门
PostgreSQL 与 MySQL 的基础查询结构相近,主要差异集中在标识符、自增列、类型、布尔值、字符串操作、Upsert、关联更新和结果返回。以下示例以 PostgreSQL 原生语法为准。
标识符与 Schema
未加引号的标识符会被折叠为小写,双引号保留原始大小写;字符串使用单引号。
CREATE TABLE app_user (...); -- 实际对象名 app_user
CREATE TABLE "AppUser" (...); -- 对象名严格为 AppUser
SELECT * FROM app_user;
SELECT * FROM "AppUser";MySQL 常用反引号引用对象,PostgreSQL 使用双引号:
| 目的 | MySQL | PostgreSQL |
|---|---|---|
| 引用对象 | `user` | "user" |
| 字符串 | 'text' | 'text' |
| 限定对象 | database.table | schema.table |
| 默认命名空间 | 当前 Database | search_path,通常先使用 public |
不建议创建混合大小写的带引号对象,否则后续 SQL 必须始终保持相同引号和大小写。
建表与常用类型
CREATE TABLE app_user (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username VARCHAR(100) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
balance NUMERIC(18, 2) NOT NULL DEFAULT 0,
profile JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_app_user_username UNIQUE (username),
CONSTRAINT ck_app_user_balance CHECK (balance >= 0)
);| MySQL 常用类型 | PostgreSQL 对应选择 | 差异 |
|---|---|---|
BIGINT AUTO_INCREMENT | BIGINT GENERATED ... AS IDENTITY | Identity 是标准写法;BIGSERIAL 是序列简写,不是真正类型 |
TINYINT(1) | BOOLEAN | PostgreSQL 布尔值使用 TRUE、FALSE,不能普遍当作 0/1 |
DATETIME | TIMESTAMP | 都不携带时区语义 |
TIMESTAMP | TIMESTAMPTZ 或 TIMESTAMP | TIMESTAMPTZ 按绝对时间保存并按会话时区显示 |
JSON | JSONB 或 JSON | JSONB 支持更丰富的检索与 GIN 索引,格式和键顺序不按原文本保留 |
BLOB | BYTEA | Large Object 是另一套独立机制 |
DOUBLE | DOUBLE PRECISION | 浮点值不适合精确金额 |
ENUM | Enum Type 或约束表 | PostgreSQL Enum 是 Schema 对象,演进成本需评估 |
GENERATED ALWAYS AS IDENTITY 默认拒绝显式写入生成值;BY DEFAULT 允许显式覆盖,更方便导入历史主键。二者都不保证序列值连续,事务回滚后产生空洞是正常现象。
插入与返回结果
INSERT INTO app_user (username, enabled, balance, profile)
VALUES ('alice', TRUE, 100.00, '{"level":"vip"}'::jsonb)
RETURNING id, created_at;PostgreSQL 的 INSERT、UPDATE、DELETE 和 MERGE 可以使用 RETURNING 返回变更后的列,通常不需要再查询 LAST_INSERT_ID()。
批量插入与 MySQL 相似:
INSERT INTO app_user (username, enabled)
VALUES
('alice', TRUE),
('bob', FALSE)
RETURNING id, username;Upsert
MySQL 使用 ON DUPLICATE KEY UPDATE;PostgreSQL 使用 ON CONFLICT,并明确指定冲突约束或索引列。
INSERT INTO app_user (username, enabled)
VALUES ('alice', TRUE)
ON CONFLICT (username)
DO UPDATE SET
enabled = EXCLUDED.enabled,
updated_at = CURRENT_TIMESTAMP
RETURNING id, username, enabled;EXCLUDED 表示原本尝试插入的行。也可以使用 DO NOTHING 忽略冲突。冲突目标必须能够匹配唯一索引或唯一约束,不能把任意查询条件当作冲突条件。
查询与分页
SELECT id, username, created_at
FROM app_user
WHERE enabled IS TRUE
AND username ILIKE 'a%'
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 40;| 需求 | MySQL 常见写法 | PostgreSQL |
|---|---|---|
| 分页 | LIMIT 40, 20 | LIMIT 20 OFFSET 40 |
| 不区分大小写匹配 | 依赖 Collation | ILIKE,或显式选择合适 Collation/索引 |
| NULL 判断 | IS NULL | IS NULL |
| NULL 安全相等 | <=> | IS NOT DISTINCT FROM |
| 字符串拼接 | CONCAT(a, b) | `a |
| 当前时间 | NOW() | CURRENT_TIMESTAMP 或 now() |
分页没有稳定 ORDER BY 时结果顺序不确定。深分页使用大 OFFSET 会扫描并丢弃前面的结果,持续翻页更适合使用由排序键构成的 Keyset Pagination。
更新与删除关联数据
MySQL 常见 UPDATE ... JOIN,PostgreSQL 使用 UPDATE ... FROM:
UPDATE app_user AS u
SET enabled = FALSE,
updated_at = CURRENT_TIMESTAMP
FROM user_blacklist AS b
WHERE b.username = u.username
AND b.active IS TRUE;关联删除使用 USING:
DELETE FROM app_user AS u
USING deleted_account AS d
WHERE d.user_id = u.id
RETURNING u.id;FROM 或 USING 中同一目标行若匹配多行,更新来源可能不具有业务确定性。应通过唯一约束或先聚合、去重保证每个目标行只有一个来源。
类型转换、日期与 JSONB
SELECT
'42'::BIGINT,
CAST('2026-08-20' AS DATE),
CURRENT_DATE,
CURRENT_TIMESTAMP,
created_at + INTERVAL '7 days'
FROM app_user;SELECT
profile -> 'level' AS json_value,
profile ->> 'level' AS text_value
FROM app_user
WHERE profile @> '{"level":"vip"}'::jsonb;PostgreSQL 的隐式类型转换通常比 MySQL 严格。例如整数列与任意文本直接比较可能报错,而不是把无法转换的文本当成数字零。类型边界应在参数绑定或显式 CAST 中处理。
事务与并发更新
BEGIN;
SELECT balance
FROM app_user
WHERE id = 1
FOR UPDATE;
UPDATE app_user
SET balance = balance - 10
WHERE id = 1;
COMMIT;PostgreSQL 客户端通常也处于自动提交模式,每条独立语句自动形成事务。显式事务发生错误后,事务会进入失败状态,必须 ROLLBACK 或回滚到保存点后才能继续执行;这一点与 MySQL 常见使用体验差异明显。
DDL 绝大多数可以放入事务并回滚,但并非所有运维命令都允许在事务块中执行。
MySQL 迁移检查表
| MySQL 习惯 | PostgreSQL 调整 |
|---|---|
| 反引号引用表和列 | 改用双引号,最好使用无需引号的小写名称 |
AUTO_INCREMENT | 使用 Identity Column |
TINYINT(1) 表示布尔 | 使用 BOOLEAN 和 TRUE/FALSE |
ON DUPLICATE KEY UPDATE | 使用 ON CONFLICT |
UPDATE ... JOIN | 使用 UPDATE ... FROM |
DELETE ... JOIN | 使用 DELETE ... USING |
LIMIT offset, size | 使用 LIMIT size OFFSET offset |
| 宽松的字符串转数字 | 显式转换并处理非法输入 |
IFNULL | 使用标准 COALESCE |
GROUP_CONCAT | 使用 string_agg |
DATE_FORMAT | 使用 to_char |
LAST_INSERT_ID() | 使用 DML RETURNING |