NULL은 값이 없다는 표시입니다. 모델에서는 속성마다 NULL을 허용할지 정하고, SQL에서 NULL은 연산·비교·집계마다 정해진 방식으로 움직입니다.
정하늘은 아직 부서가 정해지지 않았고, 김민준은 맨 위라 관리자가 없습니다. 이런 칸에 0을 넣으면 '0번 부서'라는 틀린 사실이 되므로, 값이 없다는 사실을 NULL로 남깁니다. NULL은 다른 값과 다르게 움직여서, 규칙을 모르면 조건에서 행이 빠지거나 집계 함수(여러 행을 값 하나로 줄이는 함수 — GROUP BY와 집계 함수 레슨에서 자세히)의 평균이 달라집니다. 성과급 표 BONUS는 이 레슨에만 쓰는 테이블로, 이서연과 정하늘의 성과급 AMT가 NULL입니다.
1NULL의 뜻과 모델의 NULL 허용
NULL은 잘못 들어간 값이 아니라 '아직 모름'이나 '해당 없음'을 적어 둔 것입니다.
숫자 0과 공백 한 칸 ' '은 값이 있으므로 NULL이 아닙니다. 다만 Oracle은 길이가 0인 빈 문자열 ''을 NULL로 다루고, SQL Server는 ''와 NULL을 구분합니다.
모델에서는 속성마다 NULL을 허용할지 정합니다.
| 속성 | NULL 허용 | 까닭 |
|---|---|---|
| EMPNO | 안 됨 | 주식별자(존재성) |
| ENAME · SAL | 안 됨 | 필수 속성 |
| DEPTNO | 됨 | 부서와 선택 관계의 FK |
| MGR | 됨 | 관리자와 선택 관계의 FK |
관계가 필수라면 FK도 NOT NULL입니다. NOT NULL인 칸에 NULL을 넣으려 할 때만 막힙니다.
2NULL의 연산과 비교
모르는 값에 무엇을 더해도 여전히 모르므로, NULL이 섞인 산술 연산(+ − × ÷)은 결과도 NULL입니다. NULL과 비교하면 참도 거짓도 아닌 '알 수 없음(UNKNOWN)'이 됩니다. WHERE 절은 조건이 참인 행만 남기므로 알 수 없음인 행도 빠집니다.
| 식 | 결과 | WHERE에서 |
|---|---|---|
300 + NULL | NULL | — |
NULL <> 20 | 알 수 없음 | 빠짐 |
NULL = NULL | 알 수 없음 | 빠짐 |
NULL IS NULL | 참 | 남음 |
= NULL은 오류 없이 0행을 내서 틀린 줄 모르고 지나치기 쉽습니다. NULL인지는 IS NULL·IS NOT NULL로 확인합니다.
NOT IN(목록의 어느 값과도 같지 않으면 참 — WHERE 절 레슨에서 자세히)은 목록에 NULL이 있으면 'NULL과 같지 않다'가 늘 알 수 없음이라 어느 행도 남기지 않습니다.
3집계 함수와 NULL
집계 함수인 COUNT(개수)·SUM(합계)·AVG(평균)는 NULL인 칸을 건너뛰고 계산합니다. COUNT(*)만 칸이 아니라 행을 셉니다.
| 함수 | NULL인 칸을 |
|---|---|
COUNT(*) | 셈(행을 셈) |
COUNT(열) · SUM(열) | 건너뜀 |
AVG(열) | 건너뛰고 남은 행 수로 나눔 |
그래서 성과급 평균은 6명이 아니라 4명으로 나눕니다. 성과급이 없는 사원까지 넣으려면 분모를 COUNT(*)로 씁니다.
예제
부서가 20이 아닌 사원을 찾으면 정하늘은 결과에 없습니다.
SELECT EMPNO,
ENAME,
DEPTNO
FROM EMP
WHERE DEPTNO <> 20
ORDER BY EMPNO;
| EMPNO | ENAME | DEPTNO |
|---|---|---|
| 1001 | 김민준 | 10 |
| 1004 | 최유나 | 30 |
= NULL로는 0행, IS NULL로는 정하늘 한 행이 나옵니다.
SELECT EMPNO,
ENAME,
DEPTNO
FROM EMP
WHERE DEPTNO = NULL;
SELECT EMPNO,
ENAME,
DEPTNO
FROM EMP
WHERE DEPTNO IS NULL;
| EMPNO | ENAME | DEPTNO |
|---|---|---|
| 1005 | 정하늘 | NULL |
목록에 NULL이 든 NOT IN은 한 행도 남기지 않습니다.
SELECT COUNT(*) AS CNT
FROM EMP
WHERE DEPTNO NOT IN
(10, NULL);
| CNT |
|---|
| 0 |
성과급 표 BONUS를 만들어 월급과 더하면, 성과급이 NULL인 두 사원은 합계 TOTAL도 NULL입니다.
CREATE TABLE BONUS (
EMPNO NUMBER PRIMARY KEY
REFERENCES EMP (EMPNO),
AMT NUMBER
);
INSERT INTO BONUS
VALUES (1001, 60);
INSERT INTO BONUS
VALUES (1002, NULL);
INSERT INTO BONUS
VALUES (1003, 30);
INSERT INTO BONUS
VALUES (1004, 40);
INSERT INTO BONUS
VALUES (1005, NULL);
INSERT INTO BONUS
VALUES (1006, 50);
SELECT E.ENAME,
E.SAL,
B.AMT,
E.SAL + B.AMT AS TOTAL
FROM EMP E
INNER JOIN BONUS B
ON B.EMPNO = E.EMPNO
ORDER BY E.EMPNO;
| ENAME | SAL | AMT | TOTAL |
|---|---|---|---|
| 김민준 | 520 | 60 | 580 |
| 이서연 | 380 | NULL | NULL |
| 박지호 | 410 | 30 | 440 |
| 최유나 | 450 | 40 | 490 |
| 정하늘 | 300 | NULL | NULL |
| 강도윤 | 470 | 50 | 520 |
TOTAL을 모두 더하는 SUM(E.SAL + B.AMT)는 NULL인 두 행이 통째로 빠져 2030입니다.
열마다 따로 더하는 SUM(E.SAL) + SUM(B.AMT)는 NULL 칸만 빼므로 2530 + 180 = 2710입니다.
같은 표로 집계합니다. AVG(AMT)와 SUM(AMT) / COUNT(*)를 견줘 보세요.
CREATE TABLE BONUS (
EMPNO NUMBER PRIMARY KEY
REFERENCES EMP (EMPNO),
AMT NUMBER
);
INSERT INTO BONUS
VALUES (1001, 60);
INSERT INTO BONUS
VALUES (1002, NULL);
INSERT INTO BONUS
VALUES (1003, 30);
INSERT INTO BONUS
VALUES (1004, 40);
INSERT INTO BONUS
VALUES (1005, NULL);
INSERT INTO BONUS
VALUES (1006, 50);
SELECT COUNT(*),
COUNT(AMT),
SUM(AMT),
AVG(AMT),
SUM(AMT) / COUNT(*)
AS PER_ROW
FROM BONUS;
| COUNT(*) | COUNT(AMT) | SUM(AMT) | AVG(AMT) | PER_ROW |
|---|---|---|---|---|
| 6 | 4 | 180 | 45 | 30 |
시험 포인트
- NULL은 0이나 공백 한 칸과 다릅니다. Oracle은 빈 문자열
''을 NULL로 다룹니다. - 'FK는 NULL이 될 수 없다'는 보기는 틀립니다. 선택 관계의 FK는 NULL을 허용합니다.
- NULL이 섞인 산술은 NULL, 비교는 알 수 없음이라 WHERE에서 빠집니다.
= NULL은 오류 없이 0행이고, NULL은IS NULL로 찾습니다. NOT IN목록에 NULL이 있으면 0행입니다.COUNT(*)말고는 집계 함수가 NULL을 건너뜁니다.AVG(열)과SUM(열) / COUNT(*)는 다를 수 있습니다.- NULL이 있으면
SUM(열1 + 열2)와SUM(열1) + SUM(열2)가 다를 수 있습니다.
확인 문제
1. 다음 쿼리의 결과 두 값은 무엇일까요?
SELECT COUNT(*) AS CNT,
SUM(SAL) AS SUM_SAL
FROM EMP
WHERE MGR <> 1001;
정답
3과 1090입니다.
| CNT | SUM_SAL |
|---|---|
| 3 | 1090 |
최유나·강도윤은 MGR이 1001이라 거짓, 김민준은 MGR이 NULL이라 알 수 없음이 되어 세 사람 모두 빠집니다. 정하늘은 부서가 NULL이어도 MGR이 1004라 남아, 이서연·박지호와 함께 380 + 410 + 300 = 1090입니다. 김민준을 넣어 4와 1610으로 셈하면 틀립니다.
2. 도서 대출 엔터티의 속성 대출번호(주식별자) · 회원번호(빌린 회원) · 대출일 · 반납일 가운데 NULL을 허용할 속성은 무엇이고, 까닭은 무엇일까요?
정답
반납일입니다. 아직 돌려받지 않은 대출은 반납일이 없으므로 비워 둡니다. 아무 날짜나 넣으면 이미 반납했다는 틀린 사실이 됩니다. 나머지는 주식별자, 대출마다 꼭 있는 회원(필수 관계의 FK), 빌릴 때 늘 생기는 날짜라 NOT NULL입니다.
3. EMP의 ENAME은 NOT NULL입니다. Oracle에서 다음 INSERT는 들어갈까요? 까닭도 말해 보세요.
INSERT INTO EMP
(EMPNO, ENAME, SAL)
VALUES (1007, '', 400);
정답
들어가지 않습니다.
Oracle은 빈 문자열 ''을 NULL로 다루므로, NOT NULL인 ENAME에 NULL을 넣으려 한 것이 되어 막힙니다(ORA-01400).