2026 年 6 月,负责 SQL 与 GQL 标准化的 ISO/IEC JTC1 SC32 WG3 在斯德哥尔摩开会,多个与 SQL 和 PostgreSQL 社区密切相关的提案进入工作草案。这里的“已接受”还不是最终国际标准发布,而是进入标准工作草案,后续仍可能经过编辑、审查和投票流程。
这次最值得开发者关注的,不是某个炫技函数,而是几类会直接改变日常 SQL 写法的语法:QUALIFY、INSERT ... BY NAME、SELECT * 后处理,以及用于降低 JOIN 写错风险的新语法。
QUALIFY:窗口函数终于有了自己的过滤位置
QUALIFY 的定位很清楚:它像 WHERE 或 HAVING,但应用在窗口函数计算之后。
过去如果你想筛出“价格高于本分类平均价”的商品,通常要包一层子查询:
SELECT product_name, category, price
FROM (
SELECT
product_name,
category,
price,
avg(price) OVER (PARTITION BY category) AS category_avg
FROM products
) AS q
WHERE q.price > q.category_avg;
新标准草案接受的写法更贴近人的阅读顺序:
SELECT product_name, category, price
FROM products
QUALIFY price > avg(price) OVER (PARTITION BY category);
这类语法已经在不少数据库中以非标准扩展存在。对 PostgreSQL 来说,相关补丁曾面向 PostgreSQL 19 推进,但因为定义细节需要标准层面澄清而暂停。现在标准提案解决了这些定义问题,后续 PostgreSQL 20 周期可能会重新看到进展。
开发者需要注意一点:QUALIFY 不是 WHERE 的替代品。能在普通行过滤阶段完成的条件,仍应放在 WHERE,这样优化器可以更早减少数据量;依赖窗口函数结果的条件,才适合放在 QUALIFY。
INSERT BY NAME:让 INSERT ... SELECT 少踩列顺序的坑
INSERT INTO ... SELECT ... 的老问题是列按位置匹配。目标列和查询输出列只要顺序错了,就可能产生难发现的数据错误。
新草案接受了 BY NAME:
INSERT INTO t1 (c1, c2) BY NAME
SELECT
c1 * 10 AS c2,
c2 + 5 AS c1
FROM t2
WHERE c1 < 5;
这里 SELECT 输出顺序是 c2, c1,但插入时按名字匹配到目标列 c1, c2。默认行为仍是按位置匹配,也可以显式写 BY POSITION 表示旧语义。
这个改动不大,但非常实用,尤其适合 ETL、批量迁移、临时表加工这类列很多、表达式很多的场景。当前还没有明确的 PostgreSQL 实现工作被提到,不过从语义范围看,它可能是一个相对独立、适合贡献者切入的项目。
SELECT * 不再只能全收:EXCLUDE、REPLACE、RENAME
SELECT * 一直方便,也一直危险:加列会影响结果,敏感列容易被误带出,重命名或替换某些列时又要手写整张表的列清单。
这次被接受的提案把星号展开后的“后处理”标准化了:
SELECT * (
EXCLUDE (a, b)
REPLACE (c WITH x + y, d WITH z * 2)
RENAME (e AS ee, f AS foo)
)
FROM t;
也支持限定表名的星号:
SELECT t.* (EXCLUDE (internal_note, deleted_at))
FROM t;
这对宽表查询、临时分析和 API 投影层都很有用。比如一张用户表有 40 个字段,你只想排除 password_hash、mfa_secret,现在不必手写另外 38 个字段。
类似能力已经存在于一些 SQL 实现中,但行为和文档并不统一。标准化的价值在于把边界讲清楚:星号何时展开、排除列是否必须存在、替换列如何命名、限定星号如何与多表查询交互。PostgreSQL 19 周期已有部分实现开工,后续可能继续面向 PostgreSQL 20 推进。
实操:今天在 PostgreSQL 里如何模拟这些写法
下面这个示例可以直接在当前 PostgreSQL 中运行,用来感受 QUALIFY 和 BY NAME 想解决的问题。运行前只需要有一个可用的 psql 连接。
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS target_prices;
CREATE TABLE products (
product_name text NOT NULL,
category text NOT NULL,
price numeric NOT NULL
);
INSERT INTO products (product_name, category, price) VALUES
('basic keyboard', 'accessory', 40),
('mechanical keyboard', 'accessory', 120),
('usb cable', 'accessory', 10),
('office chair', 'furniture', 180),
('standing desk', 'furniture', 520),
('desk lamp', 'furniture', 70);
-- 当前 PostgreSQL 写法:用子查询模拟未来的 QUALIFY
SELECT product_name, category, price
FROM (
SELECT
product_name,
category,
price,
avg(price) OVER (PARTITION BY category) AS category_avg
FROM products
) AS q
WHERE price > category_avg
ORDER BY category, price DESC;
CREATE TABLE target_prices (
category text NOT NULL,
product_name text NOT NULL
);
-- 当前 PostgreSQL 仍按位置匹配,所以这里必须让 SELECT 输出顺序匹配目标列顺序
INSERT INTO target_prices (category, product_name)
SELECT category, product_name
FROM products
WHERE price >= 100;
SELECT * FROM target_prices ORDER BY category, product_name;
如果未来数据库支持 QUALIFY 和 BY NAME,上面的关键片段可以这样实践:
-- 假设数据库已支持 SQL:202y 草案中的 QUALIFY
SELECT product_name, category, price
FROM products
QUALIFY price > avg(price) OVER (PARTITION BY category);
-- 假设数据库已支持 INSERT ... BY NAME
INSERT INTO target_prices (category, product_name) BY NAME
SELECT
product_name AS product_name,
category AS category
FROM products
WHERE price >= 100;
在迁移到新语法前,可以先在团队 SQL 规范里做两件事:窗口函数过滤统一写成“带明确别名的子查询”;INSERT ... SELECT 必须显式列出目标列和源列,避免依赖 SELECT * 或隐式顺序。
JOIN TO ONE 与 Key Joins:标准开始正面处理 JOIN 错误
JOIN TO ONE 已进入标准草案。它的含义是:运行时检查左侧每一行最多只能匹配右侧一行。如果匹配出多行,抛异常。
SELECT
ro.room_number,
ro.room_type,
ro.price,
re.guest_name
FROM reservations re
INNER JOIN TO ONE rooms ro
ON ro.room_number = re.room_number
WHERE re.id = 16354;
这个语法针对的是常见事故:你以为是一对一或多对一 JOIN,实际因为条件不完整,结果行被放大了。例如真实外键可能不是 room_number,而是 (hotel_id, room_number)。
另一个仍在推进、尚未被接受的提案是 Key Joins。它更激进:在语法检查阶段要求 JOIN 条件对应真实外键约束。示例语法仍可能变化,但大致长这样:
SELECT
ro.room_type,
ro.rate,
re.guest_name
FROM reservations AS re
JOIN rooms AS ro
FOR KEY (hotel_id, room_number) <- re (hotel_id, room_number)
WHERE re.reservation_id = 16354;
两者取舍不同:JOIN TO ONE 在运行时发现“这一批数据匹配多了”;Key Joins 希望在语义层面证明“这个 JOIN 正是沿着某个外键关系写的”。前者更容易理解,后者约束更强,也更复杂。
采用建议:别急着改代码,但可以先改习惯
这些特性还在标准工作草案阶段,数据库实现也会有时间差。现在最实际的做法不是立刻重写 SQL,而是提前整理团队的查询风格:
- 窗口函数过滤:用清晰子查询隔离,未来容易替换成
QUALIFY。 INSERT ... SELECT:目标列和源列都显式写出,减少列顺序事故。- 宽表投影:避免把
SELECT *暴露到 API 边界;未来可用EXCLUDE简化内部查询。 - JOIN:对“应该只匹配一行”的查询补充唯一约束、外键约束或测试数据校验。
- 关注 PostgreSQL 20:这些提案里,
QUALIFY和SELECT *后处理已经有过实现动向,值得在开发周期中跟踪。
SQL 标准这次的方向很务实:减少样板子查询,减少列顺序错误,减少 JOIN 误写造成的数据放大。它不会让 SQL 变短到像脚本语言,但会让那些每天写查询的人少绕几圈。