SQL:202y 新草案看点:QUALIFY、按列名插入与更安全的 JOIN

2026-06-30 21 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

2026 年 6 月,负责 SQL 与 GQL 标准化的 ISO/IEC JTC1 SC32 WG3 在斯德哥尔摩开会,多个与 SQL 和 PostgreSQL 社区密切相关的提案进入工作草案。这里的“已接受”还不是最终国际标准发布,而是进入标准工作草案,后续仍可能经过编辑、审查和投票流程。

这次最值得开发者关注的,不是某个炫技函数,而是几类会直接改变日常 SQL 写法的语法:QUALIFYINSERT ... BY NAMESELECT * 后处理,以及用于降低 JOIN 写错风险的新语法。

QUALIFY:窗口函数终于有了自己的过滤位置

QUALIFY 的定位很清楚:它像 WHEREHAVING,但应用在窗口函数计算之后。

过去如果你想筛出“价格高于本分类平均价”的商品,通常要包一层子查询:

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 * 不再只能全收:EXCLUDEREPLACERENAME

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_hashmfa_secret,现在不必手写另外 38 个字段。

类似能力已经存在于一些 SQL 实现中,但行为和文档并不统一。标准化的价值在于把边界讲清楚:星号何时展开、排除列是否必须存在、替换列如何命名、限定星号如何与多表查询交互。PostgreSQL 19 周期已有部分实现开工,后续可能继续面向 PostgreSQL 20 推进。

实操:今天在 PostgreSQL 里如何模拟这些写法

下面这个示例可以直接在当前 PostgreSQL 中运行,用来感受 QUALIFYBY 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;

如果未来数据库支持 QUALIFYBY 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:这些提案里,QUALIFYSELECT * 后处理已经有过实现动向,值得在开发周期中跟踪。

SQL 标准这次的方向很务实:减少样板子查询,减少列顺序错误,减少 JOIN 误写造成的数据放大。它不会让 SQL 变短到像脚本语言,但会让那些每天写查询的人少绕几圈。


相关推荐