Jika Anda tidak perlu MENYIMPAN data (yang seharusnya tidak, karena Anda perlu memperbarui total yang berjalan setiap kali ada baris yang diubah, ditambahkan, atau dihapus), dan jika Anda tidak mempercayai pembaruan unik (yang Anda tidak boleh, karena tidak dijamin berfungsi dan perilakunya dapat berubah dengan perbaikan terbaru, paket layanan, peningkatan, atau bahkan indeks atau perubahan statistik yang mendasarinya), Anda dapat mencoba jenis kueri ini saat runtime. Ini adalah metode sesama MVP Hugo Kornelis menciptakan "iterasi berbasis set" (ia memposting sesuatu yang serupa di salah satu babnya Penyelaman MVP SQL Server ). Karena total yang berjalan biasanya memerlukan kursor di seluruh rangkaian, pembaruan unik di seluruh rangkaian, atau penggabungan mandiri non-linier tunggal yang menjadi semakin mahal seiring bertambahnya jumlah baris, triknya di sini adalah mengulang beberapa yang terbatas elemen dalam set (dalam hal ini, "peringkat" setiap baris dalam istilah bulan, untuk setiap pengguna - dan Anda hanya memproses setiap peringkat satu kali untuk semua kombinasi pengguna/bulan pada peringkat itu, jadi alih-alih mengulang hingga 200.000 baris, Anda mengulang hingga 24 kali).
DECLARE @t TABLE
(
[user_id] INT,
[month] TINYINT,
total DECIMAL(10,1),
RunningTotal DECIMAL(10,1),
Rnk INT
);
INSERT @t SELECT [user_id], [month], total, total,
RANK() OVER (PARTITION BY [user_id] ORDER BY [month])
FROM dbo.my_table;
DECLARE @rnk INT = 1, @rc INT = 1;
WHILE @rc > 0
BEGIN
SET @rnk += 1;
UPDATE c SET RunningTotal = p.RunningTotal + c.total
FROM @t AS c INNER JOIN @t AS p
ON c.[user_id] = p.[user_id]
AND p.rnk = @rnk - 1
AND c.rnk = @rnk;
SET @rc = @@ROWCOUNT;
END
SELECT [user_id], [month], total, RunningTotal
FROM @t
ORDER BY [user_id], rnk;
Hasil:
user_id month total RunningTotal
------- ----- ----- ------------
1 1 2.0 2.0
1 2 1.0 3.0
1 3 3.5 6.5 -- I think your calculation is off
2 1 0.5 0.5
2 2 1.5 2.0
2 3 2.0 4.0
Tentu saja Anda bisa perbarui tabel dasar dari variabel tabel ini, tetapi mengapa repot-repot, karena nilai-nilai yang disimpan itu hanya baik sampai tabel berikutnya disentuh oleh pernyataan DML?
UPDATE mt
SET cumulative_total = t.RunningTotal
FROM dbo.my_table AS mt
INNER JOIN @t AS t
ON mt.[user_id] = t.[user_id]
AND mt.[month] = t.[month];
Karena kami tidak mengandalkan pemesanan implisit dalam bentuk apa pun, ini 100% didukung dan layak mendapatkan perbandingan kinerja relatif terhadap pembaruan unik yang tidak didukung. Meskipun tidak mengalahkannya tetapi mendekati, Anda harus tetap mempertimbangkan untuk menggunakannya IMHO.
Adapun solusi SQL Server 2012, Matt menyebutkan RANGE
tetapi karena metode ini menggunakan spool pada disk, Anda juga harus menguji dengan ROWS
daripada hanya menjalankan dengan RANGE
. Berikut adalah contoh cepat untuk kasus Anda:
SELECT
[user_id],
[month],
total,
RunningTotal = SUM(total) OVER
(
PARTITION BY [user_id]
ORDER BY [month] ROWS UNBOUNDED PRECEDING
)
FROM dbo.my_table
ORDER BY [user_id], [month];
Bandingkan dengan RANGE UNBOUNDED PRECEDING
atau tidak ada ROWS\RANGE
sama sekali (yang juga akan menggunakan RANGE
gulungan pada disk). Di atas akan memiliki durasi keseluruhan yang lebih rendah dan cara lebih sedikit I/O, meskipun rencananya terlihat sedikit lebih kompleks (operator proyek urutan tambahan).
Saya baru-baru ini menerbitkan posting blog yang menguraikan beberapa perbedaan kinerja yang saya amati untuk skenario total berjalan tertentu:
http://www.sqlperformance.com/2012/07 /t-sql-queries/running-totals