3  사라진 기록의 비밀

데이터 탐정의 가장 어려운 임무는 존재하지 않는 증거를 다루는 것이다. 1930년대 남극에서 기록되지 않은 날짜, 측정되지 않은 값, 그리고 의도적으로 지워진 흔적들… 이 모든 ’없음’이야말로 가장 중요한 단서가 될 수 있다.

앞 장에서 우리는 752번 방문의 날짜가 사라졌다는 충격적인 사실을 발견했다. 이제 그 ’사라진 데이터’의 진실을 파헤쳐 보자.

3.1 탐정이 마주한 ’없음’의 미스터리

실제 범죄 수사에서 가장 의미심장한 증거는 종종 없는 것이다. 현장에 없어야 할 지문, 기록되지 않은 시간, 증언에서 빠진 부분들… 1930년대 남극 탐사 기록도 마찬가지다.

데이터베이스는 이런 ’없음’을 NULL이라는 특별한 표시로 기록한다. NULL은 단순히 0이나 빈 공간이 아니다. “이 자리에는 뭔가 있어야 하지만 알 수 없다”는 뜻이다. 마치 사라진 다이어리 페이지처럼 말이다.

3.1.1 사라진 증거를 보이게 만들기

훌륭한 탐정은 보이지 않는 것도 보이게 만든다. SQLite는 기본적으로 사라진 데이터(NULL)를 빈 공간으로 표시한다. 하지만 우리는 이 ’없음’을 명확히 드러내야 한다.

.nullvalue -null-

이제 사라진 증거들이 -null-로 표시된다. 마치 포렌식 전문가가 지워진 글씨를 특수 시약으로 드러내는 것처럼.

3.2 752번 방문: 사라진 날짜 미스터리

앞 장에서 발견한 충격적인 사실을 다시 확인해보자. 8번의 방문 기록 중 오직 752번만 날짜가 사라져 있다:

SELECT * FROM Visited;
id site dated
619 DR-1 1927-02-08
622 DR-1 1927-02-10
734 DR-3 1930-01-07
735 DR-3 1930-01-12
751 DR-3 1930-02-26
752 DR-3 -null-
837 MSK-4 1932-01-14
844 DR-1 1932-03-22

바로 여기다! 752번 방문만 날짜가 -null-로 표시된다. 다른 모든 방문들은 정확한 날짜가 기록되어 있는데 말이다. 이것이 바로 우리가 찾던 의도적으로 숨겨진 증거다.

3.3 NULL 이상 행동

탐정이 “1930년 이전의 모든 방문 기록을 보여달라”고 요청했다고 가정해보자. 일반적인 추리로는 752번 방문이 1930년 이전일 수도, 이후일 수도 있으니까 어디선가는 나타날 것이라고 생각할 것이다. 하지만 NULL은 우리의 예상을 완전히 뒤집는다.

1930년 이전 방문 조사:

SELECT * FROM Visited WHERE dated < '1930-01-01';
id site dated
619 DR-1 1927-02-08
622 DR-1 1927-02-10

1930년 이후 방문 조사:

SELECT * FROM Visited WHERE dated >= '1930-01-01';
id site dated
734 DR-3 1930-01-07
735 DR-3 1930-01-12
751 DR-3 1930-02-26
837 MSK-4 1932-01-14
844 DR-1 1932-03-22

충격적인 발견: 752번 방문이 양쪽 어디에도 나타나지 않는다!

이것이 바로 NULL 미스터리다. NULL < 1930년? 알 수 없다. NULL >= 1930년? 역시 알 수 없다.

NULL은 “모른다”는 뜻이고, 모르는 것과 무엇을 비교해도 결과는 여전히 “모른다”가 된다. 그리고 SQL의 WHERE 절은 오직 확실히 ’참’인 것들만 선택한다. “모른다”는 답은 제외된다.

752번 방문은 마치 시간의 틈새에 숨어있는 것 같다.

3.3.1 NULL의 전염성

NULL은 마치 바이러스처럼 퍼진다. NULL과 만나는 모든 계산이 NULL이 되어버린다:

  • 1 + NULL = NULL (1에 무엇을 더했는지 모르니까 결과도 모름)
  • 5 × NULL = NULL (5에 무엇을 곱했는지 모르니까 결과도 모름)
  • NULL = NULL = NULL (놀랍게도 이것도 ’모름’이다!)
경고가장 흔한 함정
-- 이 쿼리는 아무것도 찾지 못한다!
SELECT * FROM Visited WHERE dated = NULL;
-- 이것도 마찬가지로 아무것도 찾지 못한다!
SELECT * FROM Visited WHERE dated != NULL;

NULL인지 확인하려면 특별한 탐정 도구가 필요하다:

3.3.2 사라진 날짜를 찾는 특별한 방법

NULL인지 확인하려면 일반적인 등호(=)나 부등호(!=)가 아닌 특별한 연산자가 필요하다. 마치 특수한 탐정 도구처럼 IS NULL을 사용해야 한다:

-- 날짜가 누락된 방문 찾기
SELECT * FROM Visited WHERE dated IS NULL;
id site dated
752 DR-3 -null-

드디어 752번 방문을 찾아냈다! 이 방문만이 의도적으로 날짜를 숨긴 의심스러운 기록이다.

반대로, 날짜가 정상적으로 기록된 모든 방문들을 보려면 IS NOT NULL을 사용한다:

-- 날짜가 기록된 모든 방문 조회
SELECT * FROM Visited WHERE dated IS NOT NULL;
id site dated
619 DR-1 1927-02-08
622 DR-1 1927-02-10
734 DR-3 1930-01-07
735 DR-3 1930-01-12
751 DR-3 1930-02-26
837 MSK-4 1932-01-14
844 DR-1 1932-03-22

8개 방문 중 7개는 정상적으로 날짜가 기록되어 있다. 752번만이 예외다. 이것이 우연일까, 아니면 의도적인 것일까?

3.4 진짜 수사: Lake 용의선상 제외

수사를 진행하던 중 탐정은 “Lake가 측정하지 않은 모든 염도 측정값”을 찾고 싶어졌다. 직관적으로 다음과 같이 쿼리를 작성할 것이다:

SELECT * FROM Survey WHERE quant = 'sal' AND person != 'lake';
taken person quant reading
619 dyer sal 0.13
622 dyer sal 0.09
752 roe sal 41.6
837 roe sal 22.5

하지만 이것은 함정이다! 측정자가 불분명한 기록이 하나 숨어있다. NULL과 Lake를 비교한 NULL != 'lake'는 ’NULL’이 되어 결과에서 제외되었다.

3.4.1 완전한 수사를 위한 추가 조사

진짜 완전한 조사를 위해서는 “측정자가 불분명한 경우도 포함”해야 한다:

SELECT * FROM Survey WHERE quant = 'sal' AND (person != 'lake' OR person IS NULL);
taken person quant reading
619 dyer sal 0.13
622 dyer sal 0.09
735 -null- sal 0.06
752 roe sal 41.6
837 roe sal 22.5

새로운 증거 발견! 735번 측정에서 측정자가 불분명한 염도값 0.06이 있었다.

3.4.2 탐정의 딜레마: 엄격함 vs 포용성

실제 수사 현장에서 탐정들이 마주하는 가장 어려운 순간 중 하나가 바로 이것이다. 불완전한 증거를 어떻게 다룰 것인가? 1930년대 남극 탐사 기록에서도 이 딜레마가 고스란히 드러난다.

첫 번째 접근법: 엄격한 수사는 확실한 증거만을 채택하는 방식이다. Lake가 측정하지 않았다고 100% 확신할 수 있는 기록들만 포함시킨다. 이 방법의 장점은 결과의 신뢰성이 높다는 것이다. 하지만 치명적인 단점이 있다 - 중요한 단서를 놓칠 수 있다는 점이다. 735번 측정에서 발견된 0.06의 염도값처럼, 측정자가 불분명하지만 패턴 분석에 중요한 데이터를 제외할 위험이 있다.

두 번째 접근법: 포용적 수사는 불완전한 증거라도 전체 그림을 파악하는 데 도움이 된다면 포함시키는 방식이다. 이는 특히 1930년대처럼 기록 시스템이 완벽하지 않았던 시대의 데이터를 다룰 때 유용하다. 극한의 추위(-26도)나 위험한 상황에서는 기록자의 신원을 정확히 남기기 어려웠을 것이다. 이런 맥락을 고려하면, 측정자가 불분명한 기록도 가치있는 정보를 담고 있을 수 있다.

현실적으로 탐정은 수사의 목적에 따라 접근법을 달리해야 한다. 법정에서 증거로 제시해야 한다면 엄격한 기준을 적용해야 하지만, 전체적인 패턴을 파악하고 새로운 수사 방향을 설정하는 단계라면 포용적 접근이 더 효과적일 수 있다. 752번 방문의 미스터리를 푸는 과정에서 우리는 두 접근법을 모두 활용해야 할 것이다.

노트통계 함수는 NULL을 무시한다

흥미롭게도 SQL의 통계 함수들(MIN, MAX, AVG 등)은 NULL을 완전히 무시한다. 이는 대부분 합리적인 결정이다.

예를 들어 평균을 계산할 때, “모르는 값”은 포함시키지 않는 것이 맞다. 마치 탐정이 불확실한 증언은 제외하고 확실한 증언만으로 결론을 내리는 것처럼.

더 자세한 집계 분석은 다음 장에서 752번 방문의 미스터리를 해결하면서 살펴볼 예정이다.


💭 생각해볼 점

NULL은 단순한 “빈 공간”이 아니라 “알 수 없음”을 의미하는 특별한 상태다. 752번 방문의 사라진 날짜처럼, NULL은 종종 가장 중요한 단서가 될 수 있다.

SQL에서 NULL을 다룰 때 가장 혼란스러운 점은 NULL이 일반적인 값과 완전히 다르게 행동한다는 것이다. 먼저 NULL과의 모든 비교 연산(=, !=, <, >)은 참도 거짓도 아닌 NULL 자체를 반환한다. 이는 “모르는 값과 무엇을 비교해도 여전히 모름”이라는 논리적 일관성 때문이다. 따라서 NULL 값을 찾고자 할 때는 반드시 IS NULL이나 IS NOT NULL이라는 특별한 연산자를 사용해야 한다.

복잡한 조건을 만들 때는 더욱 주의해야 한다. 예를 들어 “Lake가 아닌 모든 측정자”를 찾으려 할 때 단순히 person != 'lake'만 사용하면 측정자가 NULL인 기록들은 결과에서 제외된다. 이런 경우 person != 'lake' OR person IS NULL처럼 NULL 케이스를 명시적으로 처리해야 완전한 결과를 얻을 수 있다. 마지막으로 통계 함수들(MIN, MAX, AVG 등)은 NULL 값을 자동으로 무시하고 계산한다는 점도 기억해야 한다.

탐정으로서의 교훈으로 때로는 “없는 것”이 “있는 것”보다 더 많은 것을 말해준다. 752번 방문의 사라진 날짜가 바로 그 증거다.

다음 장에서는 더 복잡한 SQL 도구들을 사용해 이 미스터리의 전체 그림을 완성해보자…