Bagi mereka yang tidak menggunakan SQL Server 2012 atau lebih tinggi, kursor kemungkinan didukung yang paling efisien dan dijamin metode di luar CLR. Ada pendekatan lain seperti "pembaruan unik" yang dapat sedikit lebih cepat tetapi tidak dijamin berfungsi di masa depan, dan tentu saja pendekatan berbasis set dengan profil kinerja hiperbolik saat tabel semakin besar, dan metode CTE rekursif yang sering membutuhkan langsung #tempdb I/O atau mengakibatkan tumpahan yang menghasilkan dampak yang kurang lebih sama.
INNER JOIN - jangan lakukan ini:
Pendekatan berbasis himpunan yang lambat berbentuk:
SELECT t1.TID, t1.amt, RunningTotal = SUM(t2.amt)
FROM dbo.Transactions AS t1
INNER JOIN dbo.Transactions AS t2
ON t1.TID >= t2.TID
GROUP BY t1.TID, t1.amt
ORDER BY t1.TID;
Alasan ini lambat? Saat tabel semakin besar, setiap baris tambahan membutuhkan pembacaan n-1 baris dalam tabel. Ini eksponensial dan terikat untuk kegagalan, batas waktu, atau hanya pengguna yang marah.
Subkueri terkait - jangan lakukan ini juga:
Bentuk subquery sama menyakitkannya untuk alasan yang sama menyakitkannya.
SELECT TID, amt, RunningTotal = amt + COALESCE(
(
SELECT SUM(amt)
FROM dbo.Transactions AS i
WHERE i.TID < o.TID), 0
)
FROM dbo.Transactions AS o
ORDER BY TID;
Pembaruan unik - lakukan ini dengan risiko Anda sendiri:
Metode "pembaruan unik" lebih efisien daripada yang di atas, tetapi perilakunya tidak didokumentasikan, tidak ada jaminan tentang keteraturan, dan perilaku itu mungkin berhasil hari ini tetapi bisa rusak di masa mendatang. Saya memasukkan ini karena ini adalah metode yang populer dan efisien, tetapi bukan berarti saya mendukungnya. Alasan utama saya bahkan menjawab pertanyaan ini alih-alih menutupnya sebagai duplikat adalah karena pertanyaan lain memiliki pembaruan yang unik sebagai jawaban yang diterima.
DECLARE @t TABLE
(
TID INT PRIMARY KEY,
amt INT,
RunningTotal INT
);
DECLARE @RunningTotal INT = 0;
INSERT @t(TID, amt, RunningTotal)
SELECT TID, amt, RunningTotal = 0
FROM dbo.Transactions
ORDER BY TID;
UPDATE @t
SET @RunningTotal = RunningTotal = @RunningTotal + amt
FROM @t;
SELECT TID, amt, RunningTotal
FROM @t
ORDER BY TID;
CTE rekursif
Yang pertama ini mengandalkan TID agar berdekatan, tidak ada celah:
;WITH x AS
(
SELECT TID, amt, RunningTotal = amt
FROM dbo.Transactions
WHERE TID = 1
UNION ALL
SELECT y.TID, y.amt, x.RunningTotal + y.amt
FROM x
INNER JOIN dbo.Transactions AS y
ON y.TID = x.TID + 1
)
SELECT TID, amt, RunningTotal
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
Jika Anda tidak dapat mengandalkan ini, maka Anda dapat menggunakan variasi ini, yang hanya membuat urutan yang berdekatan menggunakan ROW_NUMBER()
:
;WITH y AS
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
FROM dbo.Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.rn
OPTION (MAXRECURSION 10000);
Bergantung pada ukuran data (mis. kolom yang tidak kami ketahui), Anda mungkin menemukan kinerja keseluruhan yang lebih baik dengan memasukkan kolom yang relevan hanya dalam tabel #temp terlebih dahulu, dan memprosesnya daripada tabel dasar:
CREATE TABLE #x
(
rn INT PRIMARY KEY,
TID INT,
amt INT
);
INSERT INTO #x (rn, TID, amt)
SELECT ROW_NUMBER() OVER (ORDER BY TID),
TID, amt
FROM dbo.Transactions;
;WITH x AS
(
SELECT TID, rn, amt, rt = amt
FROM #x
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN #x AS y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY TID
OPTION (MAXRECURSION 10000);
DROP TABLE #x;
Hanya metode CTE pertama yang akan memberikan kinerja yang menyaingi pembaruan unik, tetapi metode ini membuat asumsi besar tentang sifat data (tidak ada celah). Dua metode lainnya akan mundur dan dalam kasus tersebut Anda juga dapat menggunakan kursor (jika Anda tidak dapat menggunakan CLR dan Anda belum menggunakan SQL Server 2012 atau lebih tinggi).
kursor
Semua orang diberitahu bahwa kursor itu jahat, dan mereka harus dihindari dengan cara apa pun, tetapi ini sebenarnya mengalahkan kinerja sebagian besar metode lain yang didukung, dan lebih aman daripada pembaruan unik. Satu-satunya yang saya sukai daripada solusi kursor adalah metode 2012 dan CLR (di bawah):
CREATE TABLE #x
(
TID INT PRIMARY KEY,
amt INT,
rt INT
);
INSERT #x(TID, amt)
SELECT TID, amt
FROM dbo.Transactions
ORDER BY TID;
DECLARE @rt INT, @tid INT, @amt INT;
SET @rt = 0;
DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR SELECT TID, amt FROM #x ORDER BY TID;
OPEN c;
FETCH c INTO @tid, @amt;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @rt = @rt + @amt;
UPDATE #x SET rt = @rt WHERE TID = @tid;
FETCH c INTO @tid, @amt;
END
CLOSE c; DEALLOCATE c;
SELECT TID, amt, RunningTotal = rt
FROM #x
ORDER BY TID;
DROP TABLE #x;
SQL Server 2012 atau lebih baru
Fungsi jendela baru yang diperkenalkan di SQL Server 2012 membuat tugas ini jauh lebih mudah (dan juga bekerja lebih baik daripada semua metode di atas):
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID ROWS UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
Perhatikan bahwa pada kumpulan data yang lebih besar, Anda akan menemukan bahwa performa di atas jauh lebih baik daripada salah satu dari dua opsi berikut, karena RANGE menggunakan spool di disk (dan defaultnya menggunakan RANGE). Namun penting juga untuk dicatat bahwa perilaku dan hasil dapat berbeda, jadi pastikan keduanya memberikan hasil yang benar sebelum memutuskan di antara keduanya berdasarkan perbedaan ini.
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID)
FROM dbo.Transactions
ORDER BY TID;
SELECT TID, amt,
RunningTotal = SUM(amt) OVER (ORDER BY TID RANGE UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;
CLR
Untuk kelengkapan, saya menawarkan tautan ke metode CLR Pavel Pawlowski, yang sejauh ini merupakan metode yang lebih disukai pada versi sebelum SQL Server 2012 (tapi jelas bukan 2000).
http://www.pawlowski.cz/2010/09/sql-server-and-fastest-running-totals-using-clr/
Kesimpulan
Jika Anda menggunakan SQL Server 2012 atau lebih tinggi, pilihannya jelas - gunakan SUM() OVER()
yang baru konstruksi (dengan ROWS
vs. RANGE
). Untuk versi sebelumnya, Anda dapat membandingkan kinerja pendekatan alternatif pada skema, data, dan - dengan mempertimbangkan faktor yang tidak terkait kinerja - menentukan pendekatan mana yang tepat untuk Anda. Ini mungkin pendekatan CLR. Berikut adalah rekomendasi saya, dalam urutan preferensi:
SUM() OVER() ... ROWS
, jika pada tahun 2012 atau lebih tinggi- Metode CLR, jika memungkinkan
- Metode CTE rekursif pertama, jika memungkinkan
- Kursor
- Metode CTE rekursif lainnya
- Pembaruan unik
- Bergabung dan/atau subkueri terkait
Untuk informasi lebih lanjut dengan perbandingan kinerja metode ini, lihat pertanyaan ini di http://dba.stackexchange.com:
https://dba.stackexchange.com/questions/19507/running-total-with-count
Saya juga telah membuat blog detail lebih lanjut tentang perbandingan ini di sini:
http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals
Juga untuk total running yang dikelompokkan/dipartisi, lihat postingan berikut:
http://sqlperformance.com/2014/01/t-sql-queries/grouped-running-totals
Mempartisi menghasilkan kueri total yang berjalan
Beberapa Total Berjalan dengan Grup Menurut