NOT IN 절 내의 NULL 값


245

이 문제는 하나의 not in where제약 조건을 사용하는 동일한 쿼리 와 다른 쿼리에 대해 다른 레코드 수를 얻었을 때 발생 했습니다 left join. not in제약 조건 의 테이블에 하나의 null 값 (잘못된 데이터)이있어 해당 쿼리가 0 레코드 수를 반환했습니다. 나는 왜 그런지 이해하지만 개념을 완전히 이해하는 데 도움을 줄 수 있습니다.

간단히 말해 쿼리 A는 결과를 반환하지만 B는 그렇지 않은 이유는 무엇입니까?

A: select 'true' where 3 in (1, 2, 3, null)
B: select 'true' where 3 not in (1, 2, null)

이것은 SQL Server 2005에있었습니다. 또한 호출 set ansi_nulls off하면 B가 결과를 반환 한다는 것을 알았습니다 .

답변:


283

쿼리 A는 다음과 같습니다.

select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null

3 = 3사실 이므로 결과를 얻습니다.

쿼리 B는 다음과 같습니다.

select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null

경우 ansi_nulls에이다, 3 <> nullUNKNOWN는 술어 평가됩니다 그래서 UNKNOWN에, 당신은 어떤 행을하지 않습니다.

ansi_nulls꺼져, 3 <> null술어 평가하여 이렇게 참으로, 사실, 당신은 행을 얻을.


11
아무도이 NOT IN일련의 <> and변경으로 변환 하는 것이이 세트아닌 의미 론적 행동을 다른 것으로 바꾸 었다고 지적한 적이 있습니까?
Ian Boyd

8
@Ian- "A NOT IN ( 'X', 'Y')"은 실제로 SQL에서 A <> 'X'및 A <> 'Y'의 별칭입니다. (당신이 stackoverflow.com/questions/3924694/… 에서 직접 발견 한 것을 보았지만이 질문에 이의가 제기되었는지 확인하고 싶었습니다.)
Ryan Olson

이것이 SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);내가 예상 한 빈 결과 집합 대신 행을 생성하는 이유를 설명하는 것 같습니다 .
binki

2
"IS NULL"을 사용하여 NULL 비교를 예상하는 경우 IN 절을 동일한 동작으로 확장해야하며 잘못된 의미를 자체적으로 어리석게 적용하지 않아야하기 때문에 이는 SQL Server의 매우 잘못된 동작입니다.
OzrenTkalcecKrznaric

@binki, 당신 쿼리가 실행 여기 실행하면 rextester.com/l/sql_server_online_compiler 하지만이 작동하지 않음 실행 여기 경우 sqlcourse.com/cgi-bin/interpreter.cgi .
Istiaque Ahmed

53

NULL을 사용할 때마다 실제로는 3 값 논리를 처리합니다.

첫 번째 쿼리는 WHERE 절이 다음과 같이 평가 될 때 결과를 반환합니다.

    3 = 1 or 3 = 2 or 3 = 3 or 3 = null
which is:
    FALSE or FALSE or TRUE or UNKNOWN
which evaluates to 
    TRUE

두 번째 것 :

    3 <> 1 and 3 <> 2 and 3 <> null
which evaluates to:
    TRUE and TRUE and UNKNOWN
which evaluates to:
    UNKNOWN

UNKNOWN은 FALSE와 같지 않으므로 다음을 호출하여 쉽게 테스트 할 수 있습니다.

select 'true' where 3 <> null
select 'true' where not (3 <> null)

두 쿼리 모두 결과를 제공하지 않습니다

UNKNOWN이 FALSE와 같으면 첫 번째 쿼리가 FALSE를 제공한다고 가정하면 두 번째 쿼리는 NOT (FALSE)과 같았으므로 TRUE로 평가해야합니다.
그렇지 않습니다.

SqlServerCentral에 대한이 주제에 대한 좋은 기사가 있습니다.

NULL과 Three-Valued Logic의 전체 문제는 처음에는 약간 혼란 스러울 수 있지만 TSQL에서 올바른 쿼리를 작성하려면 이해해야합니다.

내가 추천하는 또 다른 기사는 SQL Aggregate Functions 및 NULL 입니다.


33

NOT IN 알 수없는 값과 비교할 때 0 개의 레코드를 반환합니다.

이후 NULL알 수없는이하는 NOT IN포함하는 쿼리 NULL또는 NULL가능한 값의 목록에들 항상 반환됩니다 0확인하는 것이 할 수있는 방법이 없기 때문에 기록을 NULL값이 테스트중인 값이 없습니다.


3
이것은 간단히 대답합니다. 나는 예가 없어도 이해하기 쉽다는 것을 알았습니다.
Govind Rai

18

IS NULL을 사용하지 않으면 널과 비교는 정의되지 않습니다.

따라서 3을 NULL과 비교할 때 (질의 A) 정의되지 않은 값을 반환합니다.

즉 (1,2, null)에서 3이면 SELECT 'true', (1,2, null)에서는 3이면 SELECT 'true'

NOT (UNDEFINED)이 아직 정의되지 않았지만 TRUE가 아닌 동일한 결과를 생성합니다.


좋은 지적입니다. 널 입력 (널)이 행 (ansi)을 리턴하지 않는 경우 1을 선택하십시오.
crokusek

9

글을 쓰는 시점에서이 질문의 제목은

SQL NOT IN 제한 조건 및 NULL 값

질문의 텍스트에서 문제는 SELECTSQL DDL 대신 SQL DML 쿼리 에서 발생한 것으로 보입니다 CONSTRAINT.

그러나 특히 제목의 문구를 감안할 때, 여기에 작성된 일부 진술은 잠재적으로 잘못된 진술, 즉 (낙원)

술어가 UNKNOWN으로 평가되면 행이 없습니다.

이는 SQL DML의 경우이지만 제약 조건을 고려할 때 효과가 다릅니다.

질문의 술어에서 직접 가져온 두 가지 제약 조건이있는이 매우 간단한 표를 고려하십시오 (@Brannon의 탁월한 답변으로 해결 됨).

DECLARE @T TABLE 
(
 true CHAR(4) DEFAULT 'true' NOT NULL, 
 CHECK ( 3 IN (1, 2, 3, NULL )), 
 CHECK ( 3 NOT IN (1, 2, NULL ))
);

INSERT INTO @T VALUES ('true');

SELECT COUNT(*) AS tally FROM @T;

@Brannon의 답변에 따라 첫 번째 제약 조건 (을 사용하여 IN)은 TRUE로 평가되고 두 ​​번째 제약 조건 (을 사용하여 NOT IN)은 UNKNOWN으로 평가됩니다. 그러나 삽입이 성공합니다! 따라서이 경우 실제로 행을 삽입 했으므로 "행을 얻지 못했습니다"라고 말하는 것이 정확하지 않습니다.

위의 효과는 SQL-92 표준과 관련하여 실제로 올바른 효과입니다. SQL-92 스펙에서 다음 섹션을 비교하고 대조하십시오

7.6 where 절

의 결과는 검색 조건의 결과가 참인 T의 행에 대한 테이블입니다.

무결성 제약

지정된 검색 조건이 테이블의 행에 대해 거짓이 아닌 경우에만 테이블 점검 제한 조건이 충족됩니다.

다시 말해:

SQL DML 에서 조건이 "참" 이 아니기WHERE 때문에 UNKNOWN으로 평가 하면 행이 결과에서 제거됩니다 .

이 때문에 그들이 UNKNOWN으로 평가하는 경우 SQL DDL (예 : 제약)에서 행이 결과에서 제거되지 않는 조건을 만족 "거짓 아니다".

SQL DML과 SQL DDL의 효과는 각각 모순되는 것처럼 보일 수 있지만 UNKNOWN 결과에 제약 조건을 만족시킬 수있게하여 (의미를 더 정확하게 제공함으로써 제약 조건을 충족시키지 못하도록하는) 실질적인 이유가 있습니다. :이 동작이 없으면 모든 제약 조건이 null을 명시 적으로 처리해야하며 언어 설계 관점 (코더에게는 올바른 고통은 말할 것도 없습니다!)에서 매우 불만족 스럽습니다.

추신 : "알 수없는 제약 조건을 충족시키지 못합니다"와 같은 논리를 따르는 것이 어렵다고 생각되면 SQL DDL의 nullable 열과 SQL의 모든 것을 피 함으로써이 모든 것을 피할 수 있다고 생각하십시오. 널을 생성하는 DML (예 : 외부 조인)!


솔직히이 주제에 대해 할 말이 없다고 생각했습니다. 흥미 롭군
Jamie Ide

2
@Jamie Ide : 사실, 주제에 대한 또 다른 대답이 NOT IN (subquery)있습니다. 널을 포함하면 예기치 않은 결과가 발생할 수 있기 때문에 항상 null을 올바르게 처리하는 것처럼 보이기 때문에 IN (subquery)완전히 사용 NOT EXISTS (subquery)하지 않는 것이 좋습니다. 그러나 NOT IN (subquery)예상 결과를 제공하는 반면 NOT EXISTS (subquery)예상치 못한 결과를 제공하는 경우가 있습니다 ! 주제에 대한 내 노트를 찾을 수 있다면이 글을 쓸 수 있습니다. (직관적이지 않기 때문에 노트가 필요합니다!) 결론은 동일하지만 null을 피하십시오!
11

@oneday 내가 당신의 주장에 혼란 스러울 때 NULL은 일관된 동작 (사양과 일치하지 않고 내부적으로 일관성이 있음)을 갖기 위해 특별한 경우가 필요하다고 주장합니다. "지정된 검색 조건이 참인 경우에만 테이블 점검 제한 조건이 충족 됨"을 읽도록 4.10을 변경하는 것으로 충분하지 않습니까?
DylanYoung

@DylanYoung : 아니, 사양이 중요한 이유에 대해 그런 식으로 모호하게되어 세 값 논리에서 SQL을 겪고있다, 그 값은 TRUE, FALSE하고 UNKNOWN. 4.10에서 "지정된 검색 조건이 테이블의 모든 행에 대해 TRUE 또는 UNKNOWN 인 경우에만 테이블 검사 제한 조건이 충족 됨"을 읽을 수 있다고 가정합니다. -에서 "에 대한" "모두에 '. 나는 논리 값을 투자 할 필요가 있다고 생각하기 때문에의 의미'사실 '과' '확실히 고전적인 두 개의 값 논리를 참조해야 자연 언어. 거짓
onedaywhen

1
다음을 고려하십시오 CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );.-여기서 의도는 ba거나 널 이어야합니다 . 제약 조건이 TRUE를 충족시켜야하는 경우 명시 적으로 null을 처리하도록 제약 조건을 변경해야합니다 (예 :) CHECK( a = b OR b IS NULL ). 따라서 모든 제약 조건에는 ...OR IS NULL관련된 nullable 열에 대해 사용자 가 논리를 추가 해야합니다 . 복잡성, 잊어 버린 버그 등. 따라서 SQL 표준위원회는 실용적이라고 생각합니다.
1

7

A에서 3은 세트의 각 멤버에 대해 동등성을 테스트하여 (FALSE, FALSE, TRUE, UNKNOWN)을 산출합니다. 요소 중 하나가 TRUE이므로 조건이 TRUE입니다. (일부 단락이 발생할 수도 있으므로 첫 번째 TRUE에 도달하자마자 실제로 중지되고 3 = NULL로 평가되지 않습니다.)

B에서는 조건이 NOT (3 in (1,2, null))으로 평가되고 있다고 생각합니다. 3 세트 수율 (FALSE, FALSE, UNKNOWN)에 대한 동등성을 테스트하여 UNKNOWN으로 집계합니다. NOT (UNKNOWN)은 UNKNOWN을 산출합니다. 따라서 전반적인 상태의 진실은 알려져 있지 않으며, 결국은 본질적으로 거짓으로 취급됩니다.


7

NOT IN (subquery)null을 올바르게 처리하지 않으므로 여기에 대한 답변에서 결론을 내릴 수 있습니다 NOT EXISTS. 그러나 그러한 결론은 시기상조 일 수 있습니다. Chris Date (데이터베이스 프로그래밍 및 디자인, Vol 2 No 9, 1989 년 9 월)로 인정되는 다음 시나리오 NOT IN에서는 null을 올바르게 처리하고 대신 올바른 결과를 반환합니다 NOT EXISTS.

부품 ( )을 수량 ( ) 으로 공급하는 것으로 알려진 sp공급 업체 ( sno) 를 나타내는 표 를 고려하십시오 . 이 테이블에는 현재 다음 값이 있습니다.pnoqty

      VALUES ('S1', 'P1', NULL), 
             ('S2', 'P1', 200),
             ('S3', 'P1', 1000)

수량은 무효화됩니다. 즉, 공급 업체가 수량을 모르더라도 부품을 공급한다고 알려진 사실을 기록 할 수 있습니다.

이 작업은 알려진 공급 부품 번호 'P1'이지만 1000 수량이 아닌 공급 업체를 찾는 것입니다.

다음은 NOT IN공급 업체 'S2'만 올바르게 식별하는 데 사용 됩니다.

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND 1000 NOT IN (
                        SELECT spy.qty
                          FROM sp spy
                         WHERE spy.sno = spx.sno
                               AND spy.pno = 'P1'
                       );

그러나 아래 쿼리는 동일한 일반 구조를 사용하지만 NOT EXISTS결과에 공급자 'S1'을 포함하지만 잘못 포함합니다 (예 : 수량이 null 인 경우).

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND NOT EXISTS (
                       SELECT *
                         FROM sp spy
                        WHERE spy.sno = spx.sno
                              AND spy.pno = 'P1'
                              AND spy.qty = 1000
                      );

그래서 NOT EXISTS그것이 나타날 수도있는 은색 총알이 아닙니다!

물론 문제의 원인은 널이 존재하므로 '실제'해결책은 이러한 널을 제거하는 것입니다.

이것은 두 가지 테이블을 사용하여 (다른 가능한 디자인 중에서도) 달성 할 수 있습니다.

  • sp 부품 공급으로 알려진 공급 업체
  • spq 알려진 수량으로 부품을 공급하는 것으로 알려진 공급 업체

spq참조 할 경우 외래 키 제약 조건이 있어야합니다 sp.

그런 다음 '빼기'관계 연산자 ( EXCEPT표준 SQL 에서 키워드)를 사용하여 결과를 얻을 수 있습니다.

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1' ), 
                       ( 'S2', 'P1' ),
                       ( 'S3', 'P1' ) )
              AS T ( sno, pno )
     ),
     spq AS 
     ( SELECT * 
         FROM ( VALUES ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT sno
  FROM spq
 WHERE pno = 'P1'
EXCEPT 
SELECT sno
  FROM spq
 WHERE pno = 'P1'
       AND qty = 1000;

1
세상에 실제로 이것을 작성해 주셔서 감사합니다 .... 이것은 나를 미치게했다 ..
Govind Rai

6

Null은 데이터의 의미와 부재, 즉 데이터 값이 아닌 알 수 없음을 나타냅니다. 포인터를 사용할 때 C 유형 언어에서는 null이 실제로 아무것도 아니기 때문에 프로그래밍 배경을 가진 사람들이 이것을 혼동하기가 매우 쉽습니다.

따라서 첫 번째 경우 3은 실제로 (1,2,3, null) 세트에 있으므로 true가 리턴됩니다.

그러나 두 번째로 당신은 그것을 줄일 수 있습니다

3이 아닌 ( 'null') 'true'를 선택하십시오.

파서가 비교할 세트에 대해 아무것도 모르기 때문에 아무것도 반환되지 않습니다. 빈 세트가 아니라 알 수없는 세트입니다. (1,2) 집합이 분명히 거짓이기 때문에 (1, 2, null)을 사용하는 것이 도움이되지 않지만 알 수없는 알 수없는 것에 대해 당신이하고 있습니다.


6

하위 쿼리 포함 NULL에 대해 NOT IN으로 필터링하려면 null이 아닌지 확인하십시오.

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )

특수 상황에서 레코드를 반환하지 않는 외부 조인 쿼리에 문제가 있었으므로 Null 및 기존 레코드 시나리오 모두에 대해이 솔루션을 확인하고 저에게 효과적이었습니다. 다른 문제가 발생하면 여기에 언급 할 것입니다. 감사합니다.
QMaster

1

이것은 소년을위한 것입니다 :

select party_code 
from abc as a
where party_code not in (select party_code 
                         from xyz 
                         where party_code = a.party_code);

이것은 ansi 설정에 관계없이 작동합니다


원래 질문 : B : (1, 2, null이 아닌 3)에서 'true'를 선택하십시오. null을 제거하는 방법을 수행해야합니다. 예를 들어 3이 (1, 2, isnull (null, 0)에없는 경우 'true'를 선택하십시오 ) 전반적인 논리는 NULL이 원인이면 쿼리의 일부 단계에서 NULL 값을 제거하는 방법을 찾는 것입니다.

종종 경우가 party_code하지 (party_code가 null가 아닌 XYZ에서 선택 party_code)하지만 당신은 필드를 잊어 버린 경우 좋은 운이 널 (null)을 허용하는 등 ABC에서 party_code를 선택

1

SQL은 진리 값에 3 값 논리를 사용합니다. IN쿼리는 예상되는 결과를 생성합니다 :

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

그러나 a를 추가 NOT해도 결과가 반전되지 않습니다.

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

위의 쿼리는 다음과 같습니다.

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

where 절을 평가하는 방법은 다음과 같습니다.

| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN (2)           | UNKNOWN (3)                 |

그것을주의해라:

  1. NULL수율 과 관련된 비교UNKNOWN
  2. OR피연산자 하나도없는 표현 TRUE및 상기 적어도 하나의 피연산자는 UNKNOWN수율 UNKNOWN( REF )
  3. NOTUNKNOWN수율 UNKNOWN( REF )

위의 예를 두 개 이상의 값 (예 : NULL, 1 및 2)으로 확장 할 수 있지만 결과는 동일합니다. 값 중 하나 NULL가 일치하면 행이 일치하지 않습니다.


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