SQL Server 2017子查询重写依赖优化器自动展开,CTE不物化;PostgreSQL 12默认物化CTE、禁止LIMIT子查询、严格要求标量子查询单行,两者在NULL处理、别名规则及重写可控性上差异显著。

SQL Server 2017的子查询重写行为受ANSI SQL兼容性影响大
SQL Server 2017默认使用ANSI SQL标准的严格解析规则,对子查询中未明确限定的列名(尤其是ORDER BY、TOP、OFFSET/FETCH)会直接报错,而不是尝试推断上下文。比如在FROM子句中用派生表却没加别名,会触发Msg 102, Level 15, State 1, Line X: Incorrect syntax near ')'。
它还会主动将某些简单IN子查询(尤其含常量或单表)内联为JOIN或SEMI JOIN,但仅限于优化器判定“无相关性”时;一旦子查询引用外部表字段(即相关子查询),就基本不重写,而是走嵌套循环执行计划。
- 不支持
WITH子句中定义后在多个后续语句复用(CTE只作用于紧邻的单个语句) -
EXISTS子查询里的SELECT *会被优化器忽略字段列表,但SELECT 1更稳妥,避免意外触发列解析 - 若子查询含
GROUP BY且外部查询有ORDER BY,必须显式给派生表加别名,否则报错
PostgreSQL 12默认启用子查询物化(Materialization)策略
PostgreSQL 12引入了enable_material和materialize参数控制是否将子查询结果缓存为临时行集。默认开启,意味着即使一个WHERE EXISTS (SELECT ...)子查询被多次调用,也可能只执行一次并复用结果——这和SQL Server的逐次执行完全不同。
但它对子查询重写的保守性更高:不会把IN自动转成JOIN,也不会把NOT IN重写为NOT EXISTS(这点极易引发NULL逻辑错误)。你得手动改写,否则性能可能陡降。
- 相关子查询中若含
LIMIT,PostgreSQL 12会拒绝执行(报ERROR: LIMIT # is not allowed in subqueries),而SQL Server允许 - 派生表(
FROM (SELECT ...) AS t)必须带AS别名,且别名不能省略括号——FROM (SELECT 1) t合法,FROM (SELECT 1) t(x)也合法,但FROM (SELECT 1) t在某些旧客户端驱动下可能失败 -
UNION子查询作为派生表时,PostgreSQL要求所有分支列名/类型一致,SQL Server则按第一个分支隐式推导
两者对CTE的处理差异直接决定子查询能否被“拆解重用”
SQL Server 2017把CTE当作语法糖,优化器通常展开(inline)为等价子查询,除非显式用OPTION (USE HINT('DISABLE_OPTIMIZER_ROWGOAL'))之类干预;这意味着CTE定义多次,实际可能执行多次——和写成多个相同子查询没区别。
PostgreSQL 12则默认将CTE视为“优化边界”,即WITH t AS (SELECT ...)之后,只要t被引用多次,就会物化一次结果供复用(除非加MATERIALIZED或NOT MATERIALIZED强制指定)。这是真正意义上的“子查询重写为可复用中间结果”。
- SQL Server里
WITH后面不能跟RECURSIVE关键字(虽支持递归CTE,但关键词是可选的);PostgreSQL 12必须写WITH RECURSIVE才启用递归,否则报错 - PostgreSQL中CTE可定义后立即用
INSERT/UPDATE/DELETE修改其基础表,SQL Server不允许——CTE只是只读逻辑视图 - 两者都不允许在CTE定义中引用自身(非递归场景),但PostgreSQL报错更早(PARSE阶段),SQL Server拖到EXECUTE阶段才报
最容易踩坑的是NULL感知与空结果集行为
SQL Server 2017在NOT IN (subquery)遇到子查询返回NULL时,整个条件恒为UNKNOWN,导致查不到任何行——这是ANSI SQL标准行为,但开发者常误以为是“空结果”。PostgreSQL 12同样如此,但错误更隐蔽:如果子查询本身因权限或WHERE过滤为空,NOT IN仍返回空集,而NOT EXISTS才真正表达“不存在匹配项”。
- SQL Server中
SELECT * FROM t WHERE id IN (SELECT id FROM s WHERE flag = 1),若s中无flag = 1的行,结果为空;PostgreSQL同理,但若s表被GRANT限制,还可能抛权限错误而非静默空结果 - PostgreSQL 12对
SELECT子句中的标量子查询(如SELECT (SELECT name FROM users WHERE id = t.user_id))要求必须返回0或1行,否则报more than one row returned by a subquery used as an expression;SQL Server则取第一行(不报错),行为不一致 - 两者都支持
LATERAL(SQL Server叫APPLY),但PostgreSQL 12的LATERAL可出现在WHERE或ORDER BY中,SQL Server的CROSS APPLY只能在FROM里
关键点不在“谁重写得更多”,而在于重写是否可控、是否可预测。SQL Server偏向让优化器替你决定,PostgreSQL偏向让你明确声明意图——前者省事但难调试,后者啰嗦但可追溯。真正在意子查询行为的场景(比如报表SQL迁移、性能调优),必须逐条验证执行计划,不能依赖“看起来一样”。

















