Jak uzyskać łączną sumę


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

powyższy wybór zwraca mi następujące.

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

Jak uzyskać następujące informacje:

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

5
Sprawdzanie sum w T-SQL nie jest trudne, istnieje wiele poprawnych odpowiedzi, z których większość jest dość łatwa. To, co nie jest łatwe (a nawet możliwe w tym momencie), to napisanie prawdziwego zapytania w języku T-SQL, aby uruchomić wydajne sumy. Wszystkie mają wartość O (n ^ 2), choć łatwo mogą oznaczać O (n), z wyjątkiem tego, że T-SQL nie optymalizuje się w tym przypadku. Możesz uzyskać O (n) za pomocą kursorów i / lub pętli While, ale wtedy używasz kursorów. ( blech! )
RBarryYoung

Odpowiedzi:


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

Przykład Fiddle SQL

Wynik

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

Edycja: jest to uogólnione rozwiązanie, które będzie działać na większości platform db. Jeśli dostępne jest lepsze rozwiązanie dla Twojej platformy (np. Gareth), skorzystaj z niego!


12
@Franklin Opłacalne tylko w przypadku małych stolików. Koszt rośnie proporcjonalnie do kwadratu liczby rzędów. SQL Server 2012 pozwala to zrobić znacznie wydajniej.
Martin Smith

3
FWIW, moje kostki zostały uderzone podczas robienia tego przez DBA. Myślę, że powodem jest to, że robi się naprawdę drogo, naprawdę szybko. To powiedziawszy, jest to świetne pytanie do wywiadu, ponieważ większość analityków danych / naukowców powinna była rozwiązać ten problem raz lub dwa razy :)
BenDundee

@BenDundee Agreed - zazwyczaj udostępniam uogólnione rozwiązania SQL, które będą działać na większości platform db. Jak zawsze, gdy dostępne jest lepsze podejście, np. Gareths, skorzystaj z niego!
RedFilter

199

Najnowsza wersja SQL Server (2012) pozwala na:

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

lub

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

To jest jeszcze szybsze. Wersja podzielona na partycje kończy się w 34 sekundach dla mnie w ponad 5 milionach wierszy.

Dzięki Peso, który skomentował wątek SQL Team, o którym mowa w innej odpowiedzi.


22
Dla zwięzłości możesz użyć ROWS UNBOUNDED PRECEDINGzamiast ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
Dan

1
Uwaga: jeśli kolumna, którą chcesz sumować, jest już sumą lub policzeniem, możesz owinąć całość jako wewnętrzne zapytanie lub możesz to zrobić SUM(COUNT(*)) OVER (ORDER BY RowId ROWS UNBOUNDED PRECEDING) AS CumulativeSum. Nie od razu było dla mnie oczywiste, czy to zadziała, ale zadziałało :-)
Simon_Weaver

Dostępne w PostgreSQL od 8.4: postgresql.org/docs/8.4/sql-select.html
ADJenks


13

Wersja CTE, dla zabawy:

;
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.

Zwroty:

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

13

Najpierw utwórz tabelę z obojętnymi danymi ->

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 

tutaj dołączam do tego samego stołu (SELF Joining)

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

WYNIK:

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

zaczynamy teraz sumując Somevalue t2 i otrzymamy ans

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

DLA SQL SERVER 2012 i nowszych (znacznie lepsza wydajność)

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

Pożądany rezultat

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

Drop Table CumulativeSum

Wyczyść dummitable


edytuj swoją odpowiedź i sformatuj kod, aby był czytelny
kleopatra,

Co się stanie, jeśli wartości mi „ID” zostaną powtórzone? (nie są oczywiście kluczem podstawowym w mojej tabeli) Nie mogłem dostosować tego zapytania do tej sprawy?
pabletek

AFAIK potrzebujesz unikalnego identyfikatora do skumulowanej sumy i możesz go uzyskać za pomocą numer_wiersza. sprawdź poniższy kod:; z NewTBLWITHUNiqueID jako (wybierz numer_wiersza () powyżej (zamów według id, gdzieś) UniqueID, * From CUMULATIVESUMwithoutPK)
Neeraj Prasad Sharma

Dzięki @NeerajPrasadSharma, faktycznie użyłem rank()i kolejne zamówienie według klauzuli, aby go rozwiązać.
paczka

5

Spóźniona odpowiedź, ale pokazująca jeszcze jedną możliwość ...

Generowanie sumy skumulowanej można bardziej zoptymalizować za pomocą CROSS APPLYlogiki.

Działa lepiej niż INNER JOINi OVER Clausepodczas analizy rzeczywistego planu zapytań ...

/* 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
Nie jestem przekonany. „Koszt zapytania w stosunku do partii” nie ma znaczenia dla porównywania wydajności zapytań. Koszty zapytania są szacunkami używanymi przez planistę zapytań do szybkiego zważenia różnych planów i wybrania najmniej kosztownego, ale koszty te służą do porównania planów dla tego samego zapytania i wcale nie są odpowiednie ani porównywalne między zapytaniami . Ten przykładowy zestaw danych jest również zbyt mały, aby zauważyć znaczącą różnicę między trzema metodami. Spróbuj ponownie z rzędami 1m, spójrz na rzeczywiste plany wykonania, spróbuj set io statistics oni porównaj procesor i rzeczywiste czasy.
Davos

4

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


Jest to bardzo sprytny sposób na osiągnięcie wyniku, a do sumy można dodać wiele warunków.
RaRdEvA

@RaRdEvA Nie jest to jednak świetne pod względem wydajności, uruchamia to correlated subquerydla każdego wiersza zestawu wyników, skanując coraz więcej wierszy. Nie utrzymuje bieżącej sumy i skanuje dane tak, jak potrafią to funkcje okna.
Davos

1
@Davos masz rację, jeśli go użyjesz, stanie się bardzo wolny ponad 100 000 rekordów.
RaRdEvA


2

Możesz użyć tego prostego zapytania do obliczeń progresywnych:

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

1

Po utworzeniu tabeli -

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

Powyżej (przed SQL12) widzimy takie przykłady: -

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

Bardziej wydajny...

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

Spróbuj tego

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;

Działa to z SQL Server 2012 i nowszymi wersjami, 2008 ma ograniczoną obsługę funkcji okien.
Peter Smit

0

Spróbuj tego:

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

Rozwiązanie SQL, które łączy „WIERSZE MIĘDZY BEZPIECZNYM WSTĘPNYM A BIEŻĄCYM WIERSZEM” i „SUM” zrobiło dokładnie to, co chciałem osiągnąć. Dziękuję bardzo!

Jeśli to może komukolwiek pomóc, oto moja sprawa. Chciałem kumulować +1 w kolumnie, ilekroć twórca zostanie znaleziony jako „Some Maker” (przykład). Jeśli nie, nie przyrost, ale pokaż poprzedni wynik przyrostu.

Więc ten fragment 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

Pozwolił mi zdobyć coś takiego:

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  

Objaśnienie powyższego: Zaczyna się liczenie „jakiegoś twórcy” od 0, Znaleziono jakiegoś twórcę i dajemy +1. Dla użytkownika 1, MakerC jest znaleziony, więc nie robimy +1, ale zamiast tego pionowa liczba niektórych Maker zostaje zablokowana do 2, aż do następnego rzędu. Partycjonowanie odbywa się według użytkownika, więc kiedy zmieniamy użytkownika, skumulowana liczba wraca do zera.

Jestem w pracy, nie chcę zasługiwać na tę odpowiedź, wystarczy podziękować i pokazać mój przykład na wypadek, gdyby ktoś znalazł się w tej samej sytuacji. Próbowałem połączyć SUMĘ i PARTYCJĘ, ale niesamowita składnia „ROWS MIĘDZY UNBOUNDED PRECEDING A CURRENT ROW” zakończyła zadanie.

Dzięki! Groaker


0

Bez użycia żadnego rodzaju łącznego wynagrodzenia DOŁĄCZ za pobranie osoby za pomocą następującego zapytania:

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

Na przykład: JEŻELI masz tabelę z dwiema kolumnami, jedna to ID, a druga to liczba i chce znaleźć łączną sumę.

SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.