Как получить накопительную сумму


186
declare  @t table
    (
        id int,
        SomeNumt int
    )

insert into @t
select 1,10
union
select 2,12
union
select 3,3
union
select 4,15
union
select 5,23


select * from @t

Приведенный выше выбор возвращает мне следующее.

id  SomeNumt
1   10
2   12
3   3
4   15
5   23

Как мне получить следующее:

id  srome   CumSrome
1   10  10
2   12  22
3   3   25
4   15  40
5   23  63

5
Получить итоговые данные в T-SQL не сложно, есть много правильных ответов, большинство из которых довольно просты. Что непросто (или даже возможно в настоящее время), так это написать настоящий запрос в T-SQL для эффективного выполнения итогов. Все они O (n ^ 2), хотя они могут легко быть O (n), за исключением того, что T-SQL не оптимизируется для этого случая. Вы можете получить O (n), используя курсоры и / или циклы while, но затем вы используете курсоры. ( блех! )
RBarryYoung

Ответы:


226
select t1.id, t1.SomeNumt, SUM(t2.SomeNumt) as sum
from @t t1
inner join @t t2 on t1.id >= t2.id
group by t1.id, t1.SomeNumt
order by t1.id

Пример SQL Fiddle

Вывод

| ID | SOMENUMT | SUM |
-----------------------
|  1 |       10 |  10 |
|  2 |       12 |  22 |
|  3 |        3 |  25 |
|  4 |       15 |  40 |
|  5 |       23 |  63 |

Изменить: это обобщенное решение, которое будет работать на большинстве платформ БД. Когда есть лучшее решение для вашей конкретной платформы (например, Gareth), используйте его!


12
@Franklin Только экономически выгодно для небольших столов. Стоимость растет пропорционально квадрату количества рядов. SQL Server 2012 позволяет сделать это намного эффективнее.
Мартин Смит

3
FWIW, мне повредили мои пальцы, когда я делал это администратором базы данных. Я думаю, причина в том, что это очень дорого, очень быстро. Тем не менее, это отличный вопрос для интервью, так как большинству аналитиков / ученых,
занимающихся

@BenDundee Согласовано - я склонен предоставлять обобщенные решения SQL, которые будут работать на большинстве платформ БД. Как всегда, когда есть лучший подход, например, гареты, используйте его!
RedFilter,

199

Последняя версия SQL Server (2012) позволяет следующее.

SELECT 
    RowID, 
    Col1,
    SUM(Col1) OVER(ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId

или

SELECT 
    GroupID, 
    RowID, 
    Col1,
    SUM(Col1) OVER(PARTITION BY GroupID ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId

Это еще быстрее. Разбитая версия завершается за 34 секунды более 5 миллионов строк для меня.

Спасибо Песо, который прокомментировал поток SQL Team, упомянутый в другом ответе.


22
Для краткости вы можете использовать ROWS UNBOUNDED PRECEDINGвместо ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
Дан

1
Примечание. Если столбец, который вы хотите суммировать кумулятивно, сам по себе уже является суммой или счетчиком, вы можете либо обернуть все это как внутренний запрос, либо сделать это SUM(COUNT(*)) OVER (ORDER BY RowId ROWS UNBOUNDED PRECEDING) AS CumulativeSum. Для меня не было сразу очевидно, сработает ли это, но это
сработало

Доступно в PostgreSQL с 8.4: postgresql.org/docs/8.4/sql-select.html
ADJenks

27

Для SQL Server 2012 и более поздних версий это может быть легко:

SELECT id, SomeNumt, sum(SomeNumt) OVER (ORDER BY id) as CumSrome FROM @t

потому что ORDER BYпредложение SUMпо умолчанию означает RANGE UNBOUNDED PRECEDING AND CURRENT ROWрамку окна («Общие замечания» по адресу https://msdn.microsoft.com/en-us/library/ms189461.aspx )


13

Версия CTE, просто для удовольствия:

;
WITH  abcd
        AS ( SELECT id
                   ,SomeNumt
                   ,SomeNumt AS MySum
             FROM   @t
             WHERE  id = 1
             UNION ALL
             SELECT t.id
                   ,t.SomeNumt
                   ,t.SomeNumt + a.MySum AS MySum
             FROM   @t AS t
                    JOIN abcd AS a ON a.id = t.id - 1
           )
  SELECT  *  FROM    abcd
OPTION  ( MAXRECURSION 1000 ) -- limit recursion here, or 0 for no limit.

Возвращает:

id          SomeNumt    MySum
----------- ----------- -----------
1           10          10
2           12          22
3           3           25
4           15          40
5           23          63

13

Давайте сначала создадим таблицу с фиктивными данными ->

Create Table CUMULATIVESUM (id tinyint , SomeValue tinyint)

**Now let put some data in the table**

Insert Into CUMULATIVESUM

Select 1, 10 union 
Select 2, 2  union
Select 3, 6  union
Select 4, 10 

здесь я присоединяюсь к той же таблице (SELF Joining)

Select c1.ID, c1.SomeValue, c2.SomeValue
From CumulativeSum c1,  CumulativeSum c2
Where c1.id >= c2.ID
Order By c1.id Asc

РЕЗУЛЬТАТ:

ID  SomeValue   SomeValue
1   10          10
2   2           10
2   2            2
3   6           10
3   6            2
3   6            6
4   10          10
4   10           2
4   10           6
4   10          10

здесь мы идем теперь просто суммируем Somevalue t2, и мы получим ответ

Select c1.ID, c1.SomeValue, Sum(c2.SomeValue) CumulativeSumValue
From CumulativeSum c1,  CumulativeSum c2
Where c1.id >= c2.ID
Group By c1.ID, c1.SomeValue
Order By c1.id Asc

ДЛЯ SQL SERVER 2012 и выше (намного эффективнее)

Select c1.ID, c1.SomeValue, 
SUM (SomeValue) OVER (ORDER BY c1.ID )
From CumulativeSum c1
Order By c1.id Asc

Желаемый результат

ID  SomeValue   CumlativeSumValue
1   10          10
2   2           12
3   6           18
4   10          28

Drop Table CumulativeSum

Очистить dummytable


пожалуйста, отредактируйте свой ответ и отформатируйте код, чтобы сделать его читабельным
kleopatra

Что делать, если значения mi "ID" повторяются? (они, конечно, не являются первичными ключами в моей таблице) Я не смог адаптировать этот запрос к этому случаю?
pablete

AFAIK вам нужен уникальный идентификатор для совокупной суммы, и вы можете получить его с помощью row_number. проверьте этот код ниже:; с NewTBLWITHUNiqueID as (выберите row_number () вместо (order by id, somevalue) UniqueID, * From CUMULATIVESUMwithoutPK)
Neeraj Prasad Sharma

Спасибо @NeerajPrasadSharma, я на самом деле использовал rank()и еще один порядок за предложением для ее решения.
pablete

5

Поздний ответ, но еще одна возможность ...

Генерация накопленной суммы может быть более оптимизирована с помощью CROSS APPLYлогики.

Работает лучше, чем INNER JOIN& OVER Clauseкогда анализируется фактический план запроса ...

/* Create table & populate data */
IF OBJECT_ID('tempdb..#TMP') IS NOT NULL
DROP TABLE #TMP 

SELECT * INTO #TMP 
FROM (
SELECT 1 AS id
UNION 
SELECT 2 AS id
UNION 
SELECT 3 AS id
UNION 
SELECT 4 AS id
UNION 
SELECT 5 AS id
) Tab


/* Using CROSS APPLY 
Query cost relative to the batch 17%
*/    
SELECT   T1.id, 
         T2.CumSum 
FROM     #TMP T1 
         CROSS APPLY ( 
         SELECT   SUM(T2.id) AS CumSum 
         FROM     #TMP T2 
         WHERE    T1.id >= T2.id
         ) T2

/* Using INNER JOIN 
Query cost relative to the batch 46%
*/
SELECT   T1.id, 
         SUM(T2.id) CumSum
FROM     #TMP T1
         INNER JOIN #TMP T2
                 ON T1.id > = T2.id
GROUP BY T1.id

/* Using OVER clause
Query cost relative to the batch 37%
*/
SELECT   T1.id, 
         SUM(T1.id) OVER( PARTITION BY id)
FROM     #TMP T1

Output:-
  id       CumSum
-------   ------- 
   1         1
   2         3
   3         6
   4         10
   5         15

1
Я не убежден. «Стоимость запроса относительно пакета» - бессмысленная вещь для сравнения производительности запросов. Затраты на запросы - это оценки, используемые планировщиком запросов для быстрого взвешивания различных планов и выбора наименее затратных, но эти затраты предназначены для сравнения планов для одного и того же запроса и не являются релевантными или сопоставимыми между запросами . Этот примерный набор данных также слишком мал, чтобы увидеть какие-либо существенные различия между тремя методами. Попробуйте еще раз с 1 м строк, посмотрите на фактические планы выполнения, попробуйте set io statistics onи сравните процессор и фактическое время.
Давос

4

Select *, (Select SUM(SOMENUMT) From @t S Where S.id <= M.id) From @t M


Это очень разумный способ достижения результата, и вы можете добавить к сумме несколько условий.
РАРДЕВА

@RaRdEvA Хотя это не очень хорошо для производительности, он запускает ее correlated subqueryдля каждой строки набора результатов, сканируя все больше и больше строк. Он не ведет промежуточный итог и сканирует данные один раз, как могут оконные функции.
Давос,

1
@ Давос, вы правы, если вы используете его, он очень медленно работает более 100 000 записей.
РАРДЕВА

2

В этом превосходном посте доступна намного более быстрая реализация CTE: http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx

Проблема в этой теме может быть выражена так:

    DECLARE @RT INT
    SELECT @RT = 0

    ;
    WITH  abcd
            AS ( SELECT TOP 100 percent
                        id
                       ,SomeNumt
                       ,MySum
                       order by id
               )
      update abcd
      set @RT = MySum = @RT + SomeNumt
      output inserted.*


2

Вы можете использовать этот простой запрос для прогрессивного расчета:

select 
   id
  ,SomeNumt
  ,sum(SomeNumt) over(order by id ROWS between UNBOUNDED PRECEDING and CURRENT ROW) as CumSrome
from @t

1

Как только таблица создана -

select 
    A.id, A.SomeNumt, SUM(B.SomeNumt) as sum
    from @t A, @t B where A.id >= B.id
    group by A.id, A.SomeNumt

order by A.id

1

Выше (до SQL12) мы видим такие примеры:

SELECT
    T1.id, SUM(T2.id) AS CumSum
FROM 
    #TMP T1
    JOIN #TMP T2 ON T2.id < = T1.id
GROUP BY
    T1.id

Более эффективным...

SELECT
    T1.id, SUM(T2.id) + T1.id AS CumSum
FROM 
    #TMP T1
    JOIN #TMP T2 ON T2.id < T1.id
GROUP BY
    T1.id

0

Попробуй это

select 
    t.id,
    t.SomeNumt, 
    sum(t.SomeNumt) Over (Order by t.id asc Rows Between Unbounded Preceding and Current Row) as cum
from 
    @t t 
group by
    t.id,
    t.SomeNumt
order by
    t.id asc;

Это работает с SQL Server 2012 и выше, в 2008 году ограничена поддержка оконных функций.
Питер Смит

0

Попробуй это:

CREATE TABLE #t(
 [name] varchar NULL,
 [val] [int] NULL,
 [ID] [int] NULL
) ON [PRIMARY]

insert into #t (id,name,val) values
 (1,'A',10), (2,'B',20), (3,'C',30)

select t1.id, t1.val, SUM(t2.val) as cumSum
 from #t t1 inner join #t t2 on t1.id >= t2.id
 group by t1.id, t1.val order by t1.id

0

Решение SQL, которое сочетает в себе «строки между незанятым PRECEDING и CURRENT ROW», и «SUM» сделали именно то, что я хотел достичь. Спасибо вам большое!

Если это может кому-нибудь помочь, вот мой случай. Я хотел накапливать +1 в столбце всякий раз, когда создатель определяется как «Some Maker» (пример). Если нет, то нет приращения, но отображается предыдущий результат приращения.

Итак, этот кусок SQL:

SUM( CASE [rmaker] WHEN 'Some Maker' THEN  1 ELSE 0 END) 
OVER 
(PARTITION BY UserID ORDER BY UserID,[rrank] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Cumul_CNT

Позволил мне получить что-то вроде этого:

User 1  Rank1   MakerA      0  
User 1  Rank2   MakerB      0  
User 1  Rank3   Some Maker  1  
User 1  Rank4   Some Maker  2  
User 1  Rank5   MakerC      2
User 1  Rank6   Some Maker  3  
User 2  Rank1   MakerA      0  
User 2  Rank2   SomeMaker   1  

Объяснение выше: счетчик "Some Maker" начинается с 0, Some Maker найден, и мы делаем +1. Для пользователя 1 MakerC найден, поэтому мы не делаем +1, но вместо этого вертикальный отсчет Some Maker привязывается к 2 до следующего ряда. Разделение выполняется пользователем, поэтому, когда мы меняем пользователя, кумулятивный счет возвращается к нулю.

Я на работе, я не хочу никаких заслуг в этом ответе, просто скажите спасибо и покажите мой пример на случай, если кто-то окажется в такой же ситуации. Я пытался объединить SUM и PARTITION, но удивительный синтаксис «ROWS МЕЖДУ UNBOUNDED PRECEDING И CURRENT ROW» выполнил задачу.

Спасибо! Groaker


0

Без использования какого-либо типа совокупной заработной платы JOIN для человека, полученного с помощью следующего запроса:

SELECT * , (
  SELECT SUM( salary ) 
  FROM  `abc` AS table1
  WHERE table1.ID <=  `abc`.ID
    AND table1.name =  `abc`.Name
) AS cum
FROM  `abc` 
ORDER BY Name

0

Например, если у вас есть таблица с двумя столбцами, один из которых является идентификатором, а второй - числом, и вы хотите узнать совокупную сумму.

SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.