비교할 수있는 몇 가지 방법이 있습니다. 먼저 더미 데이터가있는 테이블을 설정해 봅시다. 나는 이것을 sys.all_columns의 무작위 데이터로 채 웁니다. 글쎄, 그것은 임의의 종류입니다-나는 날짜가 연속적임을 확신하고 있습니다 (정답 중 하나에 만 중요합니다).
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
...
따라서 비교할 방법은 다음과 같습니다.
- "self-join"-세트 기반 순수 주의자 접근
- "날짜가있는 재귀 CTE"-연속 날짜에 의존합니다 (격차 없음).
- "row_number의 재귀 CTE"-위와 비슷하지만 느리며 ROW_NUMBER에 의존
- "#temp 테이블이있는 재귀 CTE"-제안 된 Mikael의 답변에서 도난
- 지원되지 않고 유망한 정의 된 동작이지만 매우 인기있는 것처럼 보이는 "기발한 업데이트"
- "커서"
- 새로운 윈도우 기능을 사용하는 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);
row_number의 재귀 적 cte
Row_number 계산은 여기에서 약간 비쌉니다. 다시 말하지만 최대 재귀 수준 인 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
제안에 따라 Mikael의 답변을 훔쳐서 테스트에 포함시킵니다.
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 with dates 방법을 사용합니다.
- 1과 2 모두 적용 할 수없는 경우, 동작이 문서화되고 보장 되었기 때문에 성능이 거의 없더라도 기발한 업데이트를 통해 자체 조인을 수행합니다. 기발한 업데이트가 중단되면 이미 모든 코드를 1로 변환 한 후에 발생하기 때문에 향후 호환성에 대해 걱정하지 않습니다. :-)
그러나 다시 스키마와 데이터에 대해 테스트해야합니다. 이것은 상대적으로 적은 행 수를 가진 고안된 테스트이기 때문에 바람에 방귀가 될 수 있습니다. 다른 스키마와 행 수로 다른 테스트를 수행했으며 성능 휴리스틱이 상당히 달랐습니다. 그래서 원래 질문에 대한 후속 질문을 많이했습니다.
최신 정보
나는 이것에 대해 더 많이 블로그에 올렸다.
총계 실행을위한 최선의 방법 – SQL Server 2012 용으로 업데이트
Day
열쇠이며 값이 연속적입니까?