티스토리 뷰
사용자 테이블에서 휴대폰 번호가 안 들어간 행을 찾으려고 IS NULL을 걸었는데 아무것도 안 나온 적이 있다. 분명 화면에는 비어 보이는데 말이다.
원인은 단순했다. 그 컬럼에 들어 있던 건 NULL이 아니라 빈 문자열이었다. IS NULL 같은 문법이 있을 줄 알고 IS EMPTY를 찾았는데, 그런 건 없고 그냥 이렇게 하면 된다.
SELECT * FROM users WHERE mobile = '';
NULL과 빈 문자열은 완전히 다른 값이다
둘 다 "값이 없다"처럼 보이지만 DB 입장에서는 전혀 다르다.
- NULL: 값이 존재하지 않음. "모른다"에 가깝다.
- 빈 문자열(''): 길이가 0인 문자열이라는 값이 존재한다.
그래서 이런 결과가 나온다.
SELECT NULL = ''; -- NULL (true도 false도 아님)
SELECT '' IS NULL; -- 0 (false)
SELECT LENGTH(''); -- 0
SELECT LENGTH(NULL); -- NULL
NULL은 어떤 값과 비교해도 결과가 NULL이다. WHERE mobile = NULL 이 항상 아무것도 반환하지 않는 이유가 이거다. 그래서 NULL 비교에는 반드시 IS NULL / IS NOT NULL 을 써야 한다.
둘 다 한 번에 잡으려면
실무에서는 대개 "비어 있는 걸 전부" 찾고 싶으므로 두 조건을 같이 건다.
-- 방법 1: OR
SELECT * FROM users WHERE mobile IS NULL OR mobile = '';
-- 방법 2: COALESCE
SELECT * FROM users WHERE COALESCE(mobile, '') = '';
-- 방법 3: NULL 안전 비교 (MySQL 전용)
SELECT * FROM users WHERE mobile <=> NULL OR mobile = '';
가독성은 COALESCE 쪽이 좋지만, 컬럼에 함수를 씌우면 인덱스를 못 탄다. 대상 테이블이 크다면 방법 1을 쓰는 게 낫다. 이건 인덱스 설계할 때 자주 놓치는 부분이다.
공백만 들어간 경우도 있다
사용자가 스페이스만 입력하고 저장한 경우, mobile = '' 로는 안 걸린다. 눈으로 보면 비어 있는데 데이터는 ' ' 인 것이다.
SELECT * FROM users WHERE TRIM(mobile) = '';
-- 탭·개행까지 포함해서 잡으려면
SELECT * FROM users WHERE TRIM(BOTH ' ' FROM REPLACE(REPLACE(mobile, '\t', ' '), '\n', ' ')) = '';
참고로 MySQL의 CHAR 타입은 뒤쪽 공백을 자동으로 잘라내지만 VARCHAR는 그대로 저장한다. 같은 데이터인데 컬럼 타입에 따라 결과가 달라질 수 있으니 주의가 필요하다.
그래서 어느 쪽으로 저장할 것인가
이 문제가 반복되는 근본 원인은 애초에 두 가지 표현이 섞여 들어가기 때문이다. 스키마를 설계할 때 정책을 하나로 정해두면 조회 쿼리가 훨씬 단순해진다.
- NULL로 통일: 선택 입력 컬럼은
DEFAULT NULL. "값 없음"의 의미가 명확하고, 유니크 인덱스를 걸어도 NULL은 중복으로 취급되지 않아 편하다. - 빈 문자열로 통일:
NOT NULL DEFAULT ''. 애플리케이션에서 null 체크를 안 해도 되고, 집계 함수 다룰 때 예외가 줄어든다.
개인적으로는 "값이 아직 없다"와 "값이 비어 있음을 확인했다"를 구분해야 하는 도메인이면 NULL을, 그럴 필요가 없으면 NOT NULL DEFAULT '' 를 선호한다. 후자가 조회 코드가 훨씬 단순해진다.
Laravel에서는
// 마이그레이션
$table->string('mobile')->nullable(); // NULL 허용
$table->string('mobile')->default(''); // 빈 문자열 기본값
// 조회
User::whereNull('mobile')->orWhere('mobile', '')->get();
// 저장 전에 빈 값을 NULL로 정규화하고 싶다면 뮤테이터
public function setMobileAttribute($value)
{
$this->attributes['mobile'] = $value === '' ? null : $value;
}
입력 폼에서 넘어온 빈 문자열을 뮤테이터나 ConvertEmptyStringsToNull 미들웨어로 한 번 정규화해두면 이후 조회가 훨씬 깔끔해진다. Laravel은 기본으로 이 미들웨어가 켜져 있다.
정리
IS EMPTY같은 문법은 없다. 빈 문자열은= ''로 찾는다- NULL은
IS NULL로만 비교 가능.= NULL은 항상 아무것도 반환하지 않는다 - 둘 다 찾으려면
IS NULL OR = ''. 큰 테이블에서는 함수 대신 이 형태가 인덱스를 탄다 - 공백만 들어간 경우는
TRIM()으로 확인 - 가장 좋은 해결은 스키마 단계에서 둘 중 하나로 통일하는 것
'개발 > SQL' 카테고리의 다른 글
| 인덱스를 걸었는데도 쿼리가 느린 이유, MySQL 실행계획으로 잡는 실전 정리 (0) | 2026.08.20 |
|---|---|
| Offset 기반 페이지네이션의 문제점, 그리고 커서 기반으로 넘어가야 할 때 (0) | 2020.10.17 |
| AWS RDS 비용 줄이는 방법 (성능 개선 도우미) (2) | 2020.09.26 |
| 디비 빠르게 백업하고 복원하기 - mariadb (mysql) (0) | 2020.04.28 |
