첫 번째 인수가 NULL이 아니더라도 SQL Server는 모든 COALESCE 함수를 읽습니까?


98

COALESCE첫 번째 인수가 실행 된 시간의 약 95 %에서 null이되지 않는 T-SQL 함수를 사용하고 있습니다. 첫 번째 인수가 NULL인 경우 두 번째 인수는 상당히 긴 프로세스입니다.

SELECT COALESCE(c.FirstName
                ,(SELECT TOP 1 b.FirstName
                  FROM TableA a 
                  JOIN TableB b ON .....)
                )

예를 들어 c.FirstName = 'John'SQL Server가 여전히 하위 쿼리를 실행합니까?

VB.NET IIF()함수를 사용하면 두 번째 인수가 True 인 경우 코드는 세 번째 인수를 읽습니다 (사용되지 않더라도).

답변:


95

아뇨 . 간단한 테스트는 다음과 같습니다.

SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error

두 번째 조건이 평가되면 0으로 나누기 예외가 발생합니다.

MSDN 설명서에 따르면 이것은 COALESCE인터프리터가 보는 방법과 관련이 CASE있습니다. 이것은 문장 을 작성하는 쉬운 방법 입니다.

CASE SQL Server에서 (주로) 안정적으로 단락되는 유일한 기능 중 하나로 잘 알려져 있습니다.

아론 버트 랜드 같이 다른 대답을 여기에 스칼라 변수와 집계에 비해 (이것은 모두에 적용됩니다 몇 가지 예외가 있습니다 CASECOALESCE) :

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

0으로 나누기 오류가 발생합니다.

이것은 버그로 간주되어야하며 일반적 COALESCE으로 왼쪽에서 오른쪽으로 구문 분석됩니다.


6
@JNK는 이것이 사실이 아닌 매우 간단한 사례를 보려면 내 대답을 참조하십시오 (제 관심사는 더 많지만 아직 발견되지 않은 시나리오가 있다는 것입니다. CASE항상 좌우에서 항상 단락을 평가 한다는 데 동의하기가 어렵습니다. ).
Aaron Bertrand

4
@SQLKiwi의 다른 흥미로운 동작은 다음과 같습니다 SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 1 END), 1);.-여러 번 반복하십시오. NULL때때로 얻을 것 입니다. 다시 시도해보십시오 ISNULL-당신은 결코 얻지 못할 것입니다 NULL...
Aaron Bertrand


@Martin 네, 그렇게 믿습니다. 그러나 대부분의 사용자가 그 문제에 대해 들어 본 적이 없거나 물지 않는 한 직관적이지 않은 행동은 아닙니다.
Aaron Bertrand

73

Itimek Ben-Gan 은 Jaime Lafargue에 의해이 소식을 들었습니다 .

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

결과:

Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.

이 과정의 사소한 해결 방법이 있지만 요점은 아직 CASE하지 않습니다 항상 왼쪽에서 오른쪽으로 평가 / 단락을 보장합니다. 여기서 버그를보고했으며 "디자인 상"으로 닫혔습니다. Paul White는 이후이 Connect 항목을 제출 했으며 고정으로 닫혔습니다. 자체적으로 수정 된 것이 아니라 집계가 CASE식 의 평가 순서를 변경할 수있는 시나리오에 대한보다 정확한 설명으로 온라인 설명서를 업데이트했기 때문 입니다. 나는 최근 에 이것에 대해 더 많은 블로그를 올렸습니다 .

부록을 편집 하면서, 나는 이것이 최우선 사례이며, 대부분의 경우 왼쪽에서 오른쪽으로 평가 및 단락에 의존 할 수 있으며, 문서와 모순되는 버그이며 결국 수정 될 것이라고 동의합니다 ( 이것은 확실하지 않습니다 -Bart Duncan의 블로그 게시물 에서 후속 대화 를 참조하여 이유를 확인하십시오), 사람들이 그것을 반증하는 단일 에지 사례가 있더라도 항상 진실이라고 말할 때 의견에 동의하지 않아야합니다. Itzik과 다른 사람들이 이와 같은 독방 버그를 발견 할 수 있다면 적어도 다른 버그도있을 수 있습니다. 우리는 OP의 나머지 질문을 알지 못하기 때문에 그가이 단락에 의존 할 것이라고 확신 할 수는 없지만 결국 물린 것으로 끝날 수 있습니다. 나에게 더 안전한 대답은 다음과 같습니다.

당신은 할 수 있지만 일반적으로 의존 CASE왼쪽에서 오른쪽 및 단락 회로를 평가하기 위해 문서에 설명 된대로, 당신이 항상 할 수있는 말을 정확하지 않습니다. 이 페이지에는 사실이 아닌 두 가지 사례가 있으며 공개적으로 사용 가능한 SQL Server 버전에서는 버그가 수정되지 않았습니다.

여기서 편집 CASE 집계가 포함되어 있지 않더라도식이 예상 순서대로 평가되지 않는 또 다른 경우입니다 (그만해야합니다) .



IMO 이것은 선택하기 전에 집계 값이 계산되므로 CASE 표현식의 평가가 보장되지 않음을 증명하지 못합니다 (따라서 내부에서 사용할 수 있음).
Salman A

1
@SalmanA CASE 표현식의 평가 순서가 정확하게 보장되지 않는다는 것을 증명하는 것 외에는 이것이 무엇을 할 수 있는지 잘 모르겠습니다. ELSE 절에 있지만 집계가 먼저 계산되기 때문에 예외가 발생합니다. 문서로 이동하면 절대 도달해서는 안됩니다.
Aaron Bertrand

@AaronBertrand 집계는 CASE 문 전에 계산 되며 IMO 여야합니다. 개정 된 문서 는 CASE를 평가 하기 전에 오류가 발생했음을 정확하게 지적합니다 .
Salman A

@SalmanA CASE 표현식이 작성된 순서대로 평가하지 않는다는 것을 여전히 일반 개발자에게 보여줍니다. 수행하려는 모든 것이 CASE 브랜치에서 오류가 발생하는 이유를 이해하는 것이라면 기본 역학은 관련이 없습니다. t에 도달했습니다. 이 페이지의 다른 모든 예제에 대해서도 논쟁이 있습니까?
Aaron Bertrand

37

이것에 대한 나의 견해는 문서가 CASE가 단락되어야한다는 의도 가 합리적으로 명확하다는 것 입니다. Aaron이 언급했듯이, 이것이 항상 사실이 아닌 것으로 밝혀진 몇 가지 사례 (ha!)가있었습니다.

지금까지 이러한 모든 버그는 버그로 인식되어 수정되었습니다. 반드시 SQL Server 버전에서는 구매할 수있는 것은 아니지만 오늘날에도 계속 접히는 버그로 인해 누적 업데이트 AFAIK가되지는 않았습니다. Itzik Ben-Gan이 처음보고 한 잠재적 인 최신 버그는 아직 조사되지 않았습니다 (Aaron 또는 곧 Connect에 추가 할 예정 임).

원래의 질문과 관련하여 부작용이있는 기능이나 하위 쿼리가 사용되는 CASE (및 COALESCE)와 관련된 다른 문제가 있습니다. 치다:

SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);

COALESCE 양식은 종종 NULL을 반환합니다. 자세한 내용은 https://connect.microsoft.com/SQLServer/feedback/details/546437/coalesce-subquery-1-may-return-null참조하십시오.

최적화 변환 및 공통 표현 추적과 관련하여 입증 된 문제는 모든 상황에서 CASE가 단락되는 것을 보장 할 수 없음을 의미합니다. 공개 쇼 계획 결과를 조사하여 행동을 예측할 수조차없는 경우도 생각할 수 있지만, 오늘은 이에 대한 재현 책이 없습니다.

요약하자면, CASE가 일반적으로 합선 될 것이라고 확신 할 수 있다고 생각합니다. 절대 보장을 위해서는 표현식이 전혀 포함되지 않은 SQL을 작성해야합니다.

매우 만족스러운 상황이 아니라고 생각합니다.


18

나는 다른 경우 건너 한 CASE/이 COALESCE쇼트을한다. 다음 TVF는 1매개 변수로 전달되면 PK 위반을 발생 시킵니다.

CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
  C INT PRIMARY KEY)
AS
  BEGIN
      INSERT INTO @T
      VALUES      (1),
                  (@P)

      RETURN
  END

다음과 같이 호출하면

DECLARE @Number INT = 1

SELECT COALESCE(@Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = @Number), 
                         (SELECT TOP (1)  C
                          FROM   F(@Number))) 

또는

DECLARE @Number INT = 1

SELECT CASE
         WHEN @Number = 1 THEN @Number
         ELSE (SELECT TOP (1) C
               FROM   F(@Number))
       END 

둘 다 결과를 제공합니다

PRIMARY KEY 제약 조건 'PK__F__3BD019A800551192'위반 'dbo. @ T'개체에 중복 키를 삽입 할 수 없습니다. 중복 키 값은 (1)입니다.

보여주는 SELECT(또는 적어도 테이블 변수 인구가) 아직 실행 명령문의 지점에 도달해서는 안됩니다에도 불구하고 오류가 발생합니다. COALESCE버전 계획 은 다음과 같습니다.

계획

이 쿼리를 다시 작성하면 문제가 발생하지 않습니다.

SELECT COALESCE(Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = Number), 
                         (SELECT TOP (1)  C
                          FROM   F(Number))) 
FROM (VALUES(1)) V(Number)   

어떤 계획을 제공

Plan2


8

또 다른 예

CREATE TABLE T1 (C INT PRIMARY KEY)

CREATE TABLE T2 (C INT PRIMARY KEY)

INSERT INTO T1 
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);

쿼리

SET STATISTICS IO ON;

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (LOOP JOIN)

전혀 읽지 않습니다 T2.

탐색은 T2통과 술어 아래에 있으며 연산자는 절대 실행되지 않습니다. 그러나

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (MERGE JOIN)

합니까T2읽기를. 의 가치 T2가 실제로는 없지만 .

물론 이것은 놀라운 일이 아니지만 세트 예제 선언 언어에서 단락이 의미하는 문제를 제기하기 때문에 카운터 예제 저장소에 추가 할 가치가 있다고 생각했습니다.


7

나는 당신이 고려하지 않은 전략을 언급하고 싶었습니다. 여기서는 일치하지 않을 수 있지만 때로는 편리합니다. 이 수정 사항이 더 나은 성능을 제공하는지 확인하십시오.

SELECT COALESCE(c.FirstName
            ,(SELECT TOP 1 b.FirstName
              FROM TableA a 
              JOIN TableB b ON .....
              WHERE C.FirstName IS NULL) -- this is the changed part
            )

이를 수행하는 다른 방법은 다음과 같습니다 (기본적으로 동일하지만 필요한 경우 다른 쿼리에서 더 많은 열에 액세스 할 수 있음).

SELECT COALESCE(c.FirstName, x.FirstName)
FROM
   TableC c
   OUTER APPLY (
      SELECT TOP 1 b.FirstName
      FROM
         TableA a 
         JOIN TableB b ON ...
      WHERE
         c.FirstName IS NULL -- the important part
   ) x

기본적으로 이것은 "하드"조인 기술이지만 행을 결합해야 할 때의 조건을 포함합니다. 내 경험상 이것은 때때로 실행 계획에 도움이되었습니다.


3

아닙니다. c.FirstNameis 일 때만 실행됩니다 NULL.

그러나 직접 시도해야합니다. 실험. 당신은 당신의 하위 쿼리가 길다고 말했다. 기준. 이것에 대한 자신의 결론을 도출하십시오.

실행중인 하위 쿼리에 대한 @Aaron 답변이 더 완벽합니다.

그러나 여전히 쿼리를 다시 작성하고을 사용해야한다고 생각합니다 LEFT JOIN. 대부분의 경우 LEFT JOINs 를 사용하도록 쿼리를 재 작업하여 하위 쿼리를 제거 할 수 있습니다 .

하위 쿼리를 사용할 때의 문제점은 기본 쿼리 결과 세트의 각 행에 대해 하위 쿼리가 실행되므로 전체 명령문이 느리게 실행된다는 것입니다.


@Adrian 그것은 여전히 ​​옳지 않습니다. 실행 계획을 보면 하위 쿼리가 종종 현명하게 JOIN으로 변환되는 것을 볼 수 있습니다. 스캔과의 중첩 루프 조인을 선택하면 효과적으로 발생할 수 있지만 각 행마다 전체 하위 쿼리를 반복해서 실행해야한다고 생각하는 것은 단순한 실험 경험 오류 입니다.
ErikE

3

실제 표준에 따르면 표현식의 데이터 유형을 전체적으로 결정하려면 모든 WHEN 절 (ELSE 절과 구문)을 구문 분석해야합니다. 오류를 처리하는 방법을 결정하려면 이전 메모 중 일부를 꺼내야합니다. 그러나 1/0은 정수를 사용하므로 오류가 있다고 가정합니다. 정수 데이터 유형에 오류가 있습니다. 통합 목록에 Null 만있는 경우 데이터 유형을 결정하기가 약간 까다로워지며 이는 또 다른 문제입니다.

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