EXP和LN可用于行数据连乘,因数学上a×b×c=EXP(LN(a)+LN(b)+LN(c))(要求所有数>0);SQL无原生连乘聚合函数,故借SUM将乘法转为加法再还原,需过滤≤0值防报错。

EXP 和 LOG 为什么能用来连乘
因为数学上 a × b × c = EXP(LOG(a) + LOG(b) + LOG(c)),前提是所有数都 > 0。SQL 没有原生的跨行乘积聚合函数,但 SUM() 很成熟,所以用对数把乘法转成加法,再用指数还原回来——这是唯一稳定、可读性尚可的通用解法。
注意:只要有一行值 ≤ 0,LOG() 就报错(如 PostgreSQL 报 invalid argument for logarithm,MySQL 报 NULL 且不提示),这点必须提前处理。
实际写法:窗口累乘与分组连乘都适用
核心结构固定:EXP(SUM(LOG(col_name)) OVER (...)) 或 EXP(SUM(LOG(col_name)))。关键在 OVER 子句和 NULL/边界处理:
- 若要每行计算「从第一行到当前行」的累积乘积,用
OVER (ORDER BY order_col) - 若要按某列分组后算组内总乘积,用
GROUP BY group_col配合EXP(SUM(LOG(...))) - 必须过滤掉 ≤ 0 的值,否则整条 SQL 失败;可用
WHERE col_name > 0或FILTER (WHERE col_name > 0)(PostgreSQL) - MySQL 8.0+ 支持
LOG()默认以 e 为底,等价于LN();PostgreSQL 和 SQL Server 需显式用LN()或LOG()(后者在 PG 中是 log₁₀,易踩坑)
示例(PostgreSQL,按 category 累乘 price):
SELECT category, EXP(SUM(LN(NULLIF(price, 0))) FILTER (WHERE price > 0)) AS product_price FROM sales GROUP BY category;
负数和零怎么安全绕过
EXP/LOG 本身不支持负数或零,但业务中常需保留符号、跳过零、或区分“全正”“含负”“有零”。这时不能只靠 WHERE price > 0 一刀切:
- 统计负数个数:
COUNT(*) FILTER (WHERE price - 判断是否含零:
BOOL_OR(price = 0) - 用
ABS()先取绝对值再连乘,最后根据负数个数补符号:POWER(-1, COUNT(*) FILTER (WHERE price - 零的存在会让结果为 0,但
LOG(0)直接报错,所以必须用NULLIF(price, 0)配合FILTER或子查询提前剔除
性能和精度陷阱
大表上用 EXP(SUM(LOG())) 看似简洁,但实际有隐性成本:
-
LOG()和EXP()是浮点运算,数值很大或很小时会丢失精度(比如连乘 100 个 1.01,理论 ≈ 2.7,但浮点误差可能让结果变成 2.699999999) - Oracle 和 older MySQL 不支持
FILTER,得用CASE WHEN包一层,语义变重且易出错 - 如果只是需要「非空判断下的乘积」,而数据量不大,不如用应用层循环计算更可控
- 某些场景(如金融)要求精确小数,此时必须改用字符串模拟乘法或专用扩展,EXP/LOG 只能作近似参考
真正难的不是写出 EXP+LOG,而是想清楚:你到底要的是数学意义的乘积,还是业务意义上“忽略零后的累积效果”,后者往往不需要那么“纯”的数学解法。

















