리터럴 값만 사용하는 WHERE 절에서 ISNULL ()을 바꾸는 다른 방법은 무엇입니까?


55

이것이 아닌 것 :

이것은 사용자 입력을 받거나 변수를 사용하는 범용 쿼리 에 대한 질문이 아닙니다 .

이것은 술어와 비교하기 위해 값을 카나리아 값 으로 대체하기 위해 절 ISNULL()에서 사용되는 쿼리 와 SQL Server에서 SARGable 이되도록 해당 쿼리를 다시 작성하는 다른 방법에 관한 것 입니다.WHERENULL

왜 저쪽에 자리가 없니?

이 예제 쿼리는 SQL Server 2016의 로컬 스택 오버플로 데이터베이스 복사본에 대한 것이며 NULL18 세 미만의 사용자를 찾습니다 .

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

쿼리 계획에는 매우 신중한 비 클러스터형 인덱스 스캔이 표시됩니다.

견과류

스캔 연산자는 모든 최신 행을 읽는 것을 보여줍니다 (최신 버전의 SQL Server에서 실제 실행 계획 XML에 추가 한 덕분에).

견과류

전체적으로 9157 읽기를 수행하고 약 0.5 초의 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 = 485 ms,  elapsed time = 483 ms.

질문 : 이 쿼리를보다 효율적이고 아마도 SARGable로 만들기 위해이 쿼리를 다시 작성하는 방법은 무엇입니까?

다른 제안을 자유롭게 제공하십시오. 내 대답은 반드시 생각하지 않습니다 대답, 그리고 더 나은 수 있습니다 대안을 마련하기 위해 거기 충분히 똑똑한 사람들이있다.

자신의 컴퓨터에서 게임을하려면 여기로 가서 SO 데이터베이스다운로드하십시오 .

감사!

답변:


57

답변 섹션

다른 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.

이것들을 쓰는 다른 방법이 있을지 모르지만, 나는 아마 EXCEPTINTERSECT보다 자주 사용하는 사람들에게 맡길 것입니다.

실제로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);

감사!


1
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 컬럼 (들)입니다).
ypercubeᵀᴹ

이것이 테스트 되었습니까? SELECT result = (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age IS NULL) ;테스트 한 백만 버전을 놓쳤다면 죄송합니다.
ypercubeᵀᴹ

@ ypercubeᵀᴹ 여기에 그 계획 이 있습니다. 약간 다르지만 UNION ALL계획 과 비슷한 특성을 가지고 있습니다 (360ms CPU, 11k 읽기).
Erik Darling

에릭 (Erik)은 단지 SQL 세계를 로밍하고 귀찮게하기 위해 "컴퓨팅 칼럼 (computed column)"이라고 말했다. <3
도가니

17

나는 하나의 테이블에 대해 110GB 데이터베이스를 복원하는 게임이 아니기 때문에 내 자신의 데이터를 만들었습니다 . 연령 분포는 스택 오버플로의 내용과 일치해야하지만 테이블 자체는 일치하지 않습니다. 어쨌든 쿼리가 인덱스에 도달하기 때문에 이것이 너무 큰 문제라고 생각하지 않습니다. SQL Server 2016 SP1이 설치된 4 CPU 컴퓨터에서 테스트 중입니다. 한 가지주의 할 점은이 작업을 빠르게 마치는 쿼리의 경우 실제 실행 계획을 포함하지 않는 것이 중요하다는 것입니다. 그렇게하면 상황이 상당히 느려질 수 있습니다.

Erik의 탁월한 답변에서 몇 가지 솔루션을 살펴보면서 시작했습니다. 이것을 위해 :

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);

10 회 이상 시행 한 sys.dm_exec_sessions 에서 다음과 같은 결과를 얻었습니다 .

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3532                 975          60830 
╚══════════╩════════════════════╩═══════════════╝

Erik에게 더 잘 작동하는 쿼리는 실제로 내 컴퓨터에서 더 나빴습니다.

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);

10 번의 시험 결과 :

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     5704                1636          60850 
╚══════════╩════════════════════╩═══════════════╝

왜 그렇게 나쁜지 즉시 설명 할 수는 없지만 쿼리 계획의 거의 모든 연산자를 병렬로 적용하려는 이유는 분명하지 않습니다. 원래 계획에는로 모든 행을 찾는 직렬 영역이 AGE < 18있습니다. 수천 행만 있습니다. 내 컴퓨터에서 쿼리의 해당 부분에 대해 9 개의 논리적 읽기와 9ms의보고 된 CPU 시간 및 경과 시간을 얻습니다. 행에 대한 전역 집계에 대한 직렬 영역도 AGE IS NULL있지만 DOP 당 하나의 행만 처리합니다. 내 컴퓨터에서 이것은 단지 네 개의 행입니다.

내 테이크 아웃은 그것과 함께 행을 발견 쿼리의 부분 최적화하기 위해 가장 중요한 것입니다 NULL위해 Age해당 행의 수백만이 있기 때문에입니다. 열에서 간단한 페이지 압축 페이지보다 데이터를 다루는 페이지 수가 적은 인덱스를 만들 수 없었습니다. 행당 최소 인덱스 크기가 있거나 시도한 트릭으로 많은 인덱스 공간을 피할 수 없다고 가정합니다. 따라서 우리가 데이터를 얻기 위해 거의 같은 수의 논리적 읽기를 고수했다면 더 빠르게 만드는 유일한 방법은 쿼리를 더 병렬로 만드는 것입니다. 8649. 위의 쿼리에서 CPU 시간 대 경과 시간의 비율은 3.62로 꽤 좋습니다. 내 컴퓨터에서 4.0의 비율이 이상적입니다.

개선의 한 가지 가능한 영역은 작업을 스레드간에 더 균등하게 나누는 것입니다. 아래 스크린 샷에서 내 CPU 중 하나가 약간의 휴식을 취하기로 결정했음을 알 수 있습니다.

게으른 실

인덱스 스캔은 병렬로 구현할 수있는 소수의 연산자 중 하나이며 행이 스레드에 분산되는 방법에 대해서는 아무것도 수행 할 수 없습니다. 그것에 대한 기회의 요소가 있지만 꽤 일관되게 나는 스레드가 부족한 것을 보았습니다. 이 문제를 해결하는 한 가지 방법은 병렬 처리를 어려운 방법으로 수행하는 것입니다. 중첩 루프 조인의 내부 부분. 중첩 루프의 내부에있는 것은 직렬 방식으로 구현되지만 많은 직렬 스레드가 동시에 실행될 수 있습니다. 우리가 유리한 병렬 분배 방법 (예 : 라운드 로빈)을 얻는 한 각 스레드에 얼마나 많은 행이 전송되는지 제어 할 수 있습니다.

DOP 4로 쿼리를 실행 중이므로 NULL테이블 의 행을 4 개의 버킷으로 균등하게 나누어야합니다 . 이를 수행하는 한 가지 방법은 계산 열에 여러 개의 색인을 작성하는 것입니다.

ALTER TABLE dbo.Users
ADD Compute_bucket_0 AS (CASE WHEN Age IS NULL AND Id % 4 = 0 THEN 1 ELSE NULL END),
Compute_bucket_1 AS (CASE WHEN Age IS NULL AND Id % 4 = 1 THEN 1 ELSE NULL END),
Compute_bucket_2 AS (CASE WHEN Age IS NULL AND Id % 4 = 2 THEN 1 ELSE NULL END),
Compute_bucket_3 AS (CASE WHEN Age IS NULL AND Id % 4 = 3 THEN 1 ELSE NULL END);

CREATE INDEX IX_Compute_bucket_0 ON dbo.Users (Compute_bucket_0) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_1 ON dbo.Users (Compute_bucket_1) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_2 ON dbo.Users (Compute_bucket_2) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_3 ON dbo.Users (Compute_bucket_3) WITH (DATA_COMPRESSION = PAGE);

왜 네 개의 개별 인덱스가 하나의 인덱스보다 약간 빠른지 잘 모르겠지만 테스트에서 찾은 것입니다.

병렬 중첩 루프 계획을 얻으려면 문서화되지 않은 추적 플래그 8649 를 사용합니다 . 또한 최적화 프로그램이 필요한 것보다 많은 행을 처리하지 않도록 장려하기 위해 코드를 조금 이상하게 작성하려고합니다. 아래는 잘 작동하는 것으로 보이는 하나의 구현입니다.

SELECT SUM(t.cnt) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18)
FROM 
(VALUES (0), (1), (2), (3)) v(x)
CROSS APPLY 
(
    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_0 = CASE WHEN v.x = 0 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_1 = CASE WHEN v.x = 1 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_2 = CASE WHEN v.x = 2 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_3 = CASE WHEN v.x = 3 THEN 1 ELSE NULL END
) t
OPTION (QUERYTRACEON 8649);

10 번의 시험 결과 :

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3093                 803          62008 
╚══════════╩════════════════════╩═══════════════╝

이 쿼리를 통해 CPU 대 경과 시간 비율은 3.85입니다! 런타임에서 17ms를 줄였으며 계산하는 데 4 개의 계산 된 열과 인덱스 만 있으면됩니다! 각 인덱스는 같은 수의 행에 매우 가깝고 각 스레드는 하나의 인덱스 만 스캔하므로 각 스레드는 전체적으로 같은 수의 행에 매우 근접하게 처리됩니다.

잘 나누어 진 일

마지막으로 easy 버튼을 누르고 클러스터되지 않은 CCI를 Age열에 추가 할 수도 있습니다 .

CREATE NONCLUSTERED COLUMNSTORE INDEX X_NCCI ON dbo.Users (Age);

다음 쿼리는 내 컴퓨터에서 3ms 후에 완료됩니다.

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18 OR u.Age IS NULL;

이기는 것은 힘들 것입니다.


7

스택 오버플로 데이터베이스의 로컬 복사본이 없지만 몇 가지 쿼리를 시도 할 수있었습니다. 내 생각은 기본 테이블에서 행 수를 직접 얻는 것과는 대조적으로 시스템 카탈로그보기에서 사용자 수를 얻는 것이 었습니다. 그런 다음 Erik의 기준과 일치하거나 일치하지 않는 행 수를 얻고 간단한 수학을 수행하십시오.

내가 사용 스택 Exchange 데이터 탐색기 (와 함께 SET STATISTICS TIME ON;SET STATISTICS IO ON;쿼리를 테스트하기 위해). 참고로 다음은 몇 가지 쿼리와 CPU / IO 통계입니다.

QUERY 1

--Erik's query From initial question.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

SQL Server 실행 시간 : CPU 시간 = 0ms, 경과 시간 = 0ms (1 행 반환)

'사용자'표. 스캔 카운트 17, 논리적 읽기 201567, 물리적 읽기 0, 미리 읽기 2740, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 : CPU 시간 = 1829ms, 경과 시간 = 296ms

QUERY 2

--Erik's "OR" query.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

SQL Server 실행 시간 : CPU 시간 = 0ms, 경과 시간 = 0ms (1 행 반환)

'사용자'표. 스캔 횟수 17, 논리적 읽기 201567, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 : CPU 시간 = 2500ms, 경과 시간 = 147ms

QUERY 3

--Erik's derived tables/UNION ALL query.
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);

SQL Server 실행 시간 : CPU 시간 = 0ms, 경과 시간 = 0ms (1 행 반환)

'사용자'표. 스캔 카운트 34, 논리적 읽기 403134, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 : CPU 시간 = 3156ms, 경과 시간 = 215ms

첫 번째 시도

이것은 내가 여기에 나열된 모든 Erik의 쿼리보다 느리게 진행되었습니다 ... 적어도 경과 시간 측면에서.

SELECT SUM(p.Rows)  -
  (
    SELECT COUNT(*)
    FROM dbo.Users AS u
    WHERE u.Age >= 18
  ) 
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SQL Server 실행 시간 : CPU 시간 = 0ms, 경과 시간 = 0ms (1 행 반환)

'작업대'표. 스캔 카운트 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0. 테이블 'sysrowsets'. 스캔 횟수 2, 논리적 읽기 10, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0. 테이블 'sysschobjs'. 스캔 카운트 1, 논리적 읽기 4, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0. 테이블 '사용자'. 스캔 횟수 1, 논리적 읽기 201567, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 : CPU 시간 = 593ms, 경과 시간 = 598ms

두 번째 시도

여기서는 하위 쿼리 대신 총 사용자 수를 저장하는 변수를 선택했습니다. 스캔 횟수는 첫 번째 시도에 비해 1에서 17로 증가했습니다. 논리적 읽기는 동일하게 유지되었습니다. 그러나 경과 시간이 상당히 줄었습니다.

DECLARE @Total INT;

SELECT @Total = SUM(p.Rows)
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SELECT @Total - COUNT(*)
FROM dbo.Users AS u
WHERE u.Age >= 18

SQL Server 실행 시간 : CPU 시간 = 0ms, 경과 시간 = 0ms '작업대'표. 스캔 카운트 0, 논리적 읽기 0, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0. 테이블 'sysrowsets'. 스캔 횟수 2, 논리적 읽기 10, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0. 테이블 'sysschobjs'. 스캔 카운트 1, 논리적 읽기 4, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0.

SQL Server 실행 시간 : CPU 시간 = 0ms, 경과 시간 = 1ms (1 행 반환)

'사용자'표. 스캔 횟수 17, 논리적 읽기 201567, 물리적 읽기 0, 미리 읽기 0, lob 논리적 읽기 0, lob 물리적 읽기 0, lob 미리 읽기 0

SQL Server 실행 시간 : CPU 시간 = 1471ms, 경과 시간 = 98ms

기타 참고 사항 : 아래에 언급 된 것처럼 DBCC TRACEON은 Stack Exchange Data Explorer에서 허용되지 않습니다.

'STACKEXCHANGE \ svc_sede'사용자에게 DBCC TRACEON을 실행할 권한이 없습니다.


1
그들은 아마 내가하는 것과 같은 색인을 가지고 있지 않으므로 차이점이 있습니다. 누가 알 겠어요? 어쩌면 내 홈 서버가 더 나은 하드웨어에있을 것입니다.
Erik Darling

첫 번째 시도에 대해 다음 쿼리를 사용해야합니다 (많은 sys.objects-overhead를 제거하므로 훨씬 빠릅니다) : SELECT SUM(p.Rows) - (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age >= 18 ) FROM sys.partitions p WHERE p.index_id < 2 AND p.object_id = OBJECT_ID('dbo.Users')
Thomas Franz

추신 : 인 메모리 인덱스 (NONCLUSTERED HASH)는 공통 힙 / 클러스터 인덱스와 같이 인덱스 id = 0 / 1를 갖지 않습니다)
Thomas Franz

1

변수를 사용 하시겠습니까?

declare @int1 int = ( select count(*) from table_1 where bb <= 1 )
declare @int2 int = ( select count(*) from table_1 where bb is null )
select @int1 + @int2;

코멘트 당 변수를 건너 뛸 수 있습니다

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);

3
SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ

3
CPU 및 IO를 확인하면서 시도해 볼 수 있습니다. 힌트 : Erik의 답변 중 하나와 동일합니다.
Brent Ozar

0

잘 사용 SET ANSI_NULLS OFF;

SET ANSI_NULLS OFF; 
SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE age=NULL or age<18

Table 'Users'. Scan count 17, logical reads 201567

 SQL Server Execution Times:
 CPU time = 2344 ms,  elapsed time = 166 ms.

이것은 내 마음에 터진 것입니다. https://data.stackexchange.com 에서 이것을 실행했습니다.

그러나 @blitz_erik만큼 효율적이지 않습니다.


0

사소한 해결책은 count (*)-count (age> = 18)를 계산하는 것입니다.

SELECT
    (SELECT COUNT(*) FROM Users) -
    (SELECT COUNT(*) FROM Users WHERE Age >= 18);

또는:

SELECT COUNT(*)
     - COUNT(CASE WHEN Age >= 18)
FROM Users;

결과는 여기

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.