SQL Server:条件聚合;

Posted

技术标签:

【中文标题】SQL Server:条件聚合;【英文标题】:SQL Server : conditional aggregate ; 【发布时间】:2015-12-22 16:20:25 【问题描述】:

我有一个如下所示的表格:

  Year       Value
  -----------------
  2013      -0.0016
  2014      -0.0001
  2015       0.0025
  2016      -0.0003
  2017       0.0023
  2018       0.0002

我需要执行一个条件聚合来生成一个新列。条件如下:

如果值为负,则聚合开始,直到值为正时才停止。然后什么都没有,直到值再次为负......结果将如下所示:

  Year       Value        AggCol
  2013      -0.0016      -0.0016
  2014      -0.0001      -0.0017
  2015       0.0025       0.0008
  2016      -0.0003      -0.0003
  2017       0.0023       0.002
  2018       0.0002       0.0002

这个 udf 和我得到的一样接近:

create function dbo.fn(@cYear numeric, @rate float)
returns float
as 
begin
    declare @pYear numeric
    declare @return float

    set @pYear = @cYear - 1

    set @return = (select 
                        case 
                            when Value < 0 and @rate > 0  then null 
                            when Value < 0 then Value + @rate
                            else @rate 
                        end 
                   from Table1 
                   where [year] = @pYear)

    return @return
end

如果这会更容易但更喜欢 SQL,我可以在 c# 中回答。我制作的函数的问题是,当值为正时,我需要能够从上一行中获取结果以添加到值中。

我在这里彻夜未眠,寻找线索却没有乐趣......

编辑:因此,将这些视为您的运营商将应用于您的手机账单的年度 CPI 值......他们只会根据 CPI 增加您的账单,而不会减少它(如果 CPI 为负数) ...但如果当年的 CPI 为正(或总和为正),它们将用当年的 CPI 抵消前几年的负 CPI...

这可能有帮助,也可能没有帮助,但情况就是这样,哈哈。

【问题讨论】:

您使用的是什么版本的 SQL Server? 您可以使用 SQL Server 中的查询来执行此操作,但它需要递归 CTE。 2012 年,我对 CTE 没问题...有 dbo 访问权限,所以可以做任何需要的事情。 “直到值为正”,你是指表中的值还是累加和? 直到表中的值为正。 【参考方案1】:
DECLARE @t TABLE ( [Year] INT, Value MONEY )

INSERT  INTO @t
VALUES  ( 2013, -0.0016 ),
        ( 2014, -0.0001 ),
        ( 2015, 0.0025 ),
        ( 2016, -0.0003 ),
        ( 2017, 0.0023 ),
        ( 2018, 0.0002 )

SELECT  t1.Year ,
        t1.Value ,
        oa.AggCol
FROM    @t t1
        OUTER APPLY ( SELECT    SUM(Value) AS AggCol
                      FROM      @t t2
                      WHERE     Year <= t1.Year
                                AND Year > ( SELECT ISNULL(MAX(Year), 0)
                                             FROM   @t
                                             WHERE  Year < t1.Year AND Value > 0)
                    ) oa

输出:

Year    Value    AggCol
2013    -0.0016  -0.0016
2014    -0.0001  -0.0017
2015    0.0025   0.0008
2016    -0.0003  -0.0003
2017    0.0023   0.002
2018    0.0002   0.0002

这意味着:对于每一行,给我一个小于或等于当前行且大于当前行之前出现的具有正值的最大行的值的总和,如果没有找到,则从 0 开始。

【讨论】:

你先生是个天生的怪物!我的意思是,以最恭维的方式,因为我被你回答这个问题的速度所震撼!非常感谢!!!! @user3486773,也感谢您提出有趣的问题。 这是一个很好的尝试,适用于提供的测试数据,但是当需要多个正值以使运行总和成为正数时,它就会崩溃。例如将 2014 更改为 .0001 并且 2015 年将出现错误的值。【参考方案2】:

你也可以使用窗口函数来做到这一点:

;WITH PrevValues AS (
   SELECT Year, Value,
          LAG(Value) OVER (ORDER BY Year) AS prevValue
   FROM Table1
), Flags AS (
  SELECT Year, Value,
         CASE 
            WHEN Value < 0 AND prevValue > 0 THEN 2  -- next slice
            WHEN Value < 0 OR prevValue < 0  THEN 1  -- same slice
            WHEN Value > 0 AND prevValue > 0 THEN -1 -- not in a slice
         END AS flag
  FROM PrevValues
), Islands AS (
  SELECT Year, Value,    
       CASE 
          WHEN flag = -1 THEN -1   
          ELSE SUM(flag) OVER (ORDER BY Year)      
               -
               ROW_NUMBER() OVER (ORDER BY Year) 
       END AS grp
  FROM Flags
)
SELECT Year, Value,
       CASE 
          WHEN grp = -1 THEN Value
          ELSE SUM(Value) OVER (PARTITION BY grp ORDER BY Year) 
       END AS AggCol
FROM Islands
ORDER BY Year

这个想法是确定应用运行总和的行岛。

Demo here

【讨论】:

这是一个很好的尝试,适用于提供的测试数据,但是当需要多个正值以使运行总和成为正数时,它就会崩溃。例如将 2014 更改为 .0001 并且 2015 年将出现错误的值。【参考方案3】:
DECLARE @t TABLE ( [Year] INT, Value MONEY )
INSERT  INTO @t
VALUES (2013,-0.0016),(2014,0.0001),(2015,0.0025),(2016,-0.0003),(2017,0.0023),(2018,0.0002)

;WITH cteRowNum AS (
    SELECT *, ROW_NUMBER() OVER (ORDER BY Year) as RowNum
    FROM
       @t
)
, cteRecursive AS (
    SELECT
       Year
       ,Value
       ,Value as AggCol
       ,RowNum
    FROM
       cteRowNum
    WHERe
       RowNum = 1

    UNION ALL

    SELECT
       c.Year
       ,c.Value
       ,CASE
          WHEN AggCol >= 0 THEN c.Value
          ELSE AggCol + c.Value
       END
       ,c.RowNum
    FROM
       cteRecursive r
       INNER JOIN cteRowNum c
       ON r.RowNum + 1 = c.RowNum
)

SELECT Year, Value, AggCol
FROM
    cteRecursive

请注意,这与您提供的数据集不同!这是结果

Year    Value        AggCol
2013    -0.0016     -0.0016
2014     0.0001     -0.0015
2015     0.0025      0.001
2016    -0.0003     -0.0003
2017     0.0023      0.002
2018     0.0002      0.0002

原始测试数据的问题在于,它没有考虑需要多个连续的正记录才能使运行总和为正的情况。 随后,我发布答案时的其他两个答案都是错误的。所以我只将 2014 年的记录更改为正数 .0001,您可以看到这个解决方案是如何工作的,而其他解决方案则没有。

可能有一些方法可以使用窗口函数来做到这一点,但递归 cte 非常简单,所以我走这条路:

首先在数据集上构建一个 row_number 以用于连接,以说明数据集中缺少 YEAR 或其他情况的情况。 接下来使用行号一次构建递归 cte 和第 1 行,并根据之前的行值是正值还是负值来确定是否需要重置或添加聚合值。

如果您对测试数据进行更改,以下是 Giorgos 和 Giorgi 的回答结果:

Year    Value        AggCol
2013    -0.0016     -0.0016
2014     0.0001     -0.0015
2015     0.0025      0.0025
2016    -0.0003     -0.0003
2017     0.0023      0.002
2018     0.0002      0.0002

您可以看到 2015 年 AggCol 的问题是错误的

请注意,我认为答案是很好的尝试,并且在涉及间隙/孤岛时显示了一些真正的技能/代码。我不是想攻击,只是为了提高帖子的质量。

【讨论】:

以上是关于SQL Server:条件聚合;的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server SUM IF 使用具有多个条件的窗口函数

SQL基础教程(第2版)第3章 聚合与排序:3-3 为聚合结果指定条件

SQL Server基础优化

查询分组中的 Oracle SQL 条件聚合函数

Pandas 条件聚合和非条件聚合在一起

如何根据 PySpark 中窗口聚合的条件计算不同值?