존재하지 않음 vs 존재하지 않음


538

이 중 어떤 쿼리가 더 빠릅니까?

존재하지 않음 :

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

또는 안 :

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

쿼리 실행 계획에 따르면 둘 다 동일한 작업을 수행합니다. 이 경우 권장되는 양식은 무엇입니까?

이것은 NorthWind 데이터베이스를 기반으로합니다.

[편집하다]

이 유용한 기사를 찾았습니다. http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

나는 존재하지 않을 것이라고 생각합니다.


3
null이있는 왼쪽 조인을 사용하여 계획을 시도 했습니까?
Sebas

1
NOT IN과 NOT EXISTS는 동일하지 않습니다. : 그들 사이의 차이가이 링크를 한 번 봐 가지고 weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
아메 Gokhale

2
데이터베이스가 다른지 궁금하지만 PostgreSQL에 대한 최신 벤치 마크에서이 NOT IN쿼리 SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)는 거의 30 배나 빠릅니다 NOT EXISTS.SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phương Nguyễn


1
@rcdmk 질문에서 날짜를 확인 했습니까?
ilitirit

답변:


693

항상 기본값은 NOT EXISTS입니다.

실행 계획은 현재 동일 할 수 있지만 나중에 두 열을 변경하여 NULLs 를 허용 하면 NOT IN버전에 더 많은 작업을 수행해야합니다 ( NULL실제로 데이터에 s가 없는 경우에도 ) 및 s 있는 NOT IN경우 NULL의 의미 어쨌든 원하는 사람이 아닐 것입니다.

시도 Products.ProductID또는 [Order Details].ProductIDNULL의을은 NOT IN다음 쿼리와 동일하게 취급됩니다.

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId) 

정확한 계획은 다를 수 있지만 내 예제 데이터의 경우 다음을 얻습니다.

NULL이 아님

합리적으로 일반적인 오해는 연관 하위 쿼리가 조인에 비해 항상 "나쁜"것입니다. 중첩 된 루프 계획 (행별로 평가 된 하위 쿼리)을 강제로 적용 할 때도 가능하지만이 계획에는 반반 결합 논리 연산자가 포함됩니다. 안티 세미 조인은 중첩 루프로 제한되지 않지만 해시 또는 병합 (이 예와 같이) 조인도 사용할 수 있습니다.

/*Not valid syntax but better reflects the plan*/ 
SELECT p.ProductID,
       p.ProductName
FROM   Products p
       LEFT ANTI SEMI JOIN [Order Details] od
         ON p.ProductId = od.ProductId 

경우 [Order Details].ProductID입니다 NULL-able 쿼리는된다

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL) 

s가 [Order Details]포함 된 경우 올바른 의미론 NULL ProductId은 결과를 반환하지 않기 때문입니다. 추가 반반 결합 및 행 수 스풀을 참조하여 계획에 추가되었는지 확인하십시오.

하나의 NULL

경우 Products.ProductID도되기 위해 변경 NULL-able 쿼리는된다

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL)
       AND NOT EXISTS (SELECT *
                       FROM   (SELECT TOP 1 *
                               FROM   [Order Details]) S
                       WHERE  p.ProductID IS NULL) 

그 이유 는 하위 쿼리가 결과를 전혀 반환하지 않는 경우 (예 : 테이블이 비어있는 경우)를 제외하고NULL Products.ProductId 결과에 a 를 반환 하면 안되기 때문 입니다. 어떤 경우에해야합니다. 내 샘플 데이터 계획에서 이것은 다음과 같이 다른 반 세미 조인을 추가하여 구현됩니다.NOT IN[Order Details]

둘 다 NULL

이것의 효과는 Buckley에 의해 이미 링크 된 블로그 게시물에 표시됩니다 . 이 예에서는 논리적 읽기 수가 약 400에서 500,000으로 증가합니다.

또한 하나 NULL의 행 수를 0으로 줄일 수 있다는 사실 은 카디널리티 추정을 매우 어렵게 만듭니다. SQL Server가 이러한 상황이 발생한다고 가정하지만 실제로 NULL데이터에 행 이 없으면 나머지 실행 계획이 더 큰 쿼리의 일부인 경우 치명적으로 악화 될 수 있습니다. 부적절한 중첩 루프로 인해 값 비싼 하위 항목이 반복적으로 실행되는 예를 들어 트리 .

이것은 유일한 가능한 실행 계획이없는 NOT INA의 NULL그러나 -able 열입니다. 이 기사는 다른 것을 보여줍니다AdventureWorks2008 데이터베이스 에 대한 쿼리에 대한 .

를 들어 NOT INA의 NOT NULL열 또는 NOT EXISTS널 (NULL) 또는 비 널 (NULL) 열 중 하나에 대한 다음과 같은 계획을 제공합니다.

존재하지 않음

열이 NULL-able로 변경되면 NOT IN계획은 다음과 같습니다.

없음-널

내부 조인 연산자를 계획에 추가합니다. 이 장치는 여기설명되어 있습니다 . 이전의 단일 상관 ​​인덱스 검색을 Sales.SalesOrderDetail.ProductID = <correlated_product_id>외부 행당 두 개의 검색으로 변환하는 것이 전부 입니다. 추가가 켜져 WHERE Sales.SalesOrderDetail.ProductID IS NULL있습니다.

반 반 조인 아래 있으므로 행을 반환하면 두 번째 탐색이 발생하지 않습니다. 그러나을 Sales.SalesOrderDetail포함하지 않으면 NULL ProductID필요한 검색 작업 수의 두 배가됩니다.


4
그림과 같은 프로파일 링 그래프를 어떻게 구할 수 있습니까?
xis

5
@xis SQL Sentry 계획 탐색기에서 열린 실행 계획입니다. SSMS에서 실행 계획을 그래픽으로 볼 수도 있습니다.
Martin Smith

나는 유일한 이유 때문에 이것을 고맙게 생각합니다 : NOT EXISTS내가 기대하는 방식대로 NOT IN기능합니다 (그렇지 않습니다).
levininja

NOT EXISTS를 사용하면 데이터베이스가 실제로 디스크에서 열을 반환 할 필요가 없도록 NOT EXISTS (SELECT 1 FROM sometable WHERE 무언가)와 같은 SELECT 1을 사용하려고합니다. EXPLAIN을 사용하여 사례에 차이가 있는지 확인하는 것이 좋습니다.
Mayur Patel

4
@Mayur SQL Server에서는 필요하지 않습니다. stackoverflow.com/questions/1597442/…
마틴 스미스

84

또한 NOT IN은 널이 될 때 NOT EXISTS와 동일하지 않습니다.

이 게시물은 그것을 잘 설명합니다

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

부속 조회가 하나의 널 (null)도 리턴하면 NOT IN은 행과 일치하지 않습니다.

NOT IN 조작의 실제 세부 사항을 보면이 이유를 찾을 수 있습니다.

예를 들어, t라는 테이블에 4 개의 행이 있고 값이 1..4 인 ID라는 열이 있다고 가정 해 봅시다.

WHERE SomeValue NOT IN (SELECT AVal FROM t)

에 해당

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

AVal은 ID = 4 인 경우 NULL입니다. 따라서! = 비교는 UNKNOWN을 반환합니다. AND에 대한 논리 진리표는 UNKNOWN과 TRUE가 UNKNOWN, UNKNOWN, FALSE가 FALSE임을 나타냅니다. 결과를 TRUE로 만들기 위해 UNKNOWN으로 AND 할 수있는 값이 없습니다.

따라서 해당 부속 쿼리의 행이 NULL을 리턴하면 전체 NOT IN 연산자는 FALSE 또는 NULL로 평가되며 레코드가 리턴되지 않습니다.


24

실행 플래너가 동일하다고 말하면 동일합니다. 당신의 의도를 더 명확하게 할 수있는 방법을 사용하십시오 –이 경우에는 두 번째 방법.


3
실행 플래너 시간은 동일 할 수 있지만 실행 결과가 다를 수 있으므로 차이가 있습니다. NOT IN은 데이터 세트에 NULL이 있으면 예기치 않은 결과를 생성합니다 (버키의 답변 참조). NOT EXISTS를 기본값으로 사용하는 것이 가장 좋습니다.
nanonerd

15

실제로, 이것이 가장 빠를 것이라고 믿습니다.

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null

2
옵티마이 저가 작업을 수행 할 때는 가장 빠를 수는 없지만 그렇지 않은 경우에는 더 빠를 것입니다.
Cade Roux 2018 년

2
그는이 게시물에 대한 쿼리를 단순화했을 수도 있습니다
Kip

1
동의 왼쪽 외부 조인은 종종 하위 쿼리보다 빠릅니다.
HLGEM

7
@HLGEM 동의하지 않습니다. 내 경험에 따르면 LOJ의 가장 좋은 경우는 LOJ가 동일하고 SQL Server가 LOJ를 반 세미 조인으로 변환한다는 것입니다. 최악의 경우 SQL Server LEFT는 모든 것을 결합하고 그 이후의 NULL을 필터링하여 훨씬 비효율적 일 수 있습니다. 이 기사의 하단에있는 예
Martin Smith

12

약 120,000 개의 레코드가있는 테이블이 있고 행 수가 약 1500, 4000, 40000, 200 인 다른 4 개의 테이블에 존재하지 않는 테이블 (varchar 열과 일치) 만 선택하면됩니다. 관련된 모든 테이블에는 고유 인덱스가 있습니다. 관련 Varchar열에.

NOT IN약 10 분이 NOT EXISTS걸렸고 4 초가 걸렸습니다.

나는 10 분에 기여 수있는 몇 가지 조정되지 않은 부분을 가지고 있습니다 재귀 쿼리를 가지고 있지만, 4 초를 복용 다른 옵션은 나에게이어야, 설명 NOT EXISTS훨씬 더 나은 또는 그 이상이다 IN하고 EXISTS있습니다 정확히 같은 항상 가치 코드를 진행하기 전에 확인하십시오.


8

옵티마이 저가 수행하려는 작업이 두 예제에서 모두 동일하다는 것을 알았으므로 특정 예제에서는 동일합니다. 그러나 사소한 예에서는 최적화 프로그램이이를 수행하지 않을 수 있으며,이 경우 경우에 따라 서로 선호하는 이유가 있습니다.

NOT IN외부 선택에서 여러 행을 테스트하는 경우 선호됩니다. NOT IN명령문 내부의 하위 쿼리 는 실행이 시작될 때 평가 될 수 있으며 임시 테이블은 필요할 때마다 하위 선택을 다시 실행하지 않고 외부 선택의 각 값에 대해 검사 할 수 있습니다.NOT EXISTS 명령문에 .

부속 조회 외부 선택과 상관 되어야 하는 경우, NOT EXISTS옵티마이 저가 동일한 기능을 수행하기 위해 임시 테이블을 작성하지 못하게하는 단순화를 발견 할 수 있기 때문에 바람직 할 수 있습니다.


6

나는 사용하고 있었다

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

결과가 잘못되었다는 것을 알았습니다 (잘못된 결과는 없습니다). TABLE2.Col1에 NULL이 있으므로

검색어를로 변경하는 동안

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

나에게 정확한 결과를 주었다.

그 이후로 나는 모든 곳에서 NOT EXISTS를 사용하기 시작했습니다.


5

그것들은 매우 유사하지만 실제로는 동일하지 않습니다.

효율성 측면에서 왼쪽 조인은 null 문이 더 효율적 이라는 것을 알았습니다 (많은 행을 선택해야 할 때)


2

옵티마이 저가 같다고하면 인적 요소를 고려하십시오. 나는 존재하지 않는 것을 선호합니다 :)


1

이것은 매우 좋은 질문이므로 내 블로그에이 주제에 대한 매우 자세한 기사 를 작성하기로 결정했습니다 .

데이터베이스 테이블 모델

데이터베이스에 일대 다 테이블 관계를 형성하는 다음 두 테이블이 있다고 가정합니다.

SQL EXISTS 테이블

student테이블은 부모이며,student_grade 이 학생 테이블의 ID를 기본 키 열을 참조하는 student_id 외래 키 열이 있기 때문에 자식 테이블입니다.

이에 student table는 다음 두 가지 레코드가 포함됩니다.

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

그리고 student_grade테이블에는 학생들이받은 성적이 저장됩니다.

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL 존재

수학 수업에서 10 학년을받은 모든 학생들을 원한다고 가정 해 봅시다.

학생 식별자에만 관심이 있다면 다음과 같은 쿼리를 실행할 수 있습니다.

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

그러나 응용 프로그램은 student식별자뿐만 아니라의 전체 이름을 표시하는 데 관심이 있으므로student 테이블의 합니다.

studentMath에서 10 등급을 가진 레코드 를 필터링하기 위해 다음과 같이 EXISTS SQL 연산자를 사용할 수 있습니다.

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

위의 쿼리를 실행할 때 Alice 행만 선택되었음을 알 수 있습니다.

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

외부 쿼리는 student클라이언트에게 반환하려는 행 열을 선택합니다 . 그러나 WHERE 절은 연관된 내부 서브 쿼리와 함께 EXISTS 연산자를 사용하고 있습니다.

부속 조회가 하나 이상의 레코드를 리턴하면 EXISTS 연산자는 true를 리턴하고 행을 선택하지 않으면 false를 리턴합니다. 데이터베이스 엔진은 하위 쿼리를 완전히 실행할 필요가 없습니다. 단일 레코드가 일치하면 EXISTS 연산자는 true를 리턴하고 연관된 다른 조회 행이 선택됩니다.

내부 하위 쿼리는 student_grade테이블 이 외부 학생 테이블의 id 열과 일치 됩니다.

존재하지 않는 SQL

9 학년 이하의 모든 학생을 선발한다고 가정 해 봅시다.이를 위해 EXISTS 연산자를 사용하지 않는 NOT EXISTS를 사용할 수 있습니다.

따라서 기본 하위 쿼리가 레코드를 반환하지 않으면 NOT EXISTS 연산자는 true를 반환합니다. 그러나 내부 하위 쿼리와 단일 레코드가 일치하면 NOT EXISTS 연산자는 false를 반환하고 하위 쿼리 실행을 중지 할 수 있습니다.

연관된 student_grade가없는 모든 학생 레코드를 9보다 낮은 값과 일치시키기 위해 다음 SQL 쿼리를 실행할 수 있습니다.

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

위의 쿼리를 실행하면 Alice 레코드 만 일치 함을 알 수 있습니다.

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

따라서 SQL EXISTS 및 NOT EXISTS 연산자를 사용하면 일치하는 레코드가있는 한 내부 서브 쿼리 실행을 중지 할 수 있다는 장점이 있습니다.


-1

때에 따라 다르지..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

키가 입력되어 있는지 확인하기 위해 쿼리에서 확인하는 크기를 크게 제한하지는 않습니다.이 경우 EXISTS가 선호됩니다.

그러나 DBMS 옵티 마이저에 따라 다르지 않습니다.

EXISTS가 더 좋은 경우의 예로서

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria

1
INEXISTS SQL 서버에서 같은 계획을 얻을 . 문제는 NOT INNOT EXISTS어쨌든입니다.
Martin Smith
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.