İşte karşılaştırabileceğiniz birkaç yöntem. İlk önce bazı yapay verileri içeren bir tablo hazırlayalım. Bunu, sys.all_columns dosyasındaki bir grup rasgele veriyle dolduruyorum. Eh, biraz rastgele - Tarihlerin bitişik olmasını sağlıyorum (bu, cevapların yalnızca biri için gerçekten önemlidir).
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;
Sonuçlar:
Earliest_Day Latest_Day Unique_Days Total_Days
------------------- ------------------- ----------- ----------
2012-05-01 00:00:00 2013-09-13 00:00:00 501 5000
Veriler şöyle gözüküyor (5000 satır) - ancak sürüme ve yapı #'ye bağlı olarak sisteminizde biraz farklı görünecek:
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
...
Akan toplamlar sonuçları şöyle görünmelidir (501 satır):
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
...
Yani karşılaştıracağım yöntemler:
- "öz-birleşme" - kümeye dayalı sadelik yaklaşımı
- "tarihleri olan özyinelemeli CTE" - bu bitişik tarihlere dayanır (boşluk yok)
- "satır_numaralı özyinelemeli CTE" - yukarıdakine benzer ancak daha yavaş, ROW_NUMBER
- "#temp tablosuyla özyinelemeli CTE" - önerildiği gibi Mikael'in cevabından çalındı
- Desteklenmeyen ve gelecek vaad eden bir davranış sergilemediği halde, "ilginç güncelleme" oldukça popüler görünüyor
- "İmleç"
- Yeni pencereleme işlevini kullanarak SQL Server 2012
iç birleşim
İnsanların sizi imleçlerden uzak durmaları konusunda uyardıklarında yapmaları gerektiğini söyleyeceği yol budur, çünkü “set tabanlı her zaman daha hızlıdır”. Bazı yeni deneylerde imlecin bu çözümü geride bıraktığını gördüm.
;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];
tarihleri ile özyinelemeli cte
Hatırlatma - Bu, bitişik tarihlere (boşluksuz), 10000'e kadar özyinelemeye dayanır ve ilgilendiğiniz aralığın başlangıç tarihini bildiğinize (çapayı ayarlamak için) dayanır. Çapayı dinamik olarak bir alt sorgu kullanarak ayarlayabilirsin, ama işleri basit tutmak istedim.
;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);
row_number ile özyinelemeli cte
Row_number hesaplama burada biraz pahalı. Yine bu, 10000'lük maksimum özyinelemeyi destekler, ancak çapa atamanıza gerek yoktur.
;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);
temp tablo ile özyinelemeli cte
Mikael'in cevabından, önerildiği gibi, bunu testlere dahil etmek için çalmak.
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;
ilginç güncelleme
Yine bunu sadece bütünlük için dahil ediyorum; Şahsen bu çözüme güvenmezdim, çünkü başka bir cevapta da belirttiğim gibi, bu yöntemin hiç çalışması garanti edilmiyor ve SQL Server'ın gelecekteki bir sürümünde tamamen bozulabilir. (Dizin seçimi için bir ipucu kullanarak, SQL Server'ı istediğim sıraya uymaya zorlamak için elimden geleni yapıyorum.)
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;
kürsör
"Dikkat et, burada imleçler var! İmleçler kötü! İmleçlerden her ne pahasına olursa olsun kaçınmalısın!" Hayır, o konuşmuyorum, sadece çok duyduğum şeyler. Popüler düşüncenin aksine, imleclerin uygun olduğu bazı durumlar vardır.
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
SQL Server'ın en yeni sürümündeyseniz, pencereleme işlevindeki iyileştirmeler, kendi kendine katılma maliyetinin katlanmadan (SUM bir seferde hesaplanır), CTE'lerin karmaşıklığı (gereklilik dahil) hesaplanmasını sağlar. daha iyi performans gösteren CTE için bitişik satırların listesi, desteklenmeyen ilginç güncelleme ve yasak imleç. Sadece kullanma RANGE
ve ROWS
hiçbir şekilde belirtmeme arasındaki farka dikkat edin - ROWS
aksi takdirde performansı önemli ölçüde azaltacak olan disk üstü bir makaradan kaçınır.
;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];
performans karşılaştırmaları
Her yaklaşımı alıp aşağıdakileri kullanarak bir seriyi tamamladım:
SELECT SYSUTCDATETIME();
GO
DBCC DROPCLEANBUFFERS;DBCC FREEPROCCACHE;
-- query here
GO 10
SELECT SYSUTCDATETIME();
Toplam sürenin milisaniye cinsinden sonuçları aşağıdadır (bunun her seferinde DBCC komutlarını da içerdiğini unutmayın):
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
DBCC komutları olmadan tekrar yaptım:
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
Hem DBCC hem de loop'ları çıkarmak, sadece bir ham yinelemeyi ölçmek:
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
Son olarak, kaynak tablodaki satır sayısını 10'la çarptım (en üst 50000'e değiştirerek ve çapraz birleştirme olarak başka bir tablo ekleyerek). Bunun sonucu, DBCC komutları olmayan tek bir yineleme (sadece zamanın çıkarları doğrultusunda):
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
Sadece süreyi ölçtüm - Okuyucunun verilerini bu yaklaşımları karşılaştırmak, önemli olabilecek (veya şemalarına / verisine göre değişebilir) diğer ölçümleri karşılaştırmak için bir egzersiz olarak bırakacağım. Bu cevaptan herhangi bir sonuç çıkarmadan önce, verilerinize ve şemalarınıza göre test etmek size kalmıştır ... bu sonuçlar, satır sayıları yükseldikçe neredeyse kesinlikle değişecektir.
gösteri
Bir sqlfiddle ekledim . Sonuçlar:
Sonuç
Testlerimde seçim şöyle olacaktır:
- Kullanılabilir SQL Server 2012 varsa, SQL Server 2012 yöntemi.
- SQL Server 2012 mevcut değilse ve tarihlerim bitişikse, özyinelemeli cte ile tarihler yöntemiyle giderdim.
- Ne 1. ne de 2. geçerli değilse, performansın yakın olmasına rağmen, sadece davranışlar belgelendiğinden ve garanti edildiğinden dolayı, ilginç güncelleme üzerinden kendi kendime katılmaya giderdim. Gelecekteki uyumluluk konusunda daha az endişeliyim, çünkü umarım ilginç güncelleme bozulursa tüm kodumu zaten 1'e dönüştürdükten sonra olur. :-)
Fakat yine de, bunları şema ve verilerinize karşı test etmelisiniz. Bu, nispeten düşük sıra sayımlı bir test olduğundan, rüzgarda da osuruk olabilir. Farklı şema ve satır sayıları ile başka testler yaptım ve performans sezgiselliği oldukça farklıydı ... bu yüzden asıl sorunuza birçok takip sorusu sordum.
GÜNCELLEME
Bununla ilgili daha fazla blog yazdım:
Toplamları çalıştırmak için en iyi yaklaşımlar - SQL Server 2012 için güncellendi
Day
bir anahtardır ve değerler bitişik midir?