요약

표의 빈칸은 0이나 빈 글자가 아니라 “값이 없다”는 표시다. 아직 모르는 값일 수도 있고, 그 행에는 애초에 쓸 수 없는 값일 수도 있다. 무엇인지 모르는 값이라 다른 값과 같은지 다른지도 알 수 없어서, 빈칸끼리 견줘도 참이 되지 않는다. 그래서 빈칸인지 묻는 말이 따로 있고, 빈칸이 낀 행은 표를 잇거나 값을 셀 때 조용히 빠진다.

예시로 보기

COMPANY 상태(관계형 데이터베이스 스키마와 상태)에는 빈칸이 두 곳 있다. Borg의 Super_ssn은 사장이라 상사가 없어서 비어 있고, WORKS_ON의 (888665555, 20) 행은 일한 시간 Hours가 적혀 있지 않다.

“상사가 없는 직원”을 찾으려고 =로 견주면 아무도 안 나온다.

조건 결과
WHERE Super_ssn = NULL 0행
WHERE Super_ssn <> NULL 0행
WHERE Super_ssn IS NULL Borg
WHERE Super_ssn IS NOT NULL 나머지 7명

= NULL도 <> NULL도 0행이다. 같다고도 다르다고도 말할 수 없기 때문이다. 빈칸인지는 IS NULL로 묻는다.

SELECT Fname, Lname
FROM   EMPLOYEE
WHERE  Super_ssn IS NULL;
검증: 위 표, 세 값 논리의 표, Hours > 10의 8행과 Hours <= 10의 7행, 상사 조인 7행, COUNT의 8·8·6·7, WORKS_ON의 16·15·275·18.33, GROUP BY의 NULL 묶음, NOT IN의 0행과 5명, 카드 C3. SQLite 3.50.4와 PostgreSQL 16에서 같은 값이 나온다 — 63_sql-null_verify.py, 63_sql-null_verify.sql

정의

NULL은 어떤 행에서 그 열의 값을 모르거나, 그 행에는 그 열이 맞지 않을 때 값 자리에 두는 표시다. NULL의 뜻은 셋으로 나눈다. 아래 표의 예는 COMPANY와 학생 표의 빈칸에 세 뜻을 맞춰 본 것이다.

뜻 풀이 예
모르는 값 (unknown) 값은 있는데 무엇인지 모른다 Borg가 프로젝트 20에서 일한 시간
얻지 못한 값 (unavailable) 값은 있는데 알려 주지 않았거나 구하지 못했다 집 전화번호를 적지 않은 학생
정해지지 않는 값 (undefined) 그 행에는 이 열이 맞지 않는다 사장 Borg의 상사, 사무실이 없는 학생의 사무실 전화번호

IS NULL. 열의 값이 NULL이면 참이다. IS NOT NULL은 그 반대다.

NULL을 다루는 규칙은 셋이다.

  • NULL은 저마다 다른 NULL과 같지 않다고 본다. 그래서 =나 <>로 NULL과 견줄 수 없다.
  • 표를 이을 때 맞추는 열이 NULL인 행은 결과에서 빠진다. 직원과 상사를 E.Super_ssn = S.Ssn으로 이으면 8행이 아니라 7행이 나온다. Borg가 빠진다. 빠진 행까지 남기는 방법이 외부 조인이다.
  • 값을 세는 계산은 NULL을 세지 않는다. COUNT(Salary)처럼 열을 세면 NULL은 어떻게 될까? 돌려 보면 이렇다1.
식 EMPLOYEE (8행) 뜻
COUNT(*) 8 행의 수
COUNT(Salary) 8 급여가 적힌 칸의 수. 같은 값도 따로 센다
COUNT(DISTINCT Salary) 6 서로 다른 급여의 수
COUNT(Super_ssn) 7 상사가 적힌 칸의 수. Borg의 빈칸은 세지 않는다

평균도 같다. WORKS_ON은 16행인데 Hours가 적힌 칸은 15개, 합은 275다. AVG(Hours)는 275 ÷ 15 = 18.33이고, 275 ÷ 16 = 17.19가 아니다(집계 함수).

참, 거짓, 모름

NULL과 견준 결과는 참도 거짓도 아닌 셋째 값 “모름”이다. WHERE는 조건이 참인 행만 남기므로 모름인 행은 거짓인 행과 함께 버려진다2.

식 값
NULL = NULL, NULL <> NULL, 1 = NULL 모름
NULL IS NULL 참
모름 AND 거짓 거짓
모름 AND 참 모름
모름 OR 참 참
모름 OR 거짓 모름
NOT 모름 모름

“모름 AND 거짓”이 거짓인 까닭은 모르는 쪽이 참이든 거짓이든 전체가 거짓이기 때문이다. “모름 OR 참”도 같은 이유로 참이다.

이 때문에 조건과 그 반대 조건이 표 전체를 덮지 못한다. WORKS_ON에서 Hours > 10은 8행, Hours <= 10은 7행이고 합쳐도 15행이다. Hours가 NULL인 한 행은 어느 쪽에도 들지 않는다. NOT (Hours > 10)도 7행이다.

활용

흔한 실수와 알아 둘 동작이다2.

  • NOT IN과 NULL. “부하가 한 명도 없는 직원”을 Ssn NOT IN ( SELECT Super_ssn FROM EMPLOYEE )로 쓰면 0행이 나온다. 안쪽 결과에 Borg의 NULL이 섞여 있어서, 어느 직원이든 “그 모르는 값과 다르다”고 말할 수 없기 때문이다. 안쪽에 WHERE Super_ssn IS NOT NULL을 더하면 Smith, Zelaya, Narayan, English, Jabbar 5명이 나온다. NOT EXISTS로 쓰면 이 문제가 없다(EXISTS와 NOT EXISTS).
  • 계산. NULL이 든 계산의 결과는 NULL이다. Hours + 1은 그 행에서 NULL이다.
  • 묶을 때는 한데 모인다. GROUP BY Super_ssn과 SELECT DISTINCT Super_ssn은 NULL끼리 한 묶음으로 본다. 묶음이 넷(상사 셋과 NULL)이다. 견줄 때는 서로 같지 않다고 보면서 묶을 때는 함께 두는 것이라 헷갈리기 쉽다(GROUP BY).
  • 줄 세울 때의 자리는 제품마다 다르다(ORDER BY와 질의의 절 순서).
  • 분석에 쓰기 전에 빈칸을 채우거나 버리는 방법: 데이터 정제

연결

확인 문제

C1 NULL의 세 가지 뜻을 쓰고, 값이 NULL인지 묻는 조건을 쓰라.

답: 모르는 값, 얻지 못한 값, 정해지지 않는 값(그 행에는 맞지 않는 열). 열 IS NULL로 묻고, 반대는 열 IS NOT NULL이다.

C2 WHERE Super_ssn = NULL이 Borg를 고르지 못하는 까닭을 설명하라. WHERE Super_ssn <> NULL은 몇 행을 고르는가?

답: NULL은 모르는 값이라 어떤 값과 같은지 알 수 없다. 비교의 결과가 참이 아니라 “모름”이 되고, WHERE는 참인 행만 남긴다. <> NULL도 모든 행에서 모름이라 0행이다.
흔한 오답: “= NULL이 0행이니 <> NULL은 8행”이라고 답하는 것. 두 조건이 서로 반대여도 모름인 행은 양쪽에서 다 빠진다.

C3 WORKS_ON에서 프로젝트 20의 행은 (333445555, 20, 10.0), (987654321, 20, 15.0), (888665555, 20, NULL)이다. 이 세 행에 대한 COUNT(*), COUNT(Hours), SUM(Hours), AVG(Hours)를 구하라.

답: 3, 2, 25.0, 12.5.
이유: COUNT(*)는 행을 세어 3이다. 나머지는 NULL을 빼고 계산한다. 값이 둘(10.0, 15.0)이라 합은 25.0, 평균은 25 ÷ 2 = 12.5다. 25 ÷ 3이 아니다.

  1. 표의 값과 WORKS_ON의 평균은 SQLite와 PostgreSQL 16에서 실행해 얻었다. ↩

  2. 세 값 논리는 Elmasri & Navathe, Fundamentals of Database Systems 7판 7.1절 “Comparisons Involving NULL and Three-Valued Logic”에 있다. 값은 모두 SQLite와 PostgreSQL 16에서 실행해 확인했다. ↩ ↩2