이번 문서의 목표: 이 문서를 다 읽으면 트랜잭션의 ACID 4대 특성을 예시로 설명하고, NULL이 산술·비교·집계 연산에서 어떻게 다르게 처리되는지 실제 SQL 결과로 예측하며, NVL·NVL2·COALESCE·NULLIF의 차이를 상황에 맞게 골라 쓸 수 있다.
트랜잭션과 ACID 특성
왜 필요한가
계좌이체를 생각해 보자. “A 계좌에서 10만 원을 빼고, B 계좌에 10만 원을 더한다”는 작업은 논리적으로 하나의 업무지만, SQL 문으로는 UPDATE 두 번으로 나뉘어 실행된다. 만약 A 계좌에서 돈을 빼는 첫 번째 UPDATE는 성공했는데, 정전이나 오류로 두 번째 UPDATE가 실행되지 못한다면 어떻게 될까? A 계좌에서는 돈이 빠져나갔는데 B 계좌에는 들어오지 않는, 있어서는 안 될 상황이 벌어진다. 이런 사고를 막으려고 여러 개의 SQL 문을 “전부 성공하거나 전부 취소되는 하나의 단위”로 묶는 개념이 필요하다.
쉽게 말하면: 트랜잭션은 “여기서부터 저기까지는 하나의 작업 묶음이니, 중간에 끊기면 전부 없었던 일로 하라”는 약속이다.
정의: 트랜잭션과 ACID
트랜잭션(transaction)은 데이터베이스의 상태를 변화시키는 하나 이상의 SQL 연산을 “더 이상 나눌 수 없는 하나의 논리적 작업 단위”로 묶은 것이다. 트랜잭션이 안전하게 동작하려면 다음 네 가지 특성을 만족해야 하며, 각 특성의 앞글자를 따 ACID라 부른다.
| 특성 | 영문 | 의미 |
|---|---|---|
| 원자성 | Atomicity | 트랜잭션에 포함된 작업은 전부 반영되거나 전부 취소된다. 일부만 반영되는 중간 상태는 존재할 수 없다 |
| 일관성 | Consistency | 트랜잭션 실행 전후로 데이터베이스는 정해진 규칙(제약조건 등)을 항상 만족하는 상태를 유지한다 |
| 고립성 | Isolation | 동시에 실행 중인 여러 트랜잭션은 서로의 중간 과정에 영향을 주지 않는다(교재에 따라 격리성·독립성으로도 번역한다) |
| 지속성 | Durability | 트랜잭션이 완료(커밋)되면 그 결과는 시스템 장애가 발생해도 영구적으로 보존된다 |
작은 예시: 계좌이체로 보는 ACID
앞서 든 계좌이체 예시로 네 특성을 하나씩 확인해 보자. A 계좌에서 10만 원을 출금하고 B 계좌에 10만 원을 입금하는 트랜잭션이다.
UPDATE 계좌 SET 잔액 = 잔액 - 100000 WHERE 계좌번호 = 'A';
UPDATE 계좌 SET 잔액 = 잔액 + 100000 WHERE 계좌번호 = 'B';
COMMIT;- 원자성: 첫 번째
UPDATE만 실행된 채 시스템이 멈추면, 데이터베이스는 이 트랜잭션 전체를 취소해 A 계좌의 잔액을 원래대로 되돌린다. “A만 출금되고 B는 입금 안 된” 중간 상태가 영구히 남는 일은 없다. - 일관성: “모든 계좌의 잔액 합계는 이체 전후로 항상 같아야 한다”는 업무 규칙이 있다면, 트랜잭션이 끝난 뒤에도 이 규칙은 그대로 성립해야 한다. 만약 오류로 출금만 반영되고 입금이 반영되지 않는다면 전체 합계가 줄어들어 일관성이 깨진 것이다.
- 고립성: 이 트랜잭션이 실행되는 도중, 다른 사용자가 A 계좌의 잔액을 조회한다면 “출금은 됐는데 아직 입금 전인” 어중간한 값을 봐서는 안 된다. 트랜잭션이 완전히 끝나기 전까지 그 중간 상태는 다른 트랜잭션에게 보이지 않아야 한다.
- 지속성:
COMMIT으로 이체가 확정된 직후 서버 전원이 꺼지더라도, 서버가 다시 켜졌을 때 이체 결과는 그대로 남아 있어야 한다.
결과 해석
ACID는 “이 네 가지를 순서대로 실행하라”는 절차가 아니라, 트랜잭션 처리 시스템이라면 반드시 갖춰야 할 네 가지 성질이라는 점을 기억해야 한다. 12편에서 다룰 COMMIT·ROLLBACK·SAVEPOINT 같은 TCL(Transaction Control Language) 명령어는 바로 이 ACID, 특히 원자성과 지속성을 SQL 차원에서 구현하는 도구다.
정규화 수준과 성능의 상충이 ACID와 만나는 지점
07편에서 정규화를 지나치게 강하게 적용하면 조회 성능이 떨어질 수 있다고 배웠다. 그런데 ACID, 특히 원자성과 일관성을 안정적으로 지키려면 오히려 정규화된 구조가 유리하다. 값이 한 군데에만 저장되어 있어야, 트랜잭션이 그 한 곳만 수정하면 되고, 수정 도중 실패해도 되돌릴 지점이 명확하기 때문이다. 반대로 07편에서 다룬 반정규화처럼 같은 값(예: 총주문금액)을 여러 테이블에 중복 저장해 두면, 트랜잭션 하나가 여러 곳을 동시에 갱신해야 하므로 원자성 보장이 더 까다로워지고, 갱신 도중 실패하면 일관성이 깨질 위험도 커진다. 결국 “정규화 vs 반정규화”의 선택은 “조회 성능”과 “트랜잭션 안전성” 사이의 저울질이기도 하다.
자주 틀리는 점
ACID 네 글자를 외우다 보면 “보안성(Security)“이나 “가용성(Availability)” 같은 그럴듯한 용어를 ACID의 하나로 착각하는 오답이 자주 등장한다. ACID는 정확히 원자성·일관성·고립성·지속성 네 가지뿐이며, 보안이나 가용성은 데이터베이스 관리의 다른 영역(보안 관리, 장애 대비)에 속하는 별개의 개념이다.
NULL이란 무엇인가 — “값이 없다”가 아니라 “알 수 없다”
왜 필요한가
SQLD 시험에서 가장 많은 학습자가 실수하는 지점을 하나만 꼽으라면 단연 NULL이다. NULL을 “0”이나 “빈 문자열”과 같은 취급을 하면, 산술 연산·비교 연산·집계 함수 어느 하나에서도 결과를 정확히 예측할 수 없다. NULL의 정체를 정확히 알아야 이후 편에서 배울 조인·서브쿼리·집계 함수의 결과를 실수 없이 읽어낼 수 있다.
쉽게 말하면: NULL은 “값이 없다”는 빈 상자가 아니라, “그 상자 안에 무엇이 들었는지 아무도 모른다”는 상태다.
정의: NULL은 값이 아니라 상태다
NULL은 “아직 값이 정해지지 않았거나, 해당 사항이 없거나, 알 수 없는 상태”를 나타내는 특수한 표시(marker)다. 여기서 핵심은 NULL이 숫자 0도, 빈 문자열(”)도, 공백도 아니라는 점이다. 0은 “값이 0이다”라는 확정된 정보이고, NULL은 “값이 무엇인지조차 확정되지 않았다”는 정보의 부재다. 예를 들어 사원 테이블의 “커미션” 컬럼이 NULL이라면 “이 사원은 커미션이 0원이다”가 아니라 “이 사원에게 커미션이라는 개념 자체가 아직 적용되지 않았거나, 얼마인지 알 수 없다”는 뜻으로 해석해야 한다.
작은 예시
다음과 같은 사원 테이블이 있다.
| 사원번호 | 사원명 | 커미션 |
|---|---|---|
100 | 김민준 | 50 |
101 | 이서연 | NULL |
102 | 박도윤 | 30 |
103 | 최지우 | NULL |
이서연과 최지우의 커미션이 NULL인 이유는 다양할 수 있다. 아직 이번 달 커미션이 정산되지 않았을 수도 있고, 애초에 커미션을 받지 않는 직군일 수도 있다. 어느 쪽이든 “0원”이라고 단정할 근거가 없으므로 NULL로 남겨 두는 것이 정확하다.
자주 틀리는 점
“두 컬럼이 모두 NULL이면 값1 = 값2 비교 결과가 참(TRUE)이 되어 같은 값으로 처리된다”는 보기가 기출에 등장한 적이 있는데, 이는 명백한 오답이다. NULL은 “알 수 없는 값”이므로, 알 수 없는 값끼리 비교한 결과도 “같은지 다른지조차 알 수 없다”는 것이 정확한 논리다. 이 원리는 바로 다음 절에서 다루는 비교 연산의 핵심이다.
NULL의 연산: 산술·비교·논리
왜 필요한가
NULL이 포함된 값을 계산하거나 비교할 때, 그 결과를 직관적으로 짐작하면 대부분 틀린다. NULL이 관여하는 연산의 규칙은 “알 수 없는 값과 무엇을 하든, 결과도 알 수 없다”는 하나의 원칙에서 출발한다.
쉽게 말하면: 얼마인지 모르는 돈(NULL)에 100원을 더해도, 결국 얼마인지는 여전히 모른다.
정의 및 계산·적용: 산술 연산
NULL이 하나라도 포함된 산술 연산의 결과는 항상 NULL이다.
SELECT 100 + NULL AS 결과1,
NULL * 5 AS 결과2,
NULL - NULL AS 결과3
FROM DUAL;| 결과1 | 결과2 | 결과3 |
|---|---|---|
NULL | NULL | NULL |
100에 “알 수 없는 값”을 더하면 그 합 역시 “알 수 없는 값”이 되는 것이 당연하다. NULL을 0으로 착각해 “100 + NULL = 100”이라고 계산하면 틀린다.
정의 및 계산·적용: 비교 연산과 3값 논리
NULL을 =, <>, <, > 같은 비교 연산자로 비교한 결과도 TRUE나 FALSE가 아니라 UNKNOWN(알 수 없음)이라는 제3의 값이 된다. SQL은 TRUE·FALSE 두 가지만 존재하는 일반적인 불(Boolean) 논리가 아니라, 여기에 UNKNOWN까지 더한 3값 논리(three-valued logic)를 사용한다.
SELECT * FROM 사원 WHERE 커미션 = NULL; -- 항상 0건
SELECT * FROM 사원 WHERE 커미션 IS NULL; -- 이서연, 최지우 2건커미션 = NULL이라는 조건은 어떤 행에 대해서도 TRUE가 될 수 없다. 커미션 값이 NULL인 이서연의 행조차, “NULL = NULL”이 TRUE가 아니라 UNKNOWN으로 평가되기 때문에 결과에서 제외된다. WHERE 절은 조건이 정확히 TRUE인 행만 통과시키므로, UNKNOWN으로 평가된 행은 FALSE와 마찬가지로 걸러진다. NULL인지 확인하려면 비교 연산자가 아니라 전용 연산자인 IS NULL(또는 IS NOT NULL)을 반드시 사용해야 하는 이유가 바로 여기에 있다.
AND·OR 논리 연산도 UNKNOWN을 포함해 확장된다.
| AND | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | FALSE | UNKNOWN |
| FALSE | FALSE | FALSE | FALSE |
| UNKNOWN | UNKNOWN | FALSE | UNKNOWN |
| OR | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE |
| FALSE | TRUE | FALSE | UNKNOWN |
| UNKNOWN | TRUE | UNKNOWN | UNKNOWN |
결과 해석: NOT IN의 대표적인 함정
이 3값 논리가 실무·시험에서 가장 크게 발목을 잡는 대상이 NOT IN 서브쿼리다. 부서 테이블의 부서번호 컬럼에 NULL이 포함된 행이 있다고 하자.
SELECT * FROM 사원
WHERE 부서번호 NOT IN (SELECT 부서번호 FROM 부서);부서번호 NOT IN (10, 20, NULL)은 내부적으로 부서번호 <> 10 AND 부서번호 <> 20 AND 부서번호 <> NULL로 풀어 쓸 수 있다. 그런데 부서번호 <> NULL은 어떤 값을 넣어도 항상 UNKNOWN이다. AND 연산에서는 TRUE와 UNKNOWN을 함께 계산하면 최댓값이 UNKNOWN에 그치므로(위 AND 표 참고), 전체 조건이 절대로 TRUE가 될 수 없다. 결과적으로 서브쿼리 결과에 NULL이 단 하나라도 섞여 있으면 NOT IN 조건은 어떤 행도 통과시키지 못하고 공집합을 반환한다.
자주 틀리는 점
이 함정 때문에 실무와 시험 모두 NOT IN 대신 NOT EXISTS(17편에서 다룬다)를 권장한다. NOT EXISTS는 서브쿼리 결과에 NULL이 있어도 영향을 받지 않고 의도한 대로 동작한다. “서브쿼리 결과에 NULL이 있으면 그 NULL 행만 제외하고 나머지는 정상 비교된다”고 생각하는 것이 전형적인 오답 패턴이므로, “NULL이 하나라도 섞이면 NOT IN 전체가 무력화된다”는 결론을 정확히 기억해야 한다.
집계 함수와 NULL: COUNT(*)와 COUNT(컬럼)
왜 필요한가
15편에서 본격적으로 다룰 집계 함수(SUM, AVG, MAX, MIN, COUNT)는 NULL을 처리하는 방식에서 SQL 초심자가 가장 자주 틀리는 영역이다. “행이 4개면 개수도 당연히 4”라고 생각하기 쉽지만, NULL이 섞여 있으면 함수마다 답이 달라진다.
쉽게 말하면: 대부분의 집계 함수는 “모르는 값(NULL)은 계산에서 아예 빼고” 셈한다. 다만
COUNT(*)만은 예외로, 값의 내용과 상관없이 “행이 몇 개 있는지”만 센다.
정의 및 작은 예시
앞서 본 사원 테이블을 다시 사용한다.
| 사원번호 | 사원명 | 커미션 |
|---|---|---|
100 | 김민준 | 50 |
101 | 이서연 | NULL |
102 | 박도윤 | 30 |
103 | 최지우 | NULL |
SELECT COUNT(*) AS 전체행수,
COUNT(커미션) AS 커미션있는행수,
SUM(커미션) AS 커미션합계,
AVG(커미션) AS 커미션평균
FROM 사원;| 전체행수 | 커미션있는행수 | 커미션합계 | 커미션평균 |
|---|---|---|---|
4 | 2 | 80 | 40 |
계산·적용
COUNT(*)는 컬럼 값의 내용을 전혀 보지 않고 “테이블에 존재하는 행 자체의 개수”만 세므로, NULL이 포함된 행도 빠짐없이 포함해 4가 나온다. 반면 COUNT(커미션)은 커미션 컬럼의 값이 NULL이 아닌 행만 골라서 세므로, 커미션이 NULL인 이서연·최지우를 제외한 2가 나온다. SUM(커미션)도 NULL 행을 계산에서 제외하고 50 + 30 = 80을 반환한다.
가장 헷갈리는 부분은 AVG(커미션)이다. 전체 4명을 기준으로 “80 ÷ 4 = 20”이라고 계산하면 틀린다. AVG는 NULL이 아닌 값들의 합을 NULL이 아닌 값의 개수로 나누므로, 실제 계산은 다음과 같다.
- 분자(50 + 30)는 NULL이 아닌 커미션 값들의 합이다.
- 분모(2)는 NULL이 아닌 커미션 값의 개수다. 전체 행 수(4)가 아니다.
결과 해석
SUM, AVG, MAX, MIN, 그리고 COUNT(컬럼명)까지 포함한 대부분의 집계 함수는 계산 대상에서 NULL을 먼저 제외한 뒤 나머지 값들만으로 계산한다는 공통 규칙을 따른다. 이 규칙에서 유일하게 벗어나는 것이 COUNT(*)이며, COUNT(*)는 “행 자체가 존재하는지”만 볼 뿐 특정 컬럼의 값이 NULL인지는 신경 쓰지 않는다.
자주 틀리는 점
“COUNT(*)와 COUNT(컬럼)은 결국 같은 결과를 낸다”고 생각하는 것이 가장 흔한 실수다. 두 값이 같아지는 경우는 오직 그 컬럼에 NULL이 하나도 없을 때뿐이다. NULL이 하나라도 존재하는 컬럼이라면 COUNT(*) ≥ COUNT(컬럼)이 항상 성립하며, 시험은 이 차이를 실제 데이터와 함께 제시하고 결과를 직접 계산하게 만드는 방식으로 자주 출제한다.
NULL 처리 함수: NVL/ISNULL, NVL2, COALESCE, NULLIF
왜 필요한가
NULL을 그대로 두면 화면에 빈칸이 표시되거나, 앞서 본 것처럼 계산·비교가 예상과 다르게 흘러간다. 그래서 SQL은 “NULL이면 이 값으로 바꿔서 보여 달라”는 요청을 처리하는 전용 함수들을 제공한다. 네 함수는 하는 일이 서로 겹쳐 보이지만, 인자의 개수와 조건 판단 방식이 각각 다르다.
쉽게 말하면: NVL과 ISNULL은 “NULL이면 이 값으로 바꿔라”, NVL2는 “NULL 여부에 따라 서로 다른 두 값 중 하나를 골라라”, COALESCE는 “여러 후보 중 NULL이 아닌 첫 번째 값을 찾아라”, NULLIF는 “두 값이 같으면 오히려 NULL로 만들어라”는 뜻이다.
정의: 네 함수의 문법과 차이
| 함수 | 문법 | 동작 | 지원 DBMS |
|---|---|---|---|
NVL | NVL(a, b) | a가 NULL이면 b, NULL이 아니면 a를 반환 | Oracle |
ISNULL | ISNULL(a, b) | a가 NULL이면 b, NULL이 아니면 a를 반환(NVL과 동일한 역할) | SQL Server |
NVL2 | NVL2(a, b, c) | a가 NULL이 아니면 b, NULL이면 c를 반환 | Oracle |
COALESCE | COALESCE(a, b, c, ...) | 인자를 왼쪽부터 검사해 NULL이 아닌 첫 번째 값을 반환(모두 NULL이면 NULL) | 표준 SQL(Oracle·SQL Server·MySQL 등 대부분 지원) |
NULLIF | NULLIF(a, b) | a와 b가 같으면 NULL을, 다르면 a를 반환 | 표준 SQL |
작은 예시와 계산·적용
앞의 사원 테이블에 네 함수를 각각 적용해 보자.
SELECT 사원명,
커미션,
NVL(커미션, 0) AS NVL결과,
NVL2(커미션, '지급대상', '미지급') AS NVL2결과,
COALESCE(커미션, 0) AS COALESCE결과
FROM 사원;| 사원명 | 커미션 | NVL결과 | NVL2결과 | COALESCE결과 |
|---|---|---|---|---|
| 김민준 | 50 | 50 | 지급대상 | 50 |
| 이서연 | NULL | 0 | 미지급 | 0 |
| 박도윤 | 30 | 30 | 지급대상 | 30 |
| 최지우 | NULL | 0 | 미지급 | 0 |
이서연의 커미션은 NULL이므로 NVL(커미션, 0)은 두 번째 인자인 0을 반환한다. NVL2(커미션, '지급대상', '미지급')은 커미션이 NULL이 아니면 두 번째 인자, NULL이면 세 번째 인자를 반환하는 함수다. 김민준은 커미션이 NULL이 아니므로 ‘지급대상’이, 이서연은 커미션이 NULL이므로 ‘미지급’이 나온다. NVL2는 인자 순서가 “값, NULL이 아닐 때, NULL일 때” 순이라는 점이 NVL과 헷갈리는 대표적인 지점이다.
COALESCE(커미션, 0)은 커미션이 NULL이 아니면 커미션 값을, NULL이면 다음 후보인 0을 반환하므로 이 예시에서는 NVL(커미션, 0)과 결과가 완전히 같다. 다만 COALESCE는 인자를 두 개로 제한하지 않는다.
SELECT COALESCE(NULL, NULL, '지역센터', '본사') AS 결과 FROM DUAL;| 결과 |
|---|
| 지역센터 |
앞의 두 인자가 모두 NULL이므로 건너뛰고, NULL이 아닌 첫 번째 값인 ‘지역센터’를 반환한다. 이렇게 여러 개의 대체 후보를 순서대로 검사해야 할 때는 NVL을 여러 번 중첩하는 대신 COALESCE 하나로 표현하는 것이 더 간결하다.
NULLIF는 지금까지의 세 함수와 반대 방향으로 동작한다. “NULL을 다른 값으로 바꾸는” 함수가 아니라, 두 값이 같을 때 일부러 NULL을 만들어 내는 함수다.
SELECT NULLIF(10, 10) AS 결과1,
NULLIF(10, 20) AS 결과2
FROM DUAL;| 결과1 | 결과2 |
|---|---|
NULL | 10 |
NULLIF(10, 10)은 두 인자가 같으므로 NULL을 반환하고, NULLIF(10, 20)은 두 인자가 다르므로 첫 번째 인자인 10을 그대로 반환한다. 예를 들어 “매출과 목표가 정확히 같아서 증감률을 계산할 필요가 없는 경우를 걸러내고 싶을 때”, 분모가 0이 되는 것을 막으려고 NULLIF(분모, 0)을 나눗셈식에 끼워 넣어 나눗셈 오류 대신 NULL을 결과로 받는 식으로 응용한다.
결과 해석
네 함수는 겉보기에 비슷한 역할을 하지만, 표로 정리하면 역할이 뚜렷이 갈린다.
| 상황 | 적합한 함수 |
|---|---|
| NULL이면 대체값 하나로 바꾸고 싶다 | NVL(Oracle) 또는 ISNULL(SQL Server) |
| NULL 여부에 따라 서로 다른 두 결과를 각각 보여 주고 싶다 | NVL2 |
| 여러 후보 값을 순서대로 검사해 첫 NULL 아닌 값을 쓰고 싶다 | COALESCE |
| 두 값이 같을 때 오히려 NULL로 처리하고 싶다(예: 0으로 나누기 방지) | NULLIF |
자주 틀리는 점
NVL2의 인자 순서를 NVL처럼 “값, 대체값” 두 개로 착각하는 실수가 잦다. NVL2(a, b, c)는 세 개의 인자를 받으며, a가 NULL이 아닐 때 b를, NULL일 때 c를 반환한다는 순서를 정확히 기억해야 한다. 또한 ISNULL을 Oracle에서도 쓸 수 있는 표준 함수로 착각하는 경우가 있는데, ISNULL은 SQL Server 전용 함수이고 Oracle에서 같은 역할을 하는 함수는 NVL이다(DBMS별 문법 차이는 25편에서 더 자세히 다룬다). 마지막으로 NULLIF(a, b)를 “a와 b 중 NULL이 아닌 값을 반환하는 함수”로 COALESCE와 혼동하는 경우가 있는데, NULLIF는 대체값을 찾는 함수가 아니라 “같으면 NULL로 만드는” 정반대 방향의 함수라는 점을 분명히 구분해야 한다.
직접 해보기
00편에서 만든 실습 환경(Docker + Oracle + DBeaver)에서 진행합니다. 아직 만들지 않았다면 00편을 먼저 보세요.
직접 해보기 1 — NULL과 비교 연산
먼저 결과를 예상해 보자. 커미션 = NULL 조건으로 조회하면 몇 건이 나올까? IS NULL로 조회하면 몇 건이 나올까? 예상을 적어 둔 뒤 아래를 실행해서 맞춰 보자.
SELECT * FROM 사원 WHERE 커미션 = NULL;
SELECT * FROM 사원 WHERE 커미션 IS NULL;
SELECT 사원명, 커미션, 커미션 + 100 AS 계산결과 FROM 사원;무엇을 보아야 하나: 첫 번째 쿼리가 몇 건 나오는지(0건이어야 정상), 두 번째 쿼리와 결과가 얼마나 다른지, 세 번째 쿼리에서 커미션이 NULL인 행의 계산결과 컬럼이 어떻게 나오는지 확인한다.
왜 이걸 해보나: “NULL도 결국 등호로 비교되는 값 아닌가”라는 착각이 시험에서 가장 잘 나오는 함정이다. 직접 0건을 눈으로 보고 나면 다시는 헷갈리지 않는다.
직접 해보기 2 — 집계 함수와 NULL
이번에도 먼저 예상해 보자. 사원 4명 중 커미션이 NULL인 사람이 2명이다. COUNT(*)와 COUNT(커미션)은 각각 몇이 나올까? AVG(커미션)은 얼마가 나올까(80을 4로 나눈 값일까, 2로 나눈 값일까)?
SELECT COUNT(*) AS 전체행수,
COUNT(커미션) AS NULL제외행수,
AVG(커미션) AS 평균,
AVG(NVL(커미션, 0)) AS NVL적용평균
FROM 사원;무엇을 보아야 하나: COUNT(*)와 COUNT(커미션) 두 값이 서로 다른지, AVG(커미션)과 AVG(NVL(커미션, 0)) 두 평균값이 서로 다른지 확인한다.
왜 이걸 해보나:
AVG의 분모를 전체 행 수로 착각하는 실수는 두 평균값의 차이를 직접 눈으로 비교해 봐야 확실히 교정된다.
핵심 정리
- 트랜잭션은 여러 SQL 연산을 하나의 논리적 단위로 묶은 것이며, 원자성·일관성·고립성·지속성(ACID)을 모두 만족해야 안전하다. 보안성·가용성은 ACID에 속하지 않는다.
- NULL은 값이 아니라 “알 수 없음”이라는 상태다. 산술 연산 결과는 항상 NULL이 되고, 비교 연산 결과는 TRUE/FALSE가 아닌 UNKNOWN이 되므로
=대신IS NULL을 써야 한다. NOT IN서브쿼리 결과에 NULL이 하나라도 섞이면 3값 논리에 의해 전체 조건이 공집합이 된다.NOT EXISTS로 대체하는 것이 안전하다.COUNT(*)는 NULL을 포함한 모든 행을 세지만,COUNT(컬럼)을 포함한 대부분의 집계 함수는 NULL을 제외하고 계산한다.AVG의 분모도 전체 행 수가 아니라 NULL이 아닌 값의 개수다.NVL/ISNULL은 NULL을 대체값으로,NVL2는 NULL 여부에 따라 서로 다른 두 값을,COALESCE는 여러 후보 중 첫 NULL 아닌 값을,NULLIF는 두 값이 같을 때 NULL을 반환한다.