max()和min()窗口函数能单次扫描完成列内极值广播,避免子查询的多扫、错配与难扩展问题,是处理非归一化数据的真实刚需工具。

MAX() 和 MIN() 窗口函数能直接完成列内极值广播,避免子查询带来的多扫、错配、难扩展问题
窗口函数不是“锦上添花”的语法糖,而是解决非归一化数据处理中真实瓶颈的刚需工具。核心在于它把“算一次极值、广播到每行”这件事,压缩进单次扫描里,且天然对齐业务逻辑边界。
为什么子查询做 Min-Max 标准化会出错
常见写法:SELECT price, (price - (SELECT MIN(price) FROM products)) / ((SELECT MAX(price) FROM products) - (SELECT MIN(price) FROM products)) 表面简洁,实际踩坑密集:
- 三次全表扫描 ——
MIN()、MAX()各一次,主表再扫一次,I/O 成倍上涨 -
WHERE条件漏同步 —— 比如加了WHERE category = 'Electronics',子查询却没加,结果完全失真 - 多列归一化无法共存 —— 想让
price全局归一、quantity按category分组归一,子查询结构根本撑不住
MIN() OVER() 和 MAX() OVER() 一步到位的关键写法
正确姿势:SELECT price, (price - MIN(price) OVER()) / NULLIF(MAX(price) OVER() - MIN(price) OVER(), 0) AS price_norm FROM products
-
OVER()不带参数 = 整个结果集为一个窗口,极值自动广播到每一行,只扫一遍 -
NULLIF(..., 0)必须加 —— 否则当所有price值相等时,分母为 0,触发division by zero错误 - 需要分组归一?改
OVER(PARTITION BY category)即可,无需重构整个查询 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle、SQLite 3.25+ 都支持;但 MySQL 5.7 及更早版本不支持,硬套会报错
NULL 值和全空列的隐性陷阱
窗口函数对 NULL 的处理看似合理,实则容易掩盖问题:
-
MIN()/MAX()自动跳过NULL,所以含NULL的行归一化后仍是NULL—— 这符合预期,但你要确认业务是否允许缺失值参与计算 - 若整列全是
NULL,MIN() OVER()和MAX() OVER()都返回NULL,导致整个表达式结果为NULL,而非报错;你可能以为计算成功,其实没数据 - 想强制补默认值?不能靠窗口函数本身,得在外部用
COALESCE()或提前过滤
真正容易被忽略的是:窗口函数执行时机在 WHERE 和 GROUP BY 之后、ORDER BY 之前。这意味着你写的 OVER(PARTITION BY x) 是基于已过滤后的数据计算的 —— 它很可靠,但前提是你的 WHERE 条件已经写对了。漏掉这个前提,再漂亮的窗口写法也会输出错误结果。











