为啥 SQL 中没有 PRODUCT 聚合函数?
Posted
技术标签:
【中文标题】为啥 SQL 中没有 PRODUCT 聚合函数?【英文标题】:Why is there no PRODUCT aggregate function in SQL?为什么 SQL 中没有 PRODUCT 聚合函数? 【发布时间】:2011-04-24 03:44:08 【问题描述】:我正在寻找类似于 SELECT PRODUCT(table.price) FROM table GROUP BY table.sale
的东西,类似于 SUM
的工作方式。
我是否遗漏了文档中的某些内容,或者真的没有 PRODUCT
函数?
如果是这样,为什么不呢?
注意:我在 postgres、mysql 和 mssql 中查找了该函数,但没有找到,所以我假设所有 sql 都不支持它。
【问题讨论】:
我猜想在一组数字上计算乘积比求和要难得多。即使在您的示例中,您希望多久计算一次一组价格的乘积? product(table.price) 应该回答的价值是多少?阅读“产品聚合函数”,我知道它是返回结果集中找到的所有 table.price 值的乘积:Row1.Price * Row2.Price * ... * RowN.Price。但是对于我的生活,我无法理解这个价值“意味着”什么,它应该传达什么信息?对于价格或任何其他类型的价值,它的实际应用是什么?请赐教。 就我而言,它实际上不是为了价格,而是为了获得收益的乘积(qty_out / qty_in)。 @MarjanVenema 将我带到这里的用例:risk
和 risk_prevention
表中的 eevry 风险有一个 damage_value
代表处于风险中的金额。每个risk_prevention
都有一个risk_multiplier
>0 和damage_value * 所有risk_prevention
.risk_multiplier
。这个逻辑不是我的身份。它是客户正在使用的以及客户在软件中想要的。 (抱歉伪代码不好)
当 var 始终为正时,exp(sum(log(var1)) 是乘积(var1)。
【参考方案1】:
您可以执行产品聚合函数,但您必须自己进行数学运算,就像这样...
SELECT
Exp(Sum(IIf(Abs([Num])=0,0,Log(Abs([Num])))))*IIf(Min(Abs([Num]))=0,0,1)*(1-2*(Sum(IIf([Num]>=0,0,1)) Mod 2)) AS P
FROM
Table1
来源:http://productfunctionsql.codeplex.com/
【讨论】:
【参考方案2】:我不知道为什么没有,但是(多注意负数)你可以使用日志和指数来做:-
select exp (sum (ln (table.price))) from table ...
【讨论】:
如果要计算整数列值的乘积,请添加 round()。有时会返回 .9999... 而不是整个 int。 这很聪明 在 T-SQL 中,select exp (sum (log (table.price)))
【参考方案3】:
对于 MSSQL,您可以使用它。它可以被其他平台采用:它只是数学和对数聚合。
SELECT
GrpID,
CASE
WHEN MinVal = 0 THEN 0
WHEN Neg % 2 = 1 THEN -1 * EXP(ABSMult)
ELSE EXP(ABSMult)
END
FROM
(
SELECT
GrpID,
--log of +ve row values
SUM(LOG(ABS(NULLIF(Value, 0)))) AS ABSMult,
--count of -ve values. Even = +ve result.
SUM(SIGN(CASE WHEN Value < 0 THEN 1 ELSE 0 END)) AS Neg,
--anything * zero = zero
MIN(ABS(Value)) AS MinVal
FROM
Mytable
GROUP BY
GrpID
) foo
取自我在这里的回答:SQL Server Query - groupwise multiplication
【讨论】:
"这只是数学和对数的聚合" :)log(a*b*c...*n)=log(a)+log(b)+log(c)...+log(n)
快速提问。为此,您必须对 log 和 power 函数使用相同的基础,对吗?但是 LOG 和 EXP 不在同一个底上运算:LOG 以 10 为底,而 EXP 以 e 为底。所以正确的答案是使用 LN 代替 LOG 或 10^ABSMult 代替 EXP(ABSMult)。对吗?
在 SQL Server 中有 LOG(以 e 为底)和 LOG10(以 10 为底)。裸日志函数现在有一个可选的基本参数。 docs.microsoft.com/en-us/sql/t-sql/functions/log-transact-sql 和 docs.microsoft.com/en-us/sql/t-sql/functions/log10-transact-sql
确认!我们的问题是我们在 SQL Server 中使用了您的代码,后来将其移植到 Teradata,其中 LOG() 是 base 10。显然,我们的计算突然间没有明显的原因。我很确定我已经看到 LOG 为基数 2 的语言(不一定是 SQL)。由于这个问题被标记为一般 SQL,因此我们的主要信息是检查目标实现中 LOG 函数的行为如果没有得到预期的结果。顺便说一句,绝妙的想法和实施!【参考方案4】:
SQL 标准中没有PRODUCT
设置函数。不过,它似乎是一个有价值的候选者(与 CONCATENATE
集合函数不同:它不适合 SQL,例如,生成的数据类型将涉及多值并在第一范式方面造成问题)。
SQL 标准旨在整合大约 1990 年的 SQL 产品的功能,并为未来的发展提供“思想领导力”。简而言之,它们记录了 SQL 做什么以及 SQL 应该做什么。 PRODUCT
set 函数的缺失表明,在 1990 年,尽管它没有值得包含的供应商,并且没有学术兴趣将其引入标准。
当然,供应商一直在寻求添加自己的功能,这些天通常作为标准的扩展而不是切线。我不记得在我使用过的任何 SQL 产品中看到过 PRODUCT
设置函数(甚至是对一个函数的需求)。
在任何情况下,使用log
和exp
标量函数(以及处理负数的逻辑)和SUM
set 函数都相当简单;有关一些示例代码,请参阅@gbn 的答案。不过,我从来不需要在业务应用程序中执行此操作。
总之,我的最佳猜测是 SQL 最终用户不需要 PRODUCT
设置函数;此外,任何对学术感兴趣的人都可能会发现可以接受的解决方法(即不会重视 PRODUCT
set 函数将提供的语法糖)。
出于兴趣,SQL Server Land 确实需要新的集合函数,但需要窗口函数种类(以及标准 SQL)。有关更多详细信息,包括如何参与进一步推动需求,请参阅Itzik Ben-Gan's blog。
【讨论】:
+1 "SQL 标准中没有 PRODUCT 集函数。不过,它似乎是一个值得候选的对象" - 几何平均函数也是如此。 不幸的是,这个答案是完全错误的。 exp(sum(log(column))) 适用于正数或查看下面更好的答案。 马克,地理平均值是这样的:exp(avg(log(x))) 这个典故是这样的:当已经有办法做某事时,吵闹就会被静音。无论如何,sql 的传统重点是会计而不是数据科学。【参考方案5】:我认为这是因为没有编号系统能够容纳许多产品。由于数据库是为大量记录而设计的,因此 1000 个数字的乘积将非常庞大,如果是浮点数,传播的错误将是巨大的。
另请注意,使用日志可能是一个危险的解决方案。尽管在数学上 log(a*b) = log(a)*log(b),但它可能不在计算机中,因为我们不是在处理实数。如果你计算 2^(log(a)+log(b)) 而不是 a*b,你可能会得到意想不到的结果。例如:
选择 9999999999*99999999974482, EXP(LOG(9999999999)+LOG(99999999974482))
在 Sql Server 中返回
999999999644820000025518, 9.99999999644812E+23
所以我的意思是,当你尝试做产品时,要小心谨慎,并且要进行大量测试。
【讨论】:
【参考方案6】:处理此问题的一种方法(如果您使用脚本语言)是使用 group_concat 函数。
例如SELECT group_concat(table.price) FROM table GROUP BY table.sale
这将返回一个字符串,其中包含相同销售价值的所有价格,以逗号分隔。 然后使用解析器,您可以获得每个价格,并进行乘法运算。 (在php中你甚至可以使用array_reduce函数,实际上在php.net manual你得到了一个合适的例子)。
干杯
【讨论】:
【参考方案7】:T-SQL 中有一个巧妙的技巧(不确定它是否是 ANSI),它允许将一组行中的字符串值连接到一个变量中。看起来它也适用于乘法:
declare @Floats as table (value float)
insert into @Floats values (0.9)
insert into @Floats values (0.9)
insert into @Floats values (0.9)
declare @multiplier float = null
select
@multiplier = isnull(@multiplier, '1') * value
from @Floats
select @multiplier
这可能比 log/exp 解决方案在数值上更稳定。
【讨论】:
【参考方案8】:可以使用现代 SQL 功能(例如窗口函数和 CTE)来解决该问题。一切都是标准 SQL,并且 - 与基于对数的解决方案不同 - 不需要从整数世界切换到浮点世界,也不需要处理非正数。只需对行进行编号并在递归查询中评估产品,直到没有行剩余:
with recursive t(c) as (
select unnest(array[2,5,7,8])
), r(c,n) as (
select t.c, row_number() over () from t
), p(c,n) as (
select c, n from r where n = 1
union all
select r.c * p.c, r.n from p join r on p.n + 1 = r.n
)
select c from p where n = (select max(n) from p);
由于您的问题涉及按销售列分组,所以事情有点复杂,但仍然可以解决:
with recursive t(sale,price) as (
select 'multiplication', 2 union
select 'multiplication', 5 union
select 'multiplication', 7 union
select 'multiplication', 8 union
select 'trivial', 1 union
select 'trivial', 8 union
select 'negatives work', -2 union
select 'negatives work', -3 union
select 'negatives work', -5 union
select 'look ma, zero works too!', 1 union
select 'look ma, zero works too!', 0 union
select 'look ma, zero works too!', 2
), r(sale,price,n,maxn) as (
select t.sale, t.price, row_number() over (partition by sale), count(1) over (partition by sale)
from t
), p(sale,price,n,maxn) as (
select sale, price, n, maxn
from r where n = 1
union all
select p.sale, r.price * p.price, r.n, r.maxn
from p
join r on p.sale = r.sale and p.n + 1 = r.n
)
select sale, price
from p
where n = maxn
order by sale;
结果:
sale,price
"look ma, zero works too!",0
multiplication,560
negatives work,-30
trivial,8
在 Postgres 上测试。
【讨论】:
Connor McDonald 在他的精彩文章 connor-mcdonald.com/2020/11/18/… 中提到了这个答案。还提到了另一种解决方案(XML,模型子句)并从性能角度进行了分析。我完全同意 Connor 的观点,最有效的方法是在 Oracle 环境中自定义聚合函数。【参考方案9】:另一种方法基于笛卡尔积的基数是特定集合的基数的乘积;-)
⚠ 警告:这个例子只是为了好玩,比较学术,不要在生产中使用它! (除了它仅适用于正整数和实际上很小的整数)⚠
with recursive t(c) as (
select unnest(array[2,5,7,8])
), p(a) as (
select array_agg(c) from t
union all
select p.a[2:]
from p
cross join generate_series(1, p.a[1])
)
select count(*) from p where cardinality(a) = 0;
【讨论】:
【参考方案10】:这是任何需要它的人的预言机解决方案
with data(id, val) as(
select 1,1.0 from dual union all
select 2,-2.0 from dual union all
select 3,1.0 from dual union all
select 4,2.0 from dual
),
neg(val , modifier) as(
select exp(sum(ln(abs(val)))), case when mod(count(*),2) = 0 then 1 Else -1 end
from data
where val <0
)
,
pos(val) as (
select exp(sum(ln(val)))
from data
where val >=0
)
select (select val*modifier from neg)*(select val from pos) product from dual
【讨论】:
以上是关于为啥 SQL 中没有 PRODUCT 聚合函数?的主要内容,如果未能解决你的问题,请参考以下文章