【问题标题】: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)。

标签: sql aggregate


【解决方案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-sqldocs.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
            

            【讨论】:

              猜你喜欢
              • 2014-02-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-08-15
              • 1970-01-01
              • 2020-06-22
              • 2013-03-19
              相关资源
              最近更新 更多