이 문제는 하나의 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가 결과를 반환 한다는 것을 알았습니다 .
답변
쿼리 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 <> null
UNKNOWN는 술어 평가됩니다 그래서 UNKNOWN에, 당신은 어떤 행을하지 않습니다.
때 ansi_nulls
꺼져, 3 <> null
술어 평가하여 이렇게 참으로, 사실, 당신은 행을 얻을.
답변
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 입니다.
답변
NOT IN
알 수없는 값과 비교할 때 0 개의 레코드를 반환합니다.
이후 NULL
알 수없는이하는 NOT IN
포함하는 쿼리 NULL
또는 NULL
가능한 값의 목록에들 항상 반환됩니다 0
확인하는 것이 할 수있는 방법이 없기 때문에 기록을 NULL
값이 테스트중인 값이 없습니다.
답변
IS NULL을 사용하지 않으면 널과 비교는 정의되지 않습니다.
따라서 3을 NULL과 비교할 때 (질의 A) 정의되지 않은 값을 반환합니다.
즉 (1,2, null)에서 3이면 SELECT ‘true’, (1,2, null)에서는 3이면 SELECT ‘true’
NOT (UNDEFINED)이 아직 정의되지 않았지만 TRUE가 아닌 동일한 결과를 생성합니다.
답변
글을 쓰는 시점에서이 질문의 제목은
SQL NOT IN 제한 조건 및 NULL 값
질문의 텍스트에서 문제는 SELECT
SQL 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 (예 : 외부 조인)!
답변
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을 산출합니다. 따라서 전반적인 상태의 진실은 알려져 있지 않으며, 결국은 본질적으로 거짓으로 취급됩니다.
답변
NOT IN (subquery)
null을 올바르게 처리하지 않으므로 여기에 대한 답변에서 결론을 내릴 수 있습니다 NOT EXISTS
. 그러나 그러한 결론은 시기상조 일 수 있습니다. Chris Date (데이터베이스 프로그래밍 및 디자인, Vol 2 No 9, 1989 년 9 월)로 인정되는 다음 시나리오 NOT IN
에서는 null을 올바르게 처리하고 대신 올바른 결과를 반환합니다 NOT EXISTS
.
부품 ( )을 수량 ( ) 으로 공급하는 것으로 알려진 sp
공급 업체 ( sno
) 를 나타내는 표 를 고려하십시오 . 이 테이블에는 현재 다음 값이 있습니다.pno
qty
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;