วิธีรับผลรวมสะสม


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) โดยใช้เคอร์เซอร์และ / หรือในขณะที่ลูป แต่จากนั้นคุณใช้เคอร์เซอร์ ( blech! )
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 |

แก้ไข:นี่เป็นโซลูชันทั่วไปที่จะทำงานบนแพลตฟอร์ม db ส่วนใหญ่ เมื่อมีทางออกที่ดีกว่าสำหรับแพลตฟอร์มเฉพาะของคุณ (เช่น gareth's) ให้ใช้มัน!


12
@ Frankanklin คุ้มค่าสำหรับโต๊ะเล็ก ๆ เท่านั้น ต้นทุนเพิ่มขึ้นตามสัดส่วนของกำลังสองของจำนวนแถว SQL Server 2012 ช่วยให้สามารถดำเนินการได้อย่างมีประสิทธิภาพยิ่งขึ้น
Martin Smith

3
FWIW ฉันมีข้อนิ้วของฉันถูกตีเมื่อทำเช่นนี้โดย DBA ฉันคิดว่าเหตุผลคือมันแพงจริงๆเร็วจริงๆ เป็นที่กล่าวมานี้เป็นคำถามสัมภาษณ์ที่ดีเป็นนักวิเคราะห์ข้อมูลมากที่สุด / นักวิทยาศาสตร์ควรจะได้มีการแก้ปัญหานี้ครั้งหรือสองครั้ง :)
BenDundee

@BenDundee เห็นด้วย - ฉันมักจะให้โซลูชัน SQL ทั่วไปที่จะทำงานบนแพลตฟอร์ม db ส่วนใหญ่ เช่นเคยเมื่อมีวิธีการที่ดีกว่าเช่น gareths ใช้มัน!
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ๆ ฉันไม่เห็นได้ชัดว่าจะใช้งานได้ทันที แต่ :-)
Simon_Weaver

พร้อมใช้งานใน 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 

ที่นี่ฉันกำลังเข้าร่วมตารางเดียวกัน (เข้าร่วมด้วยตนเอง)

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 แล้วเราจะได้ 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

สำหรับ 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 เป็น (เลือก row_number () มากกว่า (เรียงตาม id, somevalue) UniqueID, * จาก 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และเปรียบเทียบซีพียูและเวลาที่แท้จริง
Davos

4

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


มันเป็นวิธีที่ชาญฉลาดอย่างมากในการบรรลุผลและคุณสามารถเพิ่มเงื่อนไขหลายอย่างลงในผลรวม
RaRdEvA

@RaRdEvA มันไม่ได้ยอดเยี่ยมสำหรับประสิทธิภาพ แต่มันทำงานcorrelated subqueryสำหรับทุก ๆ แถวของชุดผลลัพธ์สแกนแถวมากขึ้นเรื่อย ๆ มันไม่ได้ทำงานรวมและสแกนข้อมูลเมื่อฟังก์ชั่นหน้าต่างสามารถทำได้
Davos

1
@Davos คุณพูดถูกถ้าคุณใช้มันจะได้รับช้ากว่า 100,000 รายการ
RaRdEvA

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

ด้านบน (Pre-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 ได้รับการสนับสนุนอย่าง จำกัด สำหรับฟังก์ชันหน้าต่าง
Peter Smit

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 ซึ่งรวม "ROWS ระหว่างที่ได้รับการกำหนดล่วงหน้าและแถวปัจจุบัน" และ "ผลรวม" ทำสิ่งที่ฉันต้องการเพื่อให้บรรลุ ขอบคุณมาก!

ถ้าสามารถช่วยใครได้นี่เป็นกรณีของฉัน ฉันต้องการสะสม +1 ในคอลัมน์เมื่อใดก็ตามที่ผู้ผลิตพบว่าเป็น "ผู้สร้างบางคน" (ตัวอย่าง) ถ้าไม่ใช่จะไม่มีการเพิ่ม แต่แสดงผลการเพิ่มก่อนหน้านี้

ดังนั้น 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, พบ Maker บางคนและเราทำ +1 สำหรับผู้ใช้ 1 พบว่า MakerC เราจึงไม่ทำ +1 แต่นับจำนวนผู้ติดตั้งแบบบางส่วนในแนวตั้งติดเป็น 2 จนกระทั่งแถวถัดไป การแบ่งพาร์ติชันเป็นของผู้ใช้ดังนั้นเมื่อเราเปลี่ยนผู้ใช้การนับสะสมจะกลับเป็นศูนย์

ฉันอยู่ในที่ทำงานฉันไม่ต้องการทำบุญใด ๆ กับคำตอบนี้แค่พูดขอบคุณและแสดงตัวอย่างของฉันในกรณีที่มีคนอยู่ในสถานการณ์เดียวกัน ฉันพยายามที่จะรวม SUM และพาร์ทิชัน แต่ไวยากรณ์ที่น่าทึ่ง "ROWS ระหว่างที่ได้รับการยอมรับล่วงหน้าและแถวปัจจุบัน" เสร็จงาน

ขอบคุณ! 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

ตัวอย่างเช่น: หากคุณมีตารางที่มีสองคอลัมน์หนึ่งคือ ID และที่สองคือตัวเลขและต้องการหาผลรวมสะสม

SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.