NULL은 "알 수 없음"을 뜻한다
SQLite의 다른 값들은 모두 무언가 확실한 것을 나타낸다. 숫자든 문자열이든 블롭이든 분명한 의미가 있다. 그런데 NULL은 다르다. 값이 비어 있거나 아직 알 수 없는 상태를 표시하는 자리표시자다. 이 한 가지 개념만 잡으면 NULL이 쿼리에서 보여주는 그 모든 이상한 동작이 한 번에 이해된다.
먼저 가지고 놀 작은 테이블을 하나 만들어 보자:
두 컬럼 모두 NULL을 허용합니다. Boris는 이메일이 없고, Cleo는 나이가 없으며, Dan은 둘 다 없습니다. 이 글의 나머지 부분에서는 이런 행들을 다룰 때 헷갈리지 않고 제대로 조회하는 방법을 살펴봅니다.
SQLite에서 =와 <>로는 NULL을 비교할 수 없다
처음에는 누구나 WHERE email = NULL처럼 써보고 싶어집니다. 얼핏 보면 말이 되는 문법이죠. 하지만 결과는 아무것도 나오지 않습니다:
결과는 0행. 분명히 Boris와 Dan은 이메일이 null인데도 말이죠. 이유는 이렇습니다. NULL과 무엇을 비교하든 결과는 true나 false가 아니라 NULL이 나옵니다. SQLite의 WHERE 절은 조건이 true 인 행만 남기는데, NULL은 true가 아니니까 해당 행은 그대로 걸러져 버립니다.
<>로 비교해도 같은 함정에 빠집니다.
이 쿼리는 Ada를 제외한 모든 사람을 돌려줄 것 같지만, 실제로는 Cleo만 나옵니다. 이메일이 null인 Boris와 Dan은 결과에서 빠져버리죠. 이유는 간단합니다. NULL <> 'ada@example.com' 의 결과는 true가 아니라 또 다른 NULL이기 때문입니다.
이게 바로 SQL에서 가장 흔하게 마주치는 함정입니다. 쿼리에서 "있어야 할 행이 사라졌다" 싶으면 일단 null이 있는 컬럼부터 의심해 보세요.
IS NULL과 IS NOT NULL 사용하기
sqlite에서 null을 비교할 때는 IS 연산자를 써야 합니다. =와 달리 IS는 null을 제대로 인식해서 항상 true 또는 false만 반환하고, 절대 null을 돌려주지 않습니다:
첫 번째 쿼리는 Boris와 Dan을 반환하고, 두 번째 쿼리는 Ada와 Cleo를 반환합니다. SQLite에서 값이 비어 있는지 확인할 때는 오직 IS NULL과 IS NOT NULL 두 연산자만 사용해야 합니다. = NULL이나 <> NULL처럼 쓰고 싶을 때는 무조건 이 연산자로 바꿔야 한다고 기억해 두세요.
"Ada가 아닌 사람, NULL인 행까지 포함해서" 가져오고 싶다면 다음과 같이 두 조건을 명시적으로 묶어 주면 됩니다.
이렇게 하면 Boris, Cleo, Dan이 모두 결과에 나타납니다.
NULL은 산술 연산과 문자열 결합에서도 전파된다
"알 수 없음"이라는 규칙은 비교 연산에만 국한되지 않습니다. NULL이 끼어든 연산은 무엇이든 결과가 NULL이 됩니다:
next_year와 doubled는 Cleo와 Dan의 경우 null이 됩니다. labelled_age도 마찬가지로 null인데, 문자열에 NULL을 이어 붙이면 'Age: '가 아니라 그냥 NULL이 나오기 때문이죠. 컬럼에 null이 들어올 가능성이 있고, 그래도 의미 있는 값을 받아와야 한다면 직접 처리해 줘야 합니다. 이때 등장하는 게 다음 두 함수입니다.
IFNULL: 두 개 인자로 기본값 지정하기
IFNULL(a, b)는 a가 null이 아니면 a를, null이면 b를 돌려줍니다. null을 기본값으로 바꿔치기하는 가장 간단한 방법이죠:
Boris와 Dan은 (이메일 없음)을 받고, Cleo와 Dan은 0을 받게 됩니다. 원본 데이터는 그대로이고, IFNULL은 단지 출력 결과만 바꿔줄 뿐이에요.
IFNULL은 항상 인자 두 개만 받습니다. 더 많은 대체값이 필요하다면 COALESCE를 써야 합니다.
COALESCE: 첫 번째 NULL이 아닌 값을 반환
COALESCE(a, b, c, ...)는 인자들을 앞에서부터 차례로 훑어보면서 NULL이 아닌 첫 번째 값을 돌려줍니다. 즉, IFNULL을 여러 개의 대체값까지 지원하도록 확장한 버전이라고 보면 됩니다:
Ada와 Cleo는 이메일 값이 있으니 그대로 사용됩니다. 반면 Boris와 Dan은 이메일이 NULL이라, SQLite가 두 번째 인자인 이름 기반으로 합성한 주소를 대신 씁니다. 만약 그것마저 NULL이었다면 마지막 '익명'까지 흘러갔겠죠.
COALESCE는 어디서나 통하는 선택지입니다. 주요 SQL 데이터베이스라면 어떤 것을 써도 동작 방식이 같으니까요. 반면 IFNULL은 SQLite와 MySQL에서 인자가 두 개뿐일 때 쓸 수 있게 마련된 단축 표현입니다. 기본은 COALESCE로 가고, 인자가 정말 딱 두 개이고 짧은 이름이 더 보기 좋을 때만 IFNULL을 꺼내 쓰세요.
sqlite null과 빈 문자열은 다르다
흔히들 헷갈려 하는 부분입니다. NULL과 ''을 같은 것처럼 다루는 분들이 많은데, 둘은 전혀 다릅니다.
''은 길이가 0인 실제 문자열이고, NULL은 값 자체가 없다는 뜻입니다. 그래서 length('')는 0이지만, length(NULL)은 또 NULL이 됩니다. NULL = NULL 역시 1이 아니라 NULL로 평가되죠. 바로 이 점 때문에 IS NULL이라는 별도 연산자가 존재하는 겁니다.
같은 컬럼에 ''과 NULL이 둘 다 들어갈 수 있다면, "값 없음"을 어느 쪽으로 표현할지 미리 정하고 일관되게 사용하세요. 두 가지를 섞어 쓰면 쿼리마다 두 케이스를 모두 처리해야 하고, 결국 어딘가에서 한 케이스를 빠뜨리게 됩니다.
IN, NOT IN, DISTINCT에서의 NULL 처리
NULL이 슬그머니 발목을 잡는 자리가 몇 군데 더 있습니다.
리스트 안에 NULL이 섞인 IN 연산은 의외의 결과를 내놓는데, 특히 NOT IN과 함께 쓰면 더 그렇습니다:
25살이 아닌 사람을 모두 뽑고 싶었을 텐데, 결과는 텅 비어 있습니다. SQLite는 NOT IN (25, NULL)을 대략 age <> 25 AND age <> NULL로 풀어내는데, age <> NULL은 항상 NULL이라 전체 조건이 절대 참이 될 수 없거든요. 해결책은 비교 전에 리스트(또는 컬럼)에서 NULL을 미리 걸러내는 겁니다.
반면에 DISTINCT는 중복 제거 목적에서는 NULL끼리 서로 같다고 취급합니다:
결과로 세 행이 나옵니다. Ada의 이메일, Cleo의 이메일, 그리고 Boris와 Dan이 합쳐진 NULL 하나죠. GROUP BY나 UNION도 마찬가지로 NULL들을 하나의 그룹으로 묶어서 처리합니다. = 연산자가 NULL을 다루는 방식과는 정반대인 셈이죠. SQL이 NULL을 다루는 방식은 일관되지 않은 부분이 있어서, 어떤 연산자가 어느 쪽 규칙을 따르는지 알아두는 게 좋습니다.
sqlite null 처리 체크리스트
- 값이 비어 있는지 확인할 때는
IS NULL/IS NOT NULL을 사용하세요.= NULL은 절대 쓰면 안 됩니다. NULL이 끼어든 산술 연산, 문자열 연결, 비교 연산은 모두NULL을 반환합니다.- NULL을 기본값으로 대체하려면
COALESCE(a, b, c, ...)를 쓰고, 인자가 두 개라면 짧게IFNULL(a, b)로 처리하세요. - 빈 문자열
''과NULL은 다른 값입니다. 컬럼마다 "값 없음"을 어느 쪽으로 표현할지 정해두세요. NOT IN (..., NULL)은 거의 항상 버그입니다. NULL은 미리 걸러낸 뒤에 사용하세요.
다음 주제: 결과 정렬하기
NULL이 섞인 행까지 제대로 필터링할 수 있게 됐다면, 이제 결과를 보기 좋은 순서로 정렬할 차례입니다. 다음 페이지에서 다룰 ORDER BY는 정렬된 결과에서 NULL을 어디에 둘지에 대해 또 나름의 규칙을 가지고 있습니다.
자주 묻는 질문
SQLite에서 column = NULL은 왜 동작하지 않나요?
column = NULL은 왜 동작하지 않나요?NULL은 "값을 알 수 없음"을 의미하기 때문입니다. 알 수 없는 값과 비교하면 결과 역시 "알 수 없음"이 되고, 이건 참(true)이 아니에요. 그래서 WHERE col = NULL은 실제로 컬럼 값이 NULL인 행이 있어도 한 건도 매칭되지 않습니다. 이럴 때는 WHERE col IS NULL을 써야 합니다. <>도 마찬가지로 IS NOT NULL로 바꿔 쓰면 됩니다.
SQLite에서 IFNULL과 COALESCE는 뭐가 다른가요?
IFNULL(a, b)은 인자를 정확히 두 개만 받아서, a가 NULL이 아니면 a를, NULL이면 b를 돌려줍니다. 반면 COALESCE(a, b, c, ...)는 인자 개수에 제한이 없고 그중 NULL이 아닌 첫 번째 값을 반환해요. 정리하면 IFNULL은 인자 두 개짜리 단축 버전이고, COALESCE가 더 일반적인 형태입니다. 또 COALESCE는 대부분의 SQL DBMS에서 표준으로 지원되기 때문에 호환성을 생각하면 이쪽을 쓰는 게 좋습니다.
SQLite에서 NULL과 빈 문자열('')은 같은 건가요?
'')은 같은 건가요?다릅니다. NULL은 "값 자체가 없음"이고, ''는 길이가 0인 문자열, 즉 "비어 있다"는 사실이 명확히 정해진 값입니다. 그래서 '' IS NULL은 0(거짓)을 반환하고, length('')는 0이지만 length(NULL)은 NULL이 됩니다. 한 컬럼에 둘 다 들어올 수 있다면, 쿼리에서 따로 처리하거나 한쪽으로 통일해 두는 게 좋습니다.