티스토리 뷰

사용자 테이블에서 휴대폰 번호가 안 들어간 행을 찾으려고 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()으로 확인
  • 가장 좋은 해결은 스키마 단계에서 둘 중 하나로 통일하는 것
댓글


최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday