开窗函数这个话题,其实最核心的价值就在于它能让我们在保留全部明细数据的同时,完成复杂的聚合计算。比如,要算每个产品在其所在分类中的销售占比,这在报表分析里太常用了。如果用传统GROUP BY再加JOIN回去,SQL写得啰嗦不说,性能也糟糕。 计算分组内占比的核心逻辑 说白了就是:用当前行的值,除以它
开窗函数这个话题,其实最核心的价值就在于它能让我们在保留全部明细数据的同时,完成复杂的聚合计算。比如,要算每个产品在其所在分类中的销售占比,这在报表分析里太常用了。如果用传统GROUP BY再加JOIN回去,SQL写得啰嗦不说,性能也糟糕。
说白了就是:用当前行的值,除以它所在那个分组的总和。关键的技巧在于,必须用SUM() OVER(PARTITION BY ...)这个开窗函数,把分组的总和“广播”到每一行上。这样,分母就变成了一个针对当前分组的、重复出现的总数值,分子则是它自己的原值。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
这里的几个要点,新手最容易栽跟头:
SUM(amount) OVER (PARTITION BY category),这才能算出每个category的总和,并把它赋给该类别下的每一行。amount就行,千万别画蛇添足,再加什么聚合或窗口函数。amount是整数,直接除会得到0(整数除法截断)。通常需要显式转为DECIMAL,或者像例子中那样乘个100.0来“逼”出小数。虽然语法看着差不多,但不同数据库在除法上的默认行为不同,这一点需要注意。
SELECT
category,
product,
amount,
ROUND(amount * 100.0 / SUM(amount) OVER (PARTITION BY category), 2) AS pct
FROM sales;
100.0换成100.00是个好习惯。amount是INTEGER,而你不记得乘100.0,结果出来全是0。这是个非常常见的坑。NULL值数据里出现NULL是家常便饭,它处理起来需要点技巧。默认情况下,SUM() OVER()会忽略NULL,这本身问题不大。但最麻烦的情况是,如果整个分组的amount都是NULL,那分子分母都是NULL,结果自然也是NULL,这就很难看了。
常见的应对策略:
COALESCE(SUM(amount) OVER (...), 0)把分母里的NULL补成0。但这样有可能引出“除零”错误,所以最好再用个CASE保护一下。CASE WHEN SUM(amount) OVER (PARTITION BY category) = 0 THEN 0 ELSE ... END。逻辑清晰,也不怕除零。NULL当作0来处理,那就需要在计算分子和分母时,都先用COALESCE(amount, 0)把它替换掉。如果分组条件不止一个,比如要算“每个城市、每一年”的销售占比,那就要把维度都放进PARTITION BY里。
PARTITION BY city, YEAR(order_date)。顺序不重要,但列必须齐全。PARTITION BY city,忘了年份。结果就是把不同年份的数据混在一起算,结果完全错误。YEAR(order_date) as yr,但在OVER()里还是得老老实实写原表达式YEAR(order_date)。总结一下,写这类SQL时,最容易卡住的地方就是分母的类型转换和NULL值处理。特别是从MySQL迁移到PostgreSQL,或者在不同数据库之间切换时,同样的SQL跑出来全是0或者全是NULL,这种问题排查起来最让人头疼。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述