NULL이 0·공백·빈 문자열과 어떻게 다른지, 3값 논리와 비교·계산·집계·정렬·NOT IN·제약조건에서의 동작을 익히고 단원별 NULL 규칙을 한곳에 모읍니다.
최빈출기출 11회분 중 111문항 · 많이 나온 주제: NULL(101), NVL/NVL2(22), COALESCE(13)
학습 목표
NULL이 0·공백·빈 문자열과 어떻게 다른지, 3값 논리와 비교·계산·집계·정렬·NOT IN·제약조건에서의 동작을 익히고 단원별 NULL 규칙을 한곳에 모읍니다.
01
NULL 한눈에 보기
시험 점수로 생각해 봅시다. 시험을 보고 0점을 받은 학생은 점수 0이라는 값이 있습니다. 시험을 보지 않은(결시) 학생은 점수 자체가 없으므로 NULL입니다. 둘을 같은 0으로 처리하면 평균도, 결시 인원도 틀어집니다.
10숫자 0값 있음 · 계산·비교 가능
2' '공백 한 칸값 있음 · 문자로 비교 가능
3NULL값 자체가 없음IS NULL로만 확인
0 · 공백 · 빈 문자열 · NULL 비교 (Oracle)
표현
뜻
IS NULL
`=` 비교
산술
0
숫자 0이라는 값
FALSE
가능
가능
' '
공백 문자 한 칸(길이 1)
FALSE
가능
—
''
길이 0인 문자열
TRUE
Oracle에서 NULL 취급
—
NULL
값 없음·모름·해당 없음
TRUE
불가 (UNKNOWN)
결과 NULL
그래서 Oracle에서 PHONE = ''로는 찾을 수 없고 PHONE IS NULL로 찾습니다. 공백 한 칸 ' '은 IS NULL에 걸리지 않습니다. 표시용으로 NULL을 0이나 '없음'으로 바꿀 수는 있지만, 데이터의 의미까지 바뀌는 것은 아닙니다.
02
NULL의 규칙과 3값 논리
NULL이 들어가면 이렇게 됩니다 (5가지 규칙)
규칙
예
결과
① 산술 연산
80 + NULL
NULL (단, Oracle 문자 연결 'SQL' || NULL은 'SQL')
② 비교 연산
SCORE = NULL, NULL = NULL
UNKNOWN (TRUE가 아님)
③ NULL 확인
SCORE IS NULL / IS NOT NULL
이것만 TRUE·FALSE로 판정
④ 집계 함수
SUM·AVG·MAX·MIN·COUNT(열)
NULL은 빼고 계산, COUNT(*)만 NULL 행도 셈
⑤ NOT IN
7 NOT IN (1, 2, NULL)
UNKNOWN → 목록에 NULL이 있으면 0건 (NOT EXISTS는 영향 없음)
산술과 비교는 결과의 종류가 다릅니다. NULL + 1, 1 * NULL은 NULL이라는 값을 돌려주지만, 1 > NULL, NULL = NULL은 값이 아니라 UNKNOWN이라는 판정이 되어 WHERE에서 행을 걸러 냅니다.
3값 논리 판정
식
결과
이유
NULL = NULL
UNKNOWN
값이 없어 같다고 판단할 수 없음
100 <> NULL
UNKNOWN
다르다고도 확정할 수 없음
NOT (100 = NULL)
UNKNOWN
NOT UNKNOWN도 UNKNOWN
TRUE OR UNKNOWN
TRUE
한쪽이 이미 참
FALSE AND UNKNOWN
FALSE
한쪽이 이미 거짓
WHERE에서 NULL 비교가 사라지는 이유
WHERE SCORE = NULL
조건 평가→
UNKNOWN→
TRUE가 아님→
행 제외 → 0건
03
NULL 속성과 SQL 결과
NULL은 모델의 선택 속성(값이 없어도 되는 속성)과 선택 관계(짝이 없어도 되는 관계)에서 생깁니다. 반대로 반드시 값이 있어야 하는 속성은 테이블에서 NOT NULL이나 PRIMARY KEY로 막습니다.
선택 관계는 SQL의 OUTER JOIN에서 다시 드러납니다. 짝이 없는 쪽의 열은 NULL로 채워지고, 이 NULL을 IS NULL로 골라 '짝이 없는 행'만 찾을 수 있습니다.
선택 관계가 SQL 결과에 나타나는 방식주문이 없는 고객도 남기는 LEFT OUTER JOIN
NULL
고객
C01
김민지
C02
이서준
C03
박하윤
주문
O101
C01
O102
C02
LEFT JOIN 결과
고객
주문
C01
O101
C02
O102
C03
NULL
C03은 매칭되는 주문이 없어도 결과에 남고, 주문 쪽 컬럼이 NULL로 채워집니다.
제약조건과 NULL 입력
제약조건
NULL 입력
이유
PRIMARY KEY
불가
유일 + NOT NULL을 함께 보장
NOT NULL
불가
값을 반드시 넣어야 함
UNIQUE
가능
중복 금지는 NULL이 아닌 값에만 적용
FOREIGN KEY
가능
NULL이면 참조 검사를 하지 않음
CHECK (C > 0)
가능
NULL이면 UNKNOWN → 위반으로 보지 않음
CHECK (C IS NOT NULL)
불가
IS NOT NULL이 FALSE
UNIQUE만 걸린 열에는 NULL을 넣을 수 있으므로, NULL까지 막으려면 NOT NULL을 따로 지정합니다. CHECK는 조건이 FALSE일 때만 거부하므로 CHECK (C > 0)인 열에 NULL은 들어갑니다.
04
NULL 처리 함수
NULL을 다른 값으로 바꾸는 함수
함수
규칙
예 (STUDENT_SCORE)
NVL(A, B)
A가 NULL이면 B
NVL(BONUS, 0) → S2·S3은 0
NVL2(A, B, C)
A가 NULL 아니면 B, NULL이면 C
NVL2(BONUS, '있음', '없음') → S4(0)는 있음
NULLIF(A, B)
A = B이면 NULL, 아니면 A
NULLIF(SCORE, 0) → 0점을 NULL로
COALESCE(A, B, ...)
왼쪽부터 처음 나오는 NULL 아닌 값
COALESCE(SCORE, BONUS, -1)
CASE ... END
맞는 WHEN이 없고 ELSE도 없으면 NULL
CASE 'A' WHEN 'B' THEN 1 END → NULL
NULL을 돌려주는 경우도 함께 기억합니다. NULLIF('A', 'A')처럼 두 값이 같을 때, CASE에서 ELSE를 생략하고 맞는 WHEN이 없을 때, DECODE에서 기본값을 생략하고 일치하는 값이 없을 때 결과는 NULL입니다. 반대로 COALESCE('A', 'B', NULL)은 첫 값 'A'를 돌려줍니다.
함수로 바꾸기 전에 그 NULL이 무슨 뜻인지부터 정합니다. '아직 결제 안 함'과 '0원 결제'가 다른 업무라면 NVL(결제액, 0)으로 둘을 섞으면 집계 의미가 틀어집니다.
05
집계와 정렬에서의 NULL
TABLESTUDENT_SCORE 예시 데이터 (모의고사 4명)4행
STUDENT_ID
NAME
SCORE
BONUS
S1
서연
80
5
S2
민준
NULL
NULL
S3
하은
60
NULL
S4
도윤
40
0
STUDENT_SCORE에 집계 함수를 쓰면
쿼리
결과
이유
COUNT(*)
4
NULL 행(S2)도 셈
COUNT(SCORE)
3
SCORE가 NULL인 S2 제외
SUM(SCORE)
180
80 + 60 + 40
AVG(SCORE)
60
180 ÷ 3 (NULL은 분모에서도 빠짐)
AVG(NVL(SCORE, 0))
45
180 ÷ 4 (결시를 0점으로 본 경우)
COUNT(BONUS)
2
5와 0은 값이 있으므로 셈
값이 전부 NULL이면 SUM·AVG·MAX·MIN은 0이 아니라 NULL, COUNT(열)은 0을 돌려줍니다. 부서별로 묶었을 때 어떤 부서의 값이 모두 NULL이면 그 부서의 합계만 NULL로 나옵니다.
GROUP BY와 DISTINCT는 NULL끼리 같은 것으로 봅니다. GROUP BY 기준 열이 NULL인 행들은 결과에서 빠지지 않고 NULL 그룹 하나로 묶이고, DISTINCT도 여러 NULL을 한 행으로 줄입니다. 비교 연산에서 NULL = NULL이 UNKNOWN인 것과 다른 점입니다.
조건에 맞는 행이 하나도 없을 때도 구분합니다. GROUP BY가 없으면 전체를 한 그룹으로 보아 SELECT MAX(SCORE)가 NULL 한 행을 돌려줍니다. GROUP BY가 있으면 만들 그룹이 없어 결과가 0행입니다. 그래서 NVL(MAX(...), 'N')도 GROUP BY가 있으면 'N'을 보여 줄 행 자체가 없습니다.
정렬에서 Oracle은 NULL을 가장 큰 값처럼 다룹니다. 그래서 오름차순(ASC)이면 맨 뒤, 내림차순(DESC)이면 맨 앞입니다. SQL Server는 반대(ASC에서 맨 앞)입니다. NULLS FIRST / NULLS LAST로 직접 정할 수 있습니다.
06
NULL이 나오는 곳 모음
NULL 함정은 이 단원뿐 아니라 거의 모든 SQL 단원에 흩어져 나옵니다. 아래 표는 단원별로 NULL이 바꾸는 결과를 한 줄로 모은 것입니다. 시험 직전에 이 표 하나로 훑어보고, 헷갈리는 줄은 오른쪽 단원으로 돌아가 확인합니다.
보충 실습여기부터는 테이블을 직접 만들어 보는 긴 SQL 실습입니다. 처음 공부할 때는 건너뛰고, 위 내용을 이해한 뒤 돌아와도 됩니다.
07
SQL 실전: 점수 데이터로 NULL 다루기
보충 실습
예시 데이터를 만들고 NULL 비교, 대체 함수, 계산 결과를 확인합니다. 보너스를 더한 최종 점수를 구할 때 NULL을 그대로 더하면 결과가 NULL이 되어 S3(하은)의 점수가 사라집니다.
-- Oracle: 연습용 스키마에서 처음 한 번만 실행CREATETABLESTUDENT_SCORE(
STUDENT_ID VARCHAR2(5)PRIMARYKEY,
NAME VARCHAR2(20CHAR)NOTNULL,
SCORE NUMBER(3),
BONUS NUMBER(3));INSERTINTO STUDENT_SCORE VALUES('S1','서연',80,5);INSERTINTO STUDENT_SCORE VALUES('S2','민준',NULL,NULL);INSERTINTO STUDENT_SCORE VALUES('S3','하은',60,NULL);INSERTINTO STUDENT_SCORE VALUES('S4','도윤',40,0);COMMIT;-- 잘못된 비교: 0건SELECT NAME FROM STUDENT_SCORE WHERE SCORE =NULL;-- 올바른 비교: 민준SELECT NAME FROM STUDENT_SCORE WHERE SCORE ISNULL;-- Before / After: 보너스를 더한 최종 점수SELECT NAME,
SCORE + BONUS AS TOTAL_BEFORE,
SCORE +NVL(BONUS,0)AS TOTAL_AFTER,NVL2(BONUS,'있음','없음')AS BONUS_FLAG
FROM STUDENT_SCORE
ORDERBY STUDENT_ID;
TABLEBefore / After 결과4행
NAME
TOTAL_BEFORE
TOTAL_AFTER
BONUS_FLAG
서연
85
85
있음
민준
NULL
NULL
없음
하은
NULL
60
없음
도윤
40
40
있음
하은은 NVL(BONUS, 0) 덕분에 60점이 살아납니다. 민준은 SCORE 자체가 NULL이라 NVL을 BONUS에만 걸어도 결과가 NULL입니다. 도윤은 BONUS가 0이라는 값이 있어서 NVL2가 '있음'을 돌려줍니다.
08
SQL 실전: 공방 예약 데이터로 '' · NOT IN 확인하기
보충 실습
회원 4번 전화번호는 공백 한 칸 ' ', 5번은 빈 문자열 ''입니다. Oracle에서 5번은 NULL이 되고 4번은 NULL이 아닙니다. TRIM(PHONE)을 쓰면 공백만 있는 4번도 길이 0 → NULL이 되어 이메일로 넘어갑니다. 추가요금 NULL은 '추가 비용 없음'이라는 업무 규칙이 있어 NVL로 0 처리하고, 결제액 NULL은 '미기록'이라 바꾸지 않습니다.
CREATETABLEWORK_MEMBER(
MEMBER_ID NUMBERPRIMARYKEY,
MEMBER_NAME VARCHAR2(30)NOTNULL,
PHONE VARCHAR2(30),
EMAIL VARCHAR2(80));CREATETABLEWORK_RESERVATION(
RESERVATION_ID NUMBERPRIMARYKEY,
MEMBER_ID NUMBERNOTNULLREFERENCESWORK_MEMBER(MEMBER_ID),
BASE_PRICE NUMBER(8)NOTNULL,
EXTRA_PRICE NUMBER(8),
PAYMENT_AMOUNT NUMBER(8));INSERTINTO WORK_MEMBER VALUES(1,'지우','010-1100','jiu@example.test');INSERTINTO WORK_MEMBER VALUES(2,'도윤',NULL,'doyun@example.test');INSERTINTO WORK_MEMBER VALUES(3,'하린',NULL,NULL);INSERTINTO WORK_MEMBER VALUES(4,'민재',' ','minjae@example.test');INSERTINTO WORK_MEMBER VALUES(5,'소율','',NULL);INSERTINTO WORK_MEMBER VALUES(6,'채원',NULL,'chaewon@example.test');INSERTINTO WORK_RESERVATION VALUES(101,1,100,10,100);INSERTINTO WORK_RESERVATION VALUES(102,2,200,NULL,NULL);INSERTINTO WORK_RESERVATION VALUES(103,3,300,20,200);INSERTINTO WORK_RESERVATION VALUES(104,4,400,NULL,0);INSERTINTO WORK_RESERVATION VALUES(105,5,500,0,NULL);-- 원본 산술과 NULL을 0으로 대체한 계산 비교SELECT R.RESERVATION_ID,
R.EXTRA_PRICE,
R.BASE_PRICE + R.EXTRA_PRICE AS RAW_QUOTE,
R.BASE_PRICE +NVL(R.EXTRA_PRICE,0)AS FINAL_QUOTE,
R.PAYMENT_AMOUNT,COALESCE(TRIM(M.PHONE), M.EMAIL,'연락처 없음')AS CONTACT
FROM WORK_RESERVATION R
JOIN WORK_MEMBER M ON M.MEMBER_ID = R.MEMBER_ID
ORDERBY R.RESERVATION_ID;-- NULL 전용 조건과 상태 표시SELECT RESERVATION_ID,NVL2(PAYMENT_AMOUNT,'기록됨','미기록')AS PAYMENT_STATUS
FROM WORK_RESERVATION
WHERE PAYMENT_AMOUNT ISNULLORDERBY RESERVATION_ID;-- 틀린 비교: UNKNOWN이므로 0행SELECT RESERVATION_ID FROM WORK_RESERVATION WHERE PAYMENT_AMOUNT =NULL;-- 서브쿼리에 NULL이 포함되어 NOT IN이 0행SELECT RESERVATION_ID FROM WORK_RESERVATION
WHERE BASE_PRICE NOTIN(SELECT PAYMENT_AMOUNT FROM WORK_RESERVATION);-- 상관 NOT EXISTS: BASE_PRICE와 같은 결제액이 없는 예약SELECT R.RESERVATION_ID
FROM WORK_RESERVATION R
WHERENOTEXISTS(SELECT1FROM WORK_RESERVATION X
WHERE X.PAYMENT_AMOUNT = R.BASE_PRICE
)ORDERBY R.RESERVATION_ID;
TABLE예약별 계산 결과 (102·104는 추가요금 NULL)5행
RESERVATION_ID
RAW_QUOTE
FINAL_QUOTE
PAYMENT_AMOUNT
CONTACT
101
110
110
100
010-1100
102
NULL
200
NULL
doyun@example.test
103
320
320
200
연락처 없음
104
NULL
400
0
minjae@example.test
105
500
500
NULL
연락처 없음
결제액 미기록은 102·105번, NOT IN은 서브쿼리에 NULL이 섞여 0행, NOT EXISTS는 103·104·105번을 돌려줍니다. 결제액 집계는 COUNT(*)=5, COUNT(PAYMENT_AMOUNT)=3, AVG=100(300÷3), AVG(NVL(..., 0))=60(300÷5)입니다.