답변 섹션
다른 T-SQL 구문을 사용하여이를 다시 작성하는 다양한 방법이 있습니다. 장단점을 살펴보고 아래에서 전체 비교를 수행합니다.
첫 번째 : 사용OR
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;
를 사용 OR
하면보다 효율적인 Seek 계획을 제공 할 수 있는데,이 계획은 필요한 정확한 행 수를 읽지 만 기술 세계가 a whole mess of malarkey
쿼리 계획을 호출 하는 것을 추가합니다 .
또한 Seek은 두 번 실행되며 그래픽 연산자에서 더 분명해야합니다.
Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 469 ms, elapsed time = 473 ms.
두 번째 : UNION ALL
쿼리 와 함께 파생 테이블 사용 은 다음과 같이 다시 작성할 수도 있습니다.
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records);
이로 인해 동일한 유형의 계획이 만들어졌으며, 색인이 훨씬 더 많이 검색되고 (검색된?) 횟수에 대해보다 분명한 정직성이 제공됩니다.
OR
쿼리 와 동일한 양의 읽기 (8233)를 수행 하지만 약 100ms의 CPU 시간이 줄어 듭니다.
CPU time = 313 ms, elapsed time = 315 ms.
그러나, 당신은해야 정말 이 계획은 평행 이동하려고하면, 두 개의 분리하기 때문에, 여기서주의 COUNT
작업이 연재됩니다 그들은 각각 글로벌 스칼라 집계를 생각이기 때문에. Trace Flag 8649를 사용하여 병렬 계획을 적용하면 문제가 분명해집니다.
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
쿼리를 약간 변경하여 피할 수 있습니다.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
이제 Seek를 수행하는 두 노드는 연결 연산자에 도달 할 때까지 완전히 병렬화됩니다.
그 가치가 있다면 완전 병렬 버전에는 좋은 이점이 있습니다. 약 100 회의 추가 읽기 및 약 90ms의 추가 CPU 시간이 소요되면 경과 시간은 93ms로 줄어 듭니다.
Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 500 ms, elapsed time = 93 ms.
CROSS APPLY는 어떻습니까?
의 마술 없이는 아무런 대답도 없습니다 CROSS APPLY
!
불행히도에 대해 더 많은 문제가 발생합니다 COUNT
.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
이 계획은 끔찍하다. 이것은 성 패트릭의 날 마지막에 나타날 때 끝내는 일종의 계획입니다. 멋지게 평행하지만 어떤 이유로 PK / CX를 스캔하고 있습니다. 으. 계획 비용은 2198 쿼리 벅입니다.
Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 29532 ms, elapsed time = 5828 ms.
비 클러스터형 인덱스를 강제로 사용하면 비용이 1798 쿼리 벅으로 상당히 감소하기 때문에 이상한 선택입니다.
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
이봐 요! 저기 확인 해봐 또한의 마술 CROSS APPLY
을 사용하면 대부분 완전히 평행 한 계획을 세우기 위해 구피를 할 필요가 없습니다.
Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 27625 ms, elapsed time = 4909 ms.
교차 적용은 COUNT
거기에 물건이 없으면 더 잘 지납니다 .
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
계획은 좋아 보이지만 읽기 및 CPU는 개선되지 않았습니다.
Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 4844 ms, elapsed time = 863 ms.
교차 적용을 파생 조인으로 다시 쓰면 결과가 모두 동일합니다. 검색어 계획 및 통계 정보를 다시 게시하지는 않습니다. 실제로 변경되지 않았습니다.
SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN
(
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x ON x.Id = u.Id;
Relational Algebra : Joe Celko가 내 꿈을 깨우지 못하도록 철저히하고, 적어도 이상한 관계형 물건을 시도해야합니다. 여기에 아무것도 없다!
시도 INTERSECT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
INTERSECT
SELECT u.Age WHERE u.Age IS NOT NULL );
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 1094 ms, elapsed time = 1090 ms.
그리고 여기에 시도가 있습니다 EXCEPT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
EXCEPT
SELECT u.Age WHERE u.Age IS NULL);
Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 2126 ms, elapsed time = 376 ms.
이것들을 쓰는 다른 방법이 있을지 모르지만, 나는 아마 EXCEPT
나 INTERSECT
보다 자주 사용하는 사람들에게 맡길 것입니다.
실제로COUNT
쿼리에서 약간의 속기 의 숫자를
사용해야 하는 경우 (읽기 : 때로는 더 관련된 시나리오를 제시하기에는 너무 게으르다). 카운트가 필요한 경우 CASE
식을 사용 하여 거의 같은 작업을 수행 할 수 있습니다 .
SELECT SUM(CASE WHEN u.Age < 18 THEN 1
WHEN u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
이들은 모두 동일한 계획을 가지며 동일한 CPU 및 읽기 특성을 갖습니다.
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 719 ms, elapsed time = 719 ms.
승자?
내 테스트에서 파생 테이블에 대해 SUM을 사용한 강제 병렬 계획이 가장 잘 수행되었습니다. 그렇습니다. 이러한 쿼리 중 상당수는 두 조건자를 모두 설명하기 위해 몇 가지 필터링 된 인덱스를 추가하여 도움을받을 수 있었지만 다른 실험은 남겨두고 싶었습니다.
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
감사!
NOT EXISTS ( INTERSECT / EXCEPT )
쿼리는없이 작업 할 수INTERSECT / EXCEPT
부품 :WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );
또 다른 방법 - 사용EXCEPT
:SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ;
(사용자 ID가 PK 또는 고유 NOT NULL 컬럼 (들)입니다).