Berikut adalah beberapa metode yang dapat Anda bandingkan. Pertama mari kita buat tabel dengan beberapa data dummy. Saya mengisi ini dengan banyak data acak dari sys.all_columns. Yah, ini agak acak - saya memastikan bahwa tanggalnya berdekatan (yang benar-benar hanya penting untuk salah satu jawaban).
CREATE TABLE dbo.Hits(Day SMALLDATETIME, CustomerID INT);
CREATE CLUSTERED INDEX x ON dbo.Hits([Day]);
INSERT dbo.Hits SELECT TOP (5000) DATEADD(DAY, r, '20120501'),
COALESCE(ASCII(SUBSTRING(name, s, 1)), 86)
FROM (SELECT name, r = ROW_NUMBER() OVER (ORDER BY name)/10,
s = CONVERT(INT, RIGHT(CONVERT(VARCHAR(20), [object_id]), 1))
FROM sys.all_columns) AS x;
SELECT
Earliest_Day = MIN([Day]),
Latest_Day = MAX([Day]),
Unique_Days = DATEDIFF(DAY, MIN([Day]), MAX([Day])) + 1,
Total_Rows = COUNT(*)
FROM dbo.Hits;
Hasil:
Earliest_Day Latest_Day Unique_Days Total_Days
------------------- ------------------- ----------- ----------
2012-05-01 00:00:00 2013-09-13 00:00:00 501 5000
Data terlihat seperti ini (5000 baris) - tetapi akan terlihat sedikit berbeda pada sistem Anda tergantung pada versi dan versi #:
Day CustomerID
------------------- ---
2012-05-01 00:00:00 95
2012-05-01 00:00:00 97
2012-05-01 00:00:00 97
2012-05-01 00:00:00 117
2012-05-01 00:00:00 100
...
2012-05-02 00:00:00 110
2012-05-02 00:00:00 110
2012-05-02 00:00:00 95
...
Dan hasil total yang berjalan akan terlihat seperti ini (501 baris):
Day c rt
------------------- -- --
2012-05-01 00:00:00 6 6
2012-05-02 00:00:00 5 11
2012-05-03 00:00:00 4 15
2012-05-04 00:00:00 7 22
2012-05-05 00:00:00 6 28
...
Jadi metode yang akan saya bandingkan adalah:
- "bergabung sendiri" - pendekatan purist berbasis set
- "CTE rekursif dengan tanggal" - ini bergantung pada tanggal yang berdekatan (tanpa celah)
- "CTE rekursif dengan row_number" - mirip dengan di atas tetapi lebih lambat, mengandalkan ROW_NUMBER
- "CTE rekursif dengan tabel #temp" - dicuri dari jawaban Mikael seperti yang disarankan
- "pembaruan unik" yang, walaupun tidak didukung dan tidak menjanjikan perilaku yang ditentukan, tampaknya cukup populer
- "kursor"
- SQL Server 2012 menggunakan fungsi windowing baru
bergabung sendiri
Ini adalah cara orang akan mengatakan kepada Anda untuk melakukannya ketika mereka memperingatkan Anda untuk menjauh dari kursor, karena "berbasis set selalu lebih cepat." Dalam beberapa percobaan baru-baru ini saya telah menemukan bahwa kursor melampaui solusi ini.
;WITH g AS
(
SELECT [Day], c = COUNT(DISTINCT CustomerID)
FROM dbo.Hits
GROUP BY [Day]
)
SELECT g.[Day], g.c, rt = SUM(g2.c)
FROM g INNER JOIN g AS g2
ON g.[Day] >= g2.[Day]
GROUP BY g.[Day], g.c
ORDER BY g.[Day];
CTE rekursif dengan tanggal
Pengingat - ini bergantung pada tanggal yang berdekatan (tanpa celah), hingga 10.000 tingkat rekursi, dan Anda tahu tanggal mulai rentang yang Anda minati (untuk mengatur jangkar). Anda dapat mengatur jangkar secara dinamis menggunakan subquery, tentu saja, tetapi saya ingin menyederhanakannya.
;WITH g AS
(
SELECT [Day], c = COUNT(DISTINCT CustomerID)
FROM dbo.Hits
GROUP BY [Day]
), x AS
(
SELECT [Day], c, rt = c
FROM g
WHERE [Day] = '20120501'
UNION ALL
SELECT g.[Day], g.c, x.rt + g.c
FROM x INNER JOIN g
ON g.[Day] = DATEADD(DAY, 1, x.[Day])
)
SELECT [Day], c, rt
FROM x
ORDER BY [Day]
OPTION (MAXRECURSION 10000);
cte rekursif dengan row_number
Perhitungan Row_number sedikit mahal di sini. Sekali lagi ini mendukung level maksimum rekursi 10000, tetapi Anda tidak perlu menetapkan jangkar.
;WITH g AS
(
SELECT [Day], rn = ROW_NUMBER() OVER (ORDER BY DAY),
c = COUNT(DISTINCT CustomerID)
FROM dbo.Hits
GROUP BY [Day]
), x AS
(
SELECT [Day], rn, c, rt = c
FROM g
WHERE rn = 1
UNION ALL
SELECT g.[Day], g.rn, g.c, x.rt + g.c
FROM x INNER JOIN g
ON g.rn = x.rn + 1
)
SELECT [Day], c, rt
FROM x
ORDER BY [Day]
OPTION (MAXRECURSION 10000);
cte rekursif dengan tabel temp
Mencuri dari jawaban Mikael, seperti yang disarankan, untuk memasukkan ini dalam tes.
CREATE TABLE #Hits
(
rn INT PRIMARY KEY,
c INT,
[Day] SMALLDATETIME
);
INSERT INTO #Hits (rn, c, Day)
SELECT ROW_NUMBER() OVER (ORDER BY DAY),
COUNT(DISTINCT CustomerID),
[Day]
FROM dbo.Hits
GROUP BY [Day];
WITH x AS
(
SELECT [Day], rn, c, rt = c
FROM #Hits as c
WHERE rn = 1
UNION ALL
SELECT g.[Day], g.rn, g.c, x.rt + g.c
FROM x INNER JOIN #Hits as g
ON g.rn = x.rn + 1
)
SELECT [Day], c, rt
FROM x
ORDER BY [Day]
OPTION (MAXRECURSION 10000);
DROP TABLE #Hits;
pembaruan yang unik
Sekali lagi saya hanya memasukkan ini untuk kelengkapan; Saya pribadi tidak akan bergantung pada solusi ini karena, seperti yang saya sebutkan pada jawaban lain, metode ini tidak dijamin berfungsi sama sekali, dan mungkin benar-benar merusak versi SQL Server yang akan datang. (Saya melakukan yang terbaik untuk memaksa SQL Server mematuhi perintah yang saya inginkan, menggunakan petunjuk untuk pilihan indeks.)
CREATE TABLE #x([Day] SMALLDATETIME, c INT, rt INT);
CREATE UNIQUE CLUSTERED INDEX x ON #x([Day]);
INSERT #x([Day], c)
SELECT [Day], c = COUNT(DISTINCT CustomerID)
FROM dbo.Hits
GROUP BY [Day]
ORDER BY [Day];
DECLARE @rt1 INT;
SET @rt1 = 0;
UPDATE #x
SET @rt1 = rt = @rt1 + c
FROM #x WITH (INDEX = x);
SELECT [Day], c, rt FROM #x ORDER BY [Day];
DROP TABLE #x;
kursor
"Waspadalah, ada kursor di sini! Kursor itu jahat! Kamu harus menghindari kursor di semua biaya!" Tidak, itu bukan saya yang berbicara, itu hanya hal yang saya dengar banyak. Berlawanan dengan pendapat umum, ada beberapa kasus di mana kursor sesuai.
CREATE TABLE #x2([Day] SMALLDATETIME, c INT, rt INT);
CREATE UNIQUE CLUSTERED INDEX x ON #x2([Day]);
INSERT #x2([Day], c)
SELECT [Day], COUNT(DISTINCT CustomerID)
FROM dbo.Hits
GROUP BY [Day]
ORDER BY [Day];
DECLARE @rt2 INT, @d SMALLDATETIME, @c INT;
SET @rt2 = 0;
DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR SELECT [Day], c FROM #x2 ORDER BY [Day];
OPEN c;
FETCH NEXT FROM c INTO @d, @c;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @rt2 = @rt2 + @c;
UPDATE #x2 SET rt = @rt2 WHERE [Day] = @d;
FETCH NEXT FROM c INTO @d, @c;
END
SELECT [Day], c, rt FROM #x2 ORDER BY [Day];
DROP TABLE #x2;
SQL Server 2012
Jika Anda menggunakan SQL Server versi terbaru, peningkatan fungsionalitas windowing memungkinkan kami untuk menghitung total running dengan mudah tanpa biaya eksponensial dari self-gabung (SUM dihitung dalam satu pass), kompleksitas CTE (termasuk persyaratan) baris yang berdekatan untuk CTE yang berkinerja lebih baik), pembaruan unik yang tidak didukung, dan kursor terlarang. Berhati-hatilah dengan perbedaan antara menggunakan RANGE
dan ROWS
, atau tidak menentukan sama sekali - hanya ROWS
menghindari spool pada disk, yang akan menghambat kinerja secara signifikan.
;WITH g AS
(
SELECT [Day], c = COUNT(DISTINCT CustomerID)
FROM dbo.Hits
GROUP BY [Day]
)
SELECT g.[Day], c,
rt = SUM(c) OVER (ORDER BY [Day] ROWS UNBOUNDED PRECEDING)
FROM g
ORDER BY g.[Day];
perbandingan kinerja
Saya mengambil setiap pendekatan dan membungkusnya menggunakan berikut ini:
SELECT SYSUTCDATETIME();
GO
DBCC DROPCLEANBUFFERS;DBCC FREEPROCCACHE;
-- query here
GO 10
SELECT SYSUTCDATETIME();
Berikut adalah hasil dari total durasi, dalam milidetik (ingat ini termasuk perintah DBCC setiap kali juga):
method run 1 run 2
----------------------------- -------- --------
self-join 1296 ms 1357 ms -- "supported" non-SQL 2012 winner
recursive cte with dates 1655 ms 1516 ms
recursive cte with row_number 19747 ms 19630 ms
recursive cte with #temp table 1624 ms 1329 ms
quirky update 880 ms 1030 ms -- non-SQL 2012 winner
cursor 1962 ms 1850 ms
SQL Server 2012 847 ms 917 ms -- winner if SQL 2012 available
Dan saya melakukannya lagi tanpa perintah DBCC:
method run 1 run 2
----------------------------- -------- --------
self-join 1272 ms 1309 ms -- "supported" non-SQL 2012 winner
recursive cte with dates 1247 ms 1593 ms
recursive cte with row_number 18646 ms 18803 ms
recursive cte with #temp table 1340 ms 1564 ms
quirky update 1024 ms 1116 ms -- non-SQL 2012 winner
cursor 1969 ms 1835 ms
SQL Server 2012 600 ms 569 ms -- winner if SQL 2012 available
Menghapus DBCC dan loop, hanya mengukur satu iterasi mentah:
method run 1 run 2
----------------------------- -------- --------
self-join 313 ms 242 ms
recursive cte with dates 217 ms 217 ms
recursive cte with row_number 2114 ms 1976 ms
recursive cte with #temp table 83 ms 116 ms -- "supported" non-SQL 2012 winner
quirky update 86 ms 85 ms -- non-SQL 2012 winner
cursor 1060 ms 983 ms
SQL Server 2012 68 ms 40 ms -- winner if SQL 2012 available
Akhirnya, saya mengalikan jumlah baris dalam tabel sumber dengan 10 (mengubah atas menjadi 50.000 dan menambahkan tabel lain sebagai gabungan silang). Hasil ini, satu iterasi tunggal tanpa perintah DBCC (hanya untuk kepentingan waktu):
method run 1 run 2
----------------------------- -------- --------
self-join 2401 ms 2520 ms
recursive cte with dates 442 ms 473 ms
recursive cte with row_number 144548 ms 147716 ms
recursive cte with #temp table 245 ms 236 ms -- "supported" non-SQL 2012 winner
quirky update 150 ms 148 ms -- non-SQL 2012 winner
cursor 1453 ms 1395 ms
SQL Server 2012 131 ms 133 ms -- winner
Saya hanya mengukur durasi - Saya akan menyerahkannya kepada pembaca untuk membandingkan pendekatan ini pada data mereka, membandingkan metrik lain yang mungkin penting (atau mungkin berbeda dengan skema / data mereka). Sebelum menarik kesimpulan apa pun dari jawaban ini, terserah Anda untuk mengujinya terhadap data dan skema Anda ... hasil ini hampir pasti akan berubah karena jumlah baris semakin tinggi.
demo
Saya telah menambahkan sqlfiddle . Hasil:
kesimpulan
Dalam tes saya, pilihannya adalah:
- Metode SQL Server 2012, jika saya memiliki SQL Server 2012 tersedia.
- Jika SQL Server 2012 tidak tersedia, dan tanggal saya berdekatan, saya akan pergi dengan cyt rekursif dengan metode tanggal.
- Jika 1. atau 2. tidak berlaku, saya akan bergabung dengan self-join atas pembaruan unik, meskipun kinerjanya dekat, hanya karena perilaku tersebut didokumentasikan dan dijamin. Saya kurang khawatir tentang kompatibilitas di masa depan karena mudah-mudahan jika pembaruan yang unik istirahat itu akan setelah saya sudah mengkonversi semua kode saya ke 1. :-)
Tetapi sekali lagi, Anda harus menguji ini terhadap skema dan data Anda. Karena ini adalah tes yang dibuat dengan jumlah baris yang relatif rendah, itu mungkin juga kentut dalam angin. Saya telah melakukan tes lain dengan skema dan jumlah baris yang berbeda, dan heuristik kinerja sangat berbeda ... itulah sebabnya saya mengajukan begitu banyak pertanyaan lanjutan ke pertanyaan awal Anda.
MEMPERBARUI
Saya telah membuat blog lebih banyak tentang ini di sini:
Pendekatan terbaik untuk menjalankan total - diperbarui untuk SQL Server 2012
Day
kuncinya, dan apakah nilainya berdekatan?