知识卡片
GROUP BY/HAVING与显式演算表述在"零匹配"场景下的语义差异
内容
查询”每种由不超过2个供应商供应的零件,得到其编号、城市、总供应量”,用
[[关系演算的基本结构区间变元原型元组WHERE谓词]]标准套路能推导出一个不依赖
GROUP BY的SQL表达式(对每个零件PX,用相关子查询算COUNT和COALESCE过的SUM,
再在WHERE里判断COUNT<=2);但用GROUP BY/HAVING改写后更简洁:
SELECT PX.PNO,PX.CITY,COALESCE(SUM(SPX.QTY),0)AS TPQ FROM P AS PX,SP
AS SPX WHERE PX.PNO=SPX.PNO GROUP BY PX.PNO HAVING COUNT(*)<=2。这个
对照暴露了一个关键的语义分歧:完全没有被任何供应商供应的零件型号,在
GROUP BY/HAVING版本里根本不会出现在结果里(因为FROM子句的JOIN先天就把
这类零件过滤掉了),但按照原始查询字面意思,一个”由0个供应商供应”的零件
显然满足”不超过2个供应商供应”这个条件,理应出现在结果集里——GROUP BY/
HAVING这条更简洁的写法因此和原始查询意图并不完全一致,这正呼应了
[[SUMMARIZE与GROUP_BY_HAVING的逻辑冗余性及陷阱]]里”用GROUP BY前必须
先确认汇总对象选对了”这条实践建议:这里真正该汇总的对象应该是P(所有
零件,包括未被供应的),而GROUP BY版本实际上是在对JOIN之后、已经把无
供应零件排除掉的中间结果做分组,选错了汇总对象。这个案例是本章”简洁写法
未必等价于精确写法”这条主题的又一次具体印证——SELECT子句里出现的PX.CITY
虽然不是分组列,但当代SQL标准允许它出现是因为它函数依赖于分组列PX.PNO
(呼应[[函数依赖的精确定义及为何通常不需要专门声明]]),这条规则本身
在早期SQL版本里并不成立,SELECT项曾经被要求必须严格是分组列本身。