为啥 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 将我带到这里的用例:riskrisk_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 设置函数(甚至是对一个函数的需求)。

在任何情况下,使用logexp 标量函数(以及处理负数的逻辑)和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 聚合函数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不能再where语句中直接使用聚合函数

SQL 聚合函数

为啥聚集函数不能出现在where子句中

SQL内容补充

在sql数据库中,我用聚合函数sum,为啥显示操作数据类型varchar对于sum运算符无效啊?

SQL中的WHERE子句中为啥不允许应用聚集函数呢?请通俗的解释一下或者谈谈自己的见解!