NULL은 값이 아닙니다. 그런데 사람은 자꾸 값처럼 다룹니다. 이 시험에서 아는 사람이 틀리는 자리는 거의 전부 이 착각에서 나옵니다.
풀고 시작
문제 1. 급여가 10과 20과 NULL인 세 행에서 급여의 평균을 구하면 결과는 얼마인가요?
집계 함수는 NULL을 제외하고 계산합니다. 합 삼십을 개수 둘로 나누어 십오입니다. 셋으로 나눈 십이 아닙니다.
세 갈래 논리라는 낯선 규칙
우리가 익숙한 논리는 참과 거짓 둘뿐입니다. 그런데 SQL의 조건 평가에는 알 수 없음이라는 세 번째 값이 있습니다. NULL이 끼는 순간 비교 결과가 참도 거짓도 아닌 이 상태가 됩니다.
여기서 첫 번째 함정이 나옵니다. WHERE 절은 참인 행만 통과시킵니다. 거짓과 알 수 없음은 똑같이 탈락합니다. 그래서 급여가 백오십보다 큰 행과 백오십보다 크지 않은 행을 각각 세어 더해도 전체 행 수가 되지 않습니다. NULL인 행이 양쪽 어디에도 들어가지 않기 때문입니다. 이 사실 하나만 알아도 조건 관련 문항의 절반이 풀립니다.
두 번째 함정은 NULL끼리의 비교입니다. 알 수 없는 값 둘이 같은지도 알 수 없습니다. 그래서 등호로 비교하면 참이 되지 않습니다. NULL을 확인하려면 IS NULL이라는 전용 표현을 써야 하는 이유가 이것입니다.
그런데 여기서 규칙이 갈립니다. 중복 제거와 그룹화에서는 NULL을 같은 것으로 봅니다. 비교에서는 다르다고 하면서 묶을 때는 같다고 하는 것이 모순처럼 보이지만, 목적이 다르기 때문입니다. 부서코드가 NULL인 사원 다섯 명은 하나의 그룹으로 묶여 한 행이 됩니다.
산술과 집계가 서로 다르게 행동합니다
같은 NULL인데 산술 연산과 집계 함수의 반응이 정반대입니다. 이 차이를 섞어 기억하면 반드시 틀립니다.
산술은 전염됩니다. NULL에 십을 더하면 NULL입니다. 곱해도 NULL이고 문자열을 이어 붙여도 표준에서는 NULL입니다. 하나라도 알 수 없으면 결과 전체가 알 수 없기 때문입니다.
집계는 무시합니다. 합계도 평균도 최댓값도 NULL을 빼고 계산합니다. 그래서 평균의 분모가 전체 행 수와 달라집니다. 이것이 시험에서 가장 자주 나오는 계산 함정입니다.
개수 세기는 한 번 더 갈립니다. 별표로 세면 행 자체를 세므로 NULL이 있어도 전체 행 수입니다. 컬럼을 지정해 세면 그 컬럼이 NULL인 행은 빠집니다. 두 값의 차이가 곧 그 컬럼의 NULL 개수입니다.
마지막으로 대상이 하나도 없을 때가 있습니다. 개수는 영을 돌려주지만 합계와 평균은 NULL을 돌려줍니다. 더할 것이 없으니 알 수 없는 것입니다.
상황
결과
NULL 더하기 10
NULL
10, 20, NULL의 합계
30
10, 20, NULL의 평균
15
10, 20, NULL의 전체 행 수
3
10, 20, NULL의 해당 컬럼 개수
2
행이 하나도 없을 때의 개수
0
행이 하나도 없을 때의 합계
NULL
NOT IN이 결과를 통째로 비우는 이유
이 자리는 따로 떼어 볼 만합니다. 실무에서도 자주 사고가 나고, 시험에서도 잘 갈립니다.
IN은 여러 개의 같음 비교를 OR로 잇습니다. OR는 하나만 참이면 전체가 참이므로, 목록에 NULL이 섞여 있어도 다른 값 하나만 맞으면 통과합니다. 그래서 IN은 NULL에 강합니다.
NOT IN은 여러 개의 같지 않음 비교를 AND로 잇습니다. AND는 전부 참이어야 참인데, NULL과의 비교 하나가 알 수 없음이 되는 순간 전체가 참이 되지 못합니다. 결과가 하나도 나오지 않습니다.
그래서 주문한 적이 없는 고객을 NOT IN으로 찾았는데 결과가 비어 있다면, 모든 고객이 주문한 것이 아니라 주문 테이블의 고객번호에 NULL이 섞여 있는 것입니다. 이럴 때는 NOT EXISTS를 쓰면 됩니다. NOT EXISTS는 값을 비교하지 않고 행의 존재만 보므로 이 함정이 없습니다.
외부 조인이 만들어 내는 NULL
원래 데이터에는 없던 NULL이 조인 과정에서 생기기도 합니다. 외부 조인에서 짝을 찾지 못한 행의 반대쪽 컬럼이 NULL로 채워지는 것이 그것입니다.
이 성질을 이용하면 차집합을 구할 수 있습니다. 고객을 기준으로 외부 조인한 뒤 주문 쪽 키가 NULL인 행만 남기면 주문한 적이 없는 고객이 나옵니다. 유용한 기법입니다.
동시에 여기서 가장 많이 하는 실수도 나옵니다. 외부 조인을 해 놓고 반대쪽 테이블의 조건을 WHERE 절에 쓰는 것입니다. 짝이 없어 NULL로 채워진 행은 그 조건을 만족하지 못하고 사라지므로, 결과적으로 내부 조인과 똑같아집니다. 외부 조인의 의미를 유지하려면 그 조건을 ON 절에 써야 합니다.
한 가지 더 있습니다. 외부 조인 결과에서 개수를 셀 때 별표로 세면 짝이 없는 행도 한 행이 있어 일이 나옵니다. 사원이 없는 부서의 사원 수가 일로 나오는 이유가 이것입니다. 영을 얻으려면 사원 쪽 컬럼을 지정해 세야 합니다.
인출 문제
문제 1. 급여가 10과 20과 NULL인 세 행에서 전체 행 수를 세는 방식과 급여 컬럼을 지정해 세는 방식의 결과를 바르게 짝지은 것은?
별표는 행 자체를 세므로 셋이고, 컬럼을 지정하면 NULL이 빠져 둘입니다. 두 값의 차이가 그 컬럼의 NULL 개수입니다.
문제 2. 부서코드가 10과 20과 NULL인 세 행에서 부서코드가 10과 20 목록에 없다는 조건을 걸면 결과 행 수는?
NOT IN은 같지 않음 비교를 AND로 잇는데 NULL과의 비교가 알 수 없음이 되어 전체가 참이 되지 못합니다.
문제 3. 부서 3건과 사원 9건이 있고 사원 중 3명의 부서코드가 NULL일 때 사원을 기준으로 왼쪽 외부 조인하면 결과 행 수는?
기준 쪽 행은 모두 남습니다. 부서코드가 NULL인 세 명도 부서 컬럼이 비어 있는 채로 남습니다.
문제 4. 부서를 기준으로 외부 조인한 뒤 사원의 직급이 과장이라는 조건을 WHERE 절에 쓰면 어떤 일이 벌어지나요?
외부 조인의 의미를 유지하려면 그 조건을 ON 절에 써야 합니다. 가장 자주 나오는 실수입니다.
더 풀기
출제기준의 세세항목을 따라 이 강의 범위에서 새로 낸 문제입니다. 모의고사도 여기에서 뽑습니다.
묶음 1
문제 1. 값이 100과 200과 NULL인 세 행에서 합계를 구하면?
집계 함수는 NULL을 제외합니다. 백과 이백만 더해 삼백입니다.
문제 2. 값이 100과 200과 NULL과 NULL인 네 행에서 평균을 구하면?
NULL 둘을 빼고 두 행으로 계산하므로 삼백을 둘로 나눈 백오십입니다.
문제 3. 같은 네 행에서 NULL을 0으로 바꾼 뒤 평균을 구하면?
네 행 모두 계산에 들어가 삼백을 넷으로 나눈 칠십오가 됩니다. 어느 쪽이 맞는지는 업무가 정합니다.
문제 4. 값이 전부 NULL인 다섯 행에서 합계를 구하면?
더할 값이 하나도 없으므로 결과는 알 수 없음입니다.
문제 5. 값이 전부 NULL인 다섯 행에서 전체 행 수를 세면?
별표는 행 자체를 세므로 다섯입니다. 컬럼을 지정하면 영이 됩니다.
문제 6. 조건에 맞는 행이 하나도 없을 때 개수를 세면 결과는?
그룹 기준 없이 집계만 쓰면 대상이 없어도 한 행이 나오고 개수는 영입니다.
문제 7. 조건에 맞는 행이 하나도 없을 때 최댓값을 구하면?
비교할 값이 없으므로 알 수 없음입니다. 개수만 영을 돌려주고 나머지 집계는 NULL입니다.
문제 8. NULL에 문자열을 이어 붙이면 표준적으로 어떤 결과가 나오나요?
표준에서는 NULL이 전염됩니다. 다만 일부 제품은 빈 문자열처럼 다루므로 이식할 때 주의해야 합니다.
문제 9. 값이 NULL인 컬럼에 문자열 길이 함수를 적용하면?
대부분의 단일행 함수는 입력이 NULL이면 결과도 NULL입니다.
문제 10. 두 개의 NULL을 등호로 비교하면 결과는?
알 수 없는 값 둘이 같은지도 알 수 없습니다. 그래서 IS NULL을 씁니다.
묶음 2
문제 1. 급여가 100과 200과 NULL인 세 행에서 급여가 150보다 크다는 조건과 150보다 크지 않다는 조건의 결과 행 수를 더하면?
각각 한 행씩이라 둘입니다. NULL 행은 양쪽 어디에도 들어가지 않으므로 전체 행 수가 되지 않습니다.
문제 2. NULL인 행까지 포함해 조회하려면 조건을 어떻게 써야 하나요?
NULL은 어떤 비교로도 걸리지 않으므로 전용 조건을 따로 붙여야 합니다.
문제 3. 부서코드가 10과 10과 20과 NULL과 NULL인 다섯 행을 부서코드로 묶으면 결과 행 수는?
그룹화에서는 NULL끼리 같은 것으로 보아 하나의 그룹이 됩니다. 십과 이십과 NULL로 세 행입니다.
문제 4. 같은 다섯 행에서 서로 다른 부서코드의 개수를 세면?
중복을 없애면 십과 이십이 남고 NULL은 개수 함수에서 제외되어 둘입니다.
문제 5. 집합 연산에서 NULL은 중복 판정을 어떻게 받나요?
비교와 달리 중복 제거와 그룹화에서는 같은 것으로 묶습니다. 규칙이 다르다는 점을 기억해야 합니다.
문제 6. IN 목록에 NULL이 있어도 문제가 되지 않는 이유는?
OR는 하나만 참이면 됩니다. AND로 이어지는 NOT IN과 결정적으로 다릅니다.
문제 7. 주문한 적이 없는 고객을 NOT IN으로 찾았더니 결과가 하나도 없습니다. 가장 가능성이 높은 이유는?
NULL과의 같지 않음 비교가 알 수 없음이 되고 AND로 이어져 어떤 행도 통과하지 못합니다.
문제 8. 같은 상황에서 안전한 대안으로 가장 알맞은 것은?
NOT EXISTS는 값을 비교하지 않고 행의 존재만 보므로 NULL 함정이 없습니다.
문제 9. 스칼라 서브쿼리의 결과가 한 행도 없을 때 그 자리의 값은?
결과가 없으면 NULL로 채워집니다. 반대로 여러 행이 나오면 오류가 납니다.
문제 10. 상관 서브쿼리로 값을 가져와 갱신할 때 해당하는 행이 없으면 어떻게 되나요?
값이 없다고 건너뛰지 않습니다. 그래서 조건절에 EXISTS를 함께 두어야 합니다.
묶음 3
문제 1. 부서 5건 중 두 부서에 사원이 없고 사원 20건은 모두 부서를 가질 때 부서 기준 외부 조인 결과 행 수는?
짝지어진 스무 행에 사원이 없는 부서 두 행이 더해집니다.
문제 2. 같은 결과에서 부서별로 묶어 별표로 개수를 세면 사원이 없는 부서의 값은?
외부 조인으로 한 행이 남아 있으므로 별표는 일을 셉니다. 영을 얻으려면 사원 쪽 컬럼을 지정해야 합니다.
문제 3. 같은 결과에서 사원번호 컬럼을 지정해 세면 사원이 없는 부서의 값은?
사원번호가 NULL이므로 개수에서 제외되어 영입니다.
문제 4. 주문한 적이 없는 고객을 외부 조인으로 찾는 방법으로 알맞은 것은?
짝이 없으면 그쪽 컬럼이 NULL이 됩니다. 그 성질로 차집합을 얻습니다.
문제 5. 오라클 계열에서 급여 오름차순으로 정렬할 때 NULL은 기본적으로 어디에 오나요?