Ось кілька методів, з якими можна порівняти. Спочатку давайте створимо таблицю з деякими фіктивними даними. Я заповнюю це купою випадкових даних із sys.all_column. Ну, це наче випадково - я стежу за тим, щоб дати були суміжними (що насправді важливо лише для однієї з відповідей).
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;
Результати:
Earliest_Day Latest_Day Unique_Days Total_Days
------------------- ------------------- ----------- ----------
2012-05-01 00:00:00 2013-09-13 00:00:00 501 5000
Дані виглядають приблизно так (5000 рядків) - але будуть виглядати трохи по-різному у вашій системі залежно від версії та версії #:
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
...
І результати підсумкових підсумків повинні виглядати приблизно так (501 рядок):
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
...
Тож методи, які я збираюсь порівнювати:
- "самоприєднання" - заданий на основі пуристів підхід
- "рекурсивний CTE з датами" - це спирається на суміжні дати (без прогалин)
- "рекурсивний CTE з номером рядків" - подібний до вище, але повільніше, спираючись на ROW_NUMBER
- "рекурсивний CTE з таблицею #temp" - вкрадений з відповіді Мікаеля, як було запропоновано
- "химерне оновлення", яке, хоч і не підтримується і не обіцяє визначеної поведінки, здається досить популярним
- "курсор"
- SQL Server 2012, використовуючи нові функції вікон
самостійне приєднання
Ось так люди скажуть вам це робити, коли вони попереджають вас триматися подалі від курсорів, адже "набір налаштувань завжди швидший". В деяких останніх експериментах я виявив, що курсор випереджає це рішення.
;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 з датами
Нагадування - це покладається на суміжні дати (без прогалин), до 10000 рівнів рекурсії, і на те, що ви знаєте дату початку діапазону, який вас цікавить (для встановлення якоря). Ви, звичайно, могли задавати якір динамічно, використовуючи підзапит, але я хотів, щоб все було просто.
;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 з рядком_ номер
Розрахунок рядкових номерів тут трохи дорогий. Знову ж це підтримує максимальний рівень рекурсії 10000, але вам не потрібно призначати якір.
;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 з таблицею temp
Викрадати відповідь Мікаеля, як було запропоновано, включити це до тестів.
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;
химерне оновлення
Я знову включаю це лише для повноти; Я особисто не покладався б на це рішення, оскільки, як я вже згадував в іншій відповіді, цей метод не гарантується, що він взагалі працює, і, можливо, повністю перерветься в майбутній версії SQL Server. (Я роблю все можливе, щоб змусити SQL Server виконувати потрібний порядок, використовуючи підказку для вибору індексу.)
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;
курсор
"Остерігайтеся, тут є курсори! Курсори злі! Ви повинні уникати курсорів за будь-яку ціну!" Ні, це я не говорю, це просто речі, які я багато чую. Всупереч поширеній думці, є випадки, коли курсори доречні.
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, вдосконалення функціональності вікон дозволяє нам легко обчислювати кількість підсумкових даних без експоненціальної вартості самостійного приєднання (SUM обчислюється за один прохід), складності CTE (включаючи вимогу суміжних рядків для кращої роботи CTE), непідтримуваного химерного оновлення та забороненого курсору. Просто будьте обережні щодо різниці між використанням RANGE
та ROWS
(або) не вказівкою взагалі - ви ROWS
уникаєте лише котушки на диску, що в іншому випадку буде суттєво утримувати продуктивність.
;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];
порівняння продуктивності
Я взяв кожен підхід і загорнув його партією, використовуючи наступне:
SELECT SYSUTCDATETIME();
GO
DBCC DROPCLEANBUFFERS;DBCC FREEPROCCACHE;
-- query here
GO 10
SELECT SYSUTCDATETIME();
Ось результати загальної тривалості, в мілісекундах (пам’ятайте, що кожен раз також включає команди DBCC):
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:
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
Видалення і DBCC, і циклів, просто вимірювання однієї необробленої ітерації:
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
Нарешті, я помножив кількість рядків у вихідній таблиці на 10 (змінивши верхню на 50000 та додавши ще одну таблицю як перехресне з'єднання). Результати цього, одна єдина ітерація без команд DBCC (просто в інтересах часу):
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
Я лише вимірював тривалість - я залишаю це читачеві вправі порівняти ці підходи щодо своїх даних, порівнюючи інші показники, які можуть бути важливими (або можуть відрізнятися залежно від їх схеми / даних). Перш ніж робити будь-які висновки з цієї відповіді, вам належить перевірити їх на ваших даних та вашої схеми ... ці результати майже напевно зміниться, коли кількість рядків зростатиме.
демонстрація
Я додав sqlfiddle . Результати:
висновок
У моїх тестах вибір був би:
- Метод SQL Server 2012, якщо у мене є SQL Server 2012.
- Якщо SQL Server 2012 недоступний, а мої дати суміжні, я б перейшов до методу рекурсивного cte з датами.
- Якщо ні 1., ні 2. не застосовні, я б пішов із самостійним приєднанням до химерного оновлення, навіть якщо продуктивність була близькою, лише тому, що поведінка задокументована та гарантована. Я менше переживаю за сумісність у майбутньому, оскільки, сподіваюся, якщо вигадливе оновлення буде порушено, це станеться після того, як я вже перетворив увесь свій код на 1. :-)
Але знову ж таки, ви повинні перевірити їх на вашій схемі та даних. Оскільки це був надуманий тест із відносно низьким числом рядків, він, можливо, може бути і пердетом на вітрі. Я робив інші тести з різними схемами та підрахунками рядків, а евристика продуктивності була зовсім різною ... саме тому я задав так багато подальших запитань до вашого початкового питання.
ОНОВЛЕННЯ
Я більше про це блогував тут:
Найкращі підходи до запуску підсумків - оновлено для SQL Server 2012
Day
ключовим, і чи є значення суміжними?