이것은 정기적으로 제기되는 문제이며 아직 좋은 해결책을 찾지 못했습니다.
다음 테이블 구조를 가정
CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)
그리고 요구 사항은 널 입력 가능 열 중 하나 B
또는 C
실제로 임의의 NULL
값을 포함 하는지 여부를 판별하는 것입니다 (있는 경우).
또한 테이블에 수백만 개의 행이 포함되어 있다고 가정합니다 (이 클래스의 쿼리에 대한보다 일반적인 솔루션에 관심이 있으므로 열 통계를 사용할 수 없음).
나는 이것에 접근하는 몇 가지 방법을 생각할 수 있지만 모두 약점이 있습니다.
두 개의 별도 EXISTS
진술. 이는 쿼리 NULL
가 발견 되 자마자 검색을 일찍 중지 할 수 있다는 이점이 있습니다 . 그러나 실제로 두 열에 모두 NULL
s 가 없으면 두 번의 전체 스캔이 수행됩니다.
단일 집계 쿼리
SELECT
MAX(CASE WHEN B IS NULL THEN 1 ELSE 0 END) AS B,
MAX(CASE WHEN C IS NULL THEN 1 ELSE 0 END) AS C
FROM T
이렇게하면 두 열을 동시에 처리 할 수 있으므로 전체 스캔이 최악 인 경우가 있습니다. 단점은 NULL
쿼리에서 두 열 모두에서 매우 일찍 발생하더라도 나머지 테이블 전체를 계속 스캔한다는 것입니다.
사용자 변수
나는 이것을 하는 세 번째 방법을 생각할 수 있습니다.
BEGIN TRY
DECLARE @B INT, @C INT, @D INT
SELECT
@B = CASE WHEN B IS NULL THEN 1 ELSE @B END,
@C = CASE WHEN C IS NULL THEN 1 ELSE @C END,
/*Divide by zero error if both @B and @C are 1.
Might happen next row as no guarantee of order of
assignments*/
@D = 1 / (2 - (@B + @C))
FROM T
OPTION (MAXDOP 1)
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 /*Divide by zero*/
BEGIN
SELECT 'B,C both contain NULLs'
RETURN;
END
ELSE
RETURN;
END CATCH
SELECT ISNULL(@B,0),
ISNULL(@C,0)
그러나 집계 연결 쿼리의 올바른 동작이 정의되어 있지 않으므로 프로덕션 코드에 적합하지 않습니다. 오류를 발생시켜 스캔을 종료하는 것은 어쨌든 끔찍한 해결책입니다.
위의 접근 방식의 장점을 결합한 다른 옵션이 있습니까?
편집하다
@ypercube의 테스트 데이터를 사용하여 지금까지 제출 된 답변에 대한 읽기 측면에서 얻은 결과로 이것을 업데이트하십시오.
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | 2 * EXISTS | CASE | Kejser | Kejser | Kejser | ypercube | 8kb |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | | | | MAXDOP 1 | HASH GROUP, MAXDOP 1 | | |
| No Nulls | 15208 | 7604 | 8343 | 7604 | 7604 | 15208 | 8346 (8343+3) |
| One Null | 7613 | 7604 | 8343 | 7604 | 7604 | 7620 | 7630 (25+7602+3) |
| Two Null | 23 | 7604 | 8343 | 7604 | 7604 | 30 | 30 (18+12) |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
@Thomas의 답변 TOP 3
을 TOP 2
위해 잠재적으로 더 일찍 종료되도록 변경 했습니다 . 나는 그 대답에 대해 기본적으로 병렬 계획을 얻었으므로 MAXDOP 1
다른 계획과 비교하여 읽기 수를 더하기 위해 힌트로 시도했습니다 . 이전 테스트에서 전체 테이블을 읽지 않고 쿼리 단락을 보았으므로 결과에 다소 놀랐습니다.
단락이 다음과 같은 테스트 데이터 계획
ypercube의 데이터 계획은
따라서 계획에 블로킹 정렬 연산자를 추가합니다. 나는 또한 HASH GROUP
힌트로 시도 했지만 여전히 모든 행을 읽습니다.
따라서 hash match (flow distinct)
다른 대안은 어쨌든 모든 행을 차단하고 소비하므로 운영자 가이 계획을 단락시킬 수 있도록 하는 것이 핵심 입니다. 나는 이것을 구체적으로 강요 할 힌트가 없다고 생각하지만 분명히 "일반적으로 옵티마이 저는 입력 세트에 고유 한 값보다 적은 수의 출력 행이 필요하다고 결정하는 Flow Distinct를 선택합니다." .
@ypercube의 데이터는 각 열에 하나의 행 NULL
(값은 카디널리티 = 30300)을 가지며 연산자로 들어오고 나가는 예상 행은 모두 1
입니다. 옵티 마이저에 술어를 좀 더 불투명하게하여 플로우 구별 연산자를 사용하여 계획을 생성했습니다.
SELECT TOP 2 *
FROM (SELECT DISTINCT
CASE WHEN b IS NULL THEN NULL ELSE 'foo' END AS b
, CASE WHEN c IS NULL THEN NULL ELSE 'bar' END AS c
FROM test T
WHERE LEFT(b,1) + LEFT(c,1) IS NULL
) AS DT
편집 2
떠올랐다 마지막 비틀기 쿼리 위에서 여전히 함께 첫 번째 행이 발생하는 경우에 필요 이상으로 열을 처리 끝낼 수 있다는 것이다 NULL
열 모두 NULL을 가지고 B
와 C
. 즉시 종료하지 않고 계속 스캔합니다. 이것을 피하는 한 가지 방법은 행을 스캔 할 때 피벗을 해제하는 것입니다. Thomas Kejser의 답변에 대한 최종 수정안 은 다음과 같습니다.
SELECT DISTINCT TOP 2 NullExists
FROM test T
CROSS APPLY (VALUES(CASE WHEN b IS NULL THEN 'b' END),
(CASE WHEN c IS NULL THEN 'c' END)) V(NullExists)
WHERE NullExists IS NOT NULL
술어가 WHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULL
이전 테스트 데이터와 비교하는 것이 더 좋을 것입니다. 하지만 흐름 구별이있는 계획을 제공하지 않는 반면 NullExists IS NOT NULL
(아래 계획).
TOP 3
만있을 수TOP 2
는 다음 각 중 하나를 찾을 때까지이 스캔 현재로(NOT_NULL,NULL)
,(NULL,NOT_NULL)
,(NULL,NULL)
. 이 3 개 중 2 개는 충분할 것입니다.(NULL,NULL)
첫 번째를 찾으면 두 번째도 필요하지 않습니다. 또한 단락을 위해 계획은 통해 구분을 구현해야hash match (flow distinct)
보다는 운영자hash match (aggregate)
또는distinct sort