SQL Server: условный агрегат;

У меня есть таблица, которая выглядит так:

  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. Проблема с созданной мной функцией заключается в том, что мне нужно ухватить результаты из предыдущей строки, чтобы добавить к значению, когда значение положительное.

Я всю ночь напролет искал подсказки и без радости...

EDIT: подумайте об этом как о значениях CPI за год, которые будут применяться к вашему счету сотового телефона вашим перевозчиком... Они только увеличат ваш счет по CPI и никогда не уменьшат его (если индекс CPI отрицательный)... но они будут компенсировать отрицательный ИПЦ прошлых лет по текущему ИПЦ в текущем году, если ИПЦ текущего года будет положительным (или сумма приведет к положительному)...

Это может или не может помочь, но это ситуация lol.

Ответ 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, если такого не найдено.

Ответ 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

Идея состоит в том, чтобы идентифицировать острова строк, в которых применяется текущая сумма.

Демо здесь

Ответ 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 в наборе данных для использования в соединениях для учета ситуации, если в вашем наборе данных отсутствует что-то из года.
  • Затем создайте рекурсивную строку cte и step 1 за раз, используя номер строки, и определите, должно ли агрегированное значение быть reset или добавлено в зависимости от того, является ли предыдущее значение строк положительным или отрицательным.

Вот результаты ответов 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

Вы можете увидеть, что проблема с AggCol для 2015 года неверна.

Обратите внимание: я думаю, что ответы - это отличные попытки и показать некоторые реальные навыки/код, когда дело доходит до пробелов/островов. Я не пытаюсь атаковать, просто повышаю качество сообщения.