Dưới đây là một vài phương pháp bạn có thể so sánh. Trước tiên, hãy thiết lập một bảng với một số dữ liệu giả. Tôi đang điền dữ liệu này với một loạt dữ liệu ngẫu nhiên từ sys.all_columns. Chà, thật là ngẫu nhiên - Tôi đảm bảo rằng ngày tháng không liên tục (điều này thực sự chỉ quan trọng đối với một trong những câu trả lời).
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;
Các kết quả:
Earliest_Day Latest_Day Unique_Days Total_Days
------------------- ------------------- ----------- ----------
2012-05-01 00:00:00 2013-09-13 00:00:00 501 5000
Dữ liệu trông như thế này (5000 hàng) - nhưng sẽ trông hơi khác trên hệ thống của bạn tùy thuộc vào phiên bản và bản dựng #:
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
...
Và kết quả tổng cộng đang chạy sẽ như thế này (501 hàng):
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
...
Vì vậy, các phương pháp tôi sẽ so sánh là:
- "tự tham gia" - cách tiếp cận thuần túy dựa trên tập hợp
- "CTE đệ quy có ngày" - điều này phụ thuộc vào ngày tiếp giáp (không có khoảng trống)
- "CTE đệ quy với row_number" - tương tự như trên nhưng chậm hơn, dựa vào ROW_NUMBER
- "CTE đệ quy với bảng #temp" - bị đánh cắp từ câu trả lời của Mikael như đề xuất
- "cập nhật kỳ quặc", trong khi hành vi được xác định không được hỗ trợ và không hứa hẹn, dường như khá phổ biến
- "con trỏ"
- SQL Server 2012 sử dụng chức năng cửa sổ mới
tự tham gia
Đây là cách mọi người sẽ bảo bạn làm điều đó khi họ cảnh báo bạn tránh xa những con trỏ, bởi vì "dựa trên tập hợp luôn nhanh hơn". Trong một số thí nghiệm gần đây, tôi thấy rằng con trỏ vượt quá giải pháp này.
;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 đệ quy với ngày
Nhắc nhở - điều này phụ thuộc vào các ngày liền kề (không có khoảng trống), lên tới 10000 mức đệ quy và bạn biết ngày bắt đầu của phạm vi bạn quan tâm (để đặt neo). Dĩ nhiên, bạn có thể thiết lập neo một cách linh hoạt bằng cách sử dụng truy vấn con, nhưng tôi muốn giữ mọi thứ đơn giản.
;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 đệ quy với row_number
Tính toán Row_number hơi đắt ở đây. Một lần nữa, điều này hỗ trợ mức đệ quy tối đa 10000, nhưng bạn không cần gán neo.
;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 đệ quy với bảng tạm thời
Ăn cắp từ câu trả lời của Mikael, như được đề xuất, để đưa điều này vào các bài kiểm tra.
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;
cập nhật kỳ quặc
Một lần nữa tôi chỉ bao gồm điều này cho sự hoàn chỉnh; Cá nhân tôi sẽ không dựa vào giải pháp này vì như tôi đã đề cập trong một câu trả lời khác, phương pháp này hoàn toàn không đảm bảo để hoạt động và hoàn toàn có thể phá vỡ phiên bản SQL Server trong tương lai. (Tôi đang cố hết sức để ép buộc SQL Server tuân theo thứ tự tôi muốn, sử dụng một gợi ý cho lựa chọn chỉ mục.)
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;
con trỏ
"Coi chừng, có con trỏ ở đây! Con trỏ là ác quỷ! Bạn nên tránh con trỏ bằng mọi giá!" Không, đó không phải là tôi nói, đó chỉ là thứ tôi nghe được nhiều. Trái với ý kiến phổ biến, có một số trường hợp con trỏ thích hợp.
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;
Máy chủ SQL 2012
Nếu bạn đang sử dụng phiên bản SQL Server mới nhất, việc cải tiến chức năng cửa sổ cho phép chúng tôi dễ dàng tính toán tổng số chạy mà không phải trả chi phí theo cấp số nhân (SUM được tính trong một lần), độ phức tạp của CTE (bao gồm cả yêu cầu các hàng liền kề để CTE hoạt động tốt hơn), cập nhật kỳ quặc không được hỗ trợ và con trỏ bị cấm. Chỉ cần cảnh giác về sự khác biệt giữa việc sử dụng RANGE
và ROWS
hoặc không chỉ định tất cả - chỉ ROWS
tránh một bộ đệm trên đĩa, điều này sẽ cản trở hiệu suất đáng kể nếu không.
;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];
so sánh hiệu suất
Tôi đã thực hiện từng cách tiếp cận và gói nó một đợt bằng cách sử dụng như sau:
SELECT SYSUTCDATETIME();
GO
DBCC DROPCLEANBUFFERS;DBCC FREEPROCCACHE;
-- query here
GO 10
SELECT SYSUTCDATETIME();
Dưới đây là kết quả của tổng thời lượng, tính bằng mili giây (hãy nhớ điều này bao gồm cả các lệnh DBCC mỗi lầ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
Và tôi đã làm điều đó một lần nữa mà không có lệnh 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
Loại bỏ cả DBCC và các vòng lặp, chỉ cần đo một lần lặp thô:
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
Cuối cùng, tôi đã nhân số đếm hàng trong bảng nguồn với 10 (thay đổi hàng đầu thành 50000 và thêm một bảng khác dưới dạng nối chéo). Kết quả của việc này, một lần lặp duy nhất không có lệnh DBCC (đơn giản là vì lợi ích của thời gian):
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
Tôi chỉ đo thời lượng - Tôi sẽ để nó như một bài tập cho người đọc để so sánh các cách tiếp cận này trên dữ liệu của họ, so sánh các số liệu khác có thể quan trọng (hoặc có thể thay đổi theo lược đồ / dữ liệu của họ). Trước khi đưa ra bất kỳ kết luận nào từ câu trả lời này, bạn sẽ phải kiểm tra nó dựa trên dữ liệu và lược đồ của bạn ... những kết quả này gần như chắc chắn sẽ thay đổi khi số lượng hàng tăng cao hơn.
bản giới thiệu
Tôi đã thêm một sqlfiddle . Các kết quả:
phần kết luận
Trong các thử nghiệm của tôi, sự lựa chọn sẽ là:
- Phương pháp SQL Server 2012, nếu tôi có sẵn SQL Server 2012.
- Nếu SQL Server 2012 không có sẵn và ngày của tôi liền kề nhau, tôi sẽ sử dụng phương thức đệ quy với phương thức ngày.
- Nếu không áp dụng 1. và 2., tôi sẽ tự tham gia vào bản cập nhật kỳ quặc, mặc dù hiệu suất đã gần, chỉ vì hành vi được ghi lại và đảm bảo. Tôi ít lo lắng hơn về khả năng tương thích trong tương lai vì hy vọng nếu bản cập nhật kỳ quặc sẽ bị hỏng sau khi tôi đã chuyển đổi tất cả mã của mình thành 1 .:-)
Nhưng một lần nữa, bạn nên kiểm tra chúng dựa trên lược đồ và dữ liệu của bạn. Vì đây là một thử nghiệm khó khăn với số lượng hàng tương đối thấp, nó cũng có thể là một cái rắm trong gió. Tôi đã thực hiện các thử nghiệm khác với các lược đồ và số hàng khác nhau và các heuristic hiệu suất hoàn toàn khác nhau ... đó là lý do tại sao tôi đã hỏi rất nhiều câu hỏi tiếp theo cho câu hỏi ban đầu của bạn.
CẬP NHẬT
Tôi đã viết thêm về điều này ở đây:
Các cách tiếp cận tốt nhất để chạy tổng số - được cập nhật cho SQL Server 2012
Day
một chìa khóa, và các giá trị có tiếp giáp nhau không?