묵묵한 파괴자 NULL: SQL 쿼리에 미치는 영향 이해하기
Lukas Schneider
DevOps Engineer · Leapcell

소개
관계형 데이터베이스의 세계에서 데이터 무결성과 정확한 쿼리는 무엇보다 중요합니다. 개발자와 데이터 분석가는 의미 있는 통찰력을 추출하기 위해 정확한 SQL 문을 만드는 데 셀 수 없는 시간을 보냅니다. 그러나 겉보기에는 간단한 쿼리 표면 아래에는 간단해 보이지만 심오하게 복잡한 개념인 NULL이 숨어 있습니다. 종종 오해되고 자주 과소평가되는 NULL은 COUNT(), JOIN 및 WHERE 절의 예상 동작을 조용히 파괴하여 잘못된 집계, 조인 시 데이터 손실 및 파악하기 어려운 레코드를 초래할 수 있습니다. 이 글은 NULL의 뉘앙스를 깊이 파고들어 이 특수 마커가 간단해 보이는 쿼리를 복잡한 퍼즐로 어떻게 변환할 수 있는지 보여주고, 그 영향을 조명하기 위한 실용적인 예제를 제공합니다.
NULL의 특별한 성격
복잡성을 풀기 전에 NULL에 대한 명확한 이해를 확립해 봅시다.
- NULL: SQL에서 NULL은 누락되거나 알 수 없는 정보를 나타내는 마커입니다. NULL은 값이 아니라는 것을 이해하는 것이 중요합니다. 0도 아니고, 빈 문자열도 아니고, 거짓도 아닙니다. 그것은 값의 부재입니다. 이 구별은 NULL이 연산자와 함수와 상호 작용하는 방식에 영향을 미치기 때문에 기본적입니다.
이러한 기초적인 이해를 바탕으로 NULL이 일반적인 SQL 연산을 어떻게 복잡하게 만드는지 살펴보겠습니다.
COUNT()와 NULL의 특수성
COUNT() 집계 함수는 행 또는 열의 NULL이 아닌 값 수를 세는 데 사용됩니다. NULL의 존재는 그 결과를 크게 변경할 수 있습니다.
COUNT(*): 이 구문은 NULL 값을 포함한 모든 행을 결과 집합에 포함하여 셉니다.COUNT(column_name): 이 구문은 지정된column_name에서 NULL이 아닌 값만 셉니다.COUNT(DISTINCT column_name): 이 구문은 지정된column_name에서 고유한 NULL이 아닌 값의 수를 셉니다.
products 테이블을 고려해 봅시다.
CREATE TABLE products ( product_id INT PRIMARY KEY, product_name VARCHAR(100), price DECIMAL(10, 2), category_id INT ); INSERT INTO products (product_id, product_name, price, category_id) VALUES (1, 'Laptop', 1200.00, 101), (2, 'Keyboard', 75.00, 102), (3, 'Mouse', 25.00, 102), (4, 'Monitor', 300.00, NULL), (5, 'Webcam', 50.00, 103), (6, 'Speaker', NULL, 103), (7, NULL, 10.00, 104); -- 이름이 알려지지 않은 제품이라고 가정
COUNT()의 작동을 살펴봅시다.
-- 모든 행 세기 SELECT COUNT(*) FROM products; -- 예상 출력: 7 -- NULL이 아닌 가격 세기 SELECT COUNT(price) FROM products; -- 예상 출력: 6 (제품 ID 6은 NULL 가격을 가짐) -- NULL이 아닌 category_id 세기 SELECT COUNT(category_id) FROM products; -- 예상 출력: 6 (제품 ID 4는 NULL category_id를 가짐) -- 고유한 category_id 세기 (NULL은 세지 않음) SELECT COUNT(DISTINCT category_id) FROM products; -- 예상 출력: 3 (101, 102, 103. NULL은 제외됨) -- NULL이 아닌 product_name 세기 SELECT COUNT(product_name) FROM products; -- 예상 출력: 6 (제품 ID 7은 NULL product_name을 가짐)
이 예제는 COUNT(column_name)이 명시적으로 NULL을 무시한다는 것을 분명히 보여줍니다. 이것은 해당 열이 존재하는 모든 행을 계산할 것이라고 예상할 경우 혼란의 원인이 될 수 있습니다.
JOIN 연산과 파악하기 어려운 NULL
JOIN 절은 관련 열을 기준으로 두 개 이상의 테이블에서 행을 결합합니다. 이러한 관련 열에 NULL이 포함된 경우 동작은 직관적이지 않을 수 있습니다.
- NULL = NULL은 FALSE 입니다: SQL에서
NULL = NULL은 알 수 없음으로 평가되며, 비교 작업에서 FALSE로 간주됩니다. 이는 NULL이 있는 행은 표준 동등 연산(=)을 사용하여INNER JOIN또는LEFT/RIGHT JOIN조건에서 NULL과 일치하지 않는다는 것을 의미합니다.
categories 테이블을 고려해 봅시다.
CREATE TABLE categories ( category_id INT PRIMARY KEY, category_name VARCHAR(100) ); INSERT INTO categories (category_id, category_name) VALUES (101, 'Electronics'), (102, 'Peripherals'), (103, 'Accessories'), (999, 'Uncategorized'); -- 사용되지 않는 카테고리
이제 products와 categories를 조인해 봅시다.
-- INNER JOIN: product.category_id가 category.category_id와 일치하는 행만 SELECT p.product_name, c.category_name FROM products p INNER JOIN categories c ON p.category_id = c.category_id;
예상 출력:
| product_name | category_name |
|---|---|
| Laptop | Electronics |
| Keyboard | Peripherals |
| Mouse | Peripherals |
| Webcam | Accessories |
| Speaker | Accessories |
'Monitor'(NULL category_id) 및 NULL product_name(존재하지 않는 category_id 104)가 있는 제품은 완전히 누락되었습니다. product.category_id = category.category_id가 NULL에 대해 알 수 없음/거짓으로 평가되기 때문입니다.

