모델을 보면 조인이 몇 번 필요한지, 어디까지가 한 트랜잭션인지, 어느 컬럼에서 합계가 틀어질지 미리 알 수 있습니다.
풀고 시작
문제 1. 어떤 컬럼에 값이 없는 행이 섞여 있을 때, 그 컬럼에 대한 SUM 함수와 COUNT 함수의 동작에 대한 설명으로 옳은 것은?
집계 함수는 값이 없는 행을 계산에서 빼고 처리합니다. SUM이 0으로 치환하는 것이 아니라 아예 제외하며, COUNT에 컬럼을 지정하면 그 컬럼에 값이 있는 행만 셉니다. 전체 행을 세려면 별표를 씁니다.
관계선 하나가 조인 조건 하나입니다
1과목이 2과목과 만나는 자리가 여기입니다. 모델에서 부모 엔터티의 식별자가 자식 엔터티로 상속되었기 때문에, 두 집합을 다시 이어 붙이는 일이 가능해집니다. 그것이 조인이죠. 즉 조인 조건은 모델의 관계선을 문법으로 옮겨 적은 것입니다.
그래서 모델을 읽는 사람은 쿼리를 쓰기 전에 조인 횟수를 셀 수 있습니다. 고객에서 주문으로, 주문에서 주문상세로, 주문상세에서 상품으로 내려가야 한다면 조인은 세 번입니다. 4강에서 식별 관계와 비식별 관계를 고르는 문제가 성능 이야기로 이어졌던 이유가 여기 있습니다. 비식별 관계를 계속 쓰면 손자 데이터를 조부모 조건으로 찾을 때 중간 세대를 모두 거쳐야 하지만, 식별 관계로 키가 상속되어 있으면 조부모의 키가 손자에게도 있어 한 번에 걸러집니다.
관계의 차수도 조인 결과를 예고합니다. 일대다 관계를 조인하면 결과 행 수는 다 쪽의 행 수를 따릅니다. 이 사실을 잊고 부모 쪽 금액을 합계하면 자식 건수만큼 부풀어 오른 숫자가 나옵니다. 실무에서 합계가 이상하게 큰 보고서의 대부분이 이 실수입니다.
필수와 선택, 그리고 외부 조인
관계의 선택성도 쿼리에 그대로 나타납니다. 자식이 부모에 필수로 참여한다면 부모 없는 자식은 존재하지 않으므로 그냥 조인해도 아무 행이 빠지지 않습니다. 그런데 선택 관계라면 부모가 없는 자식이나 자식이 없는 부모가 있을 수 있습니다. 이때 내부 조인을 쓰면 그 행들이 조용히 사라집니다.
주문이 없는 고객까지 포함한 고객 목록을 뽑아야 한다면 외부 조인이 필요합니다. 반대로 "모든 고객의 주문 건수"를 내부 조인으로 뽑아 놓고 주문이 한 건도 없는 고객이 목록에서 빠진 것을 눈치채지 못하는 사고가 흔하죠. 어떤 조인을 쓸지는 취향이 아니라 모델의 선택성이 정합니다.
모델이 표현하는 트랜잭션
트랜잭션은 코드에서 시작과 끝을 정하는 것처럼 보이지만, 그 범위의 근거는 모델에 이미 적혀 있습니다.
주문과 주문상세를 보죠. 주문상세는 주문 없이 존재할 수 없고, 주문도 상세가 하나도 없으면 의미가 없습니다. 두 엔터티가 필수 관계로 묶여 있다는 것은 둘이 함께 생기고 함께 사라져야 한다는 업무 규칙을 모델이 선언한 것입니다. 그러면 주문 한 건을 넣는 작업은 두 테이블에 걸친 하나의 트랜잭션이어야 합니다. 주문만 저장되고 상세가 실패했는데 그대로 커밋되면 모델이 선언한 규칙이 데이터에서 깨집니다.
반대로 선택 관계라면 사정이 다릅니다. 고객과 배송지의 관계가 선택이라면 고객만 먼저 등록하고 배송지는 나중에 추가해도 됩니다. 두 작업을 하나의 트랜잭션으로 묶을 필요가 없죠.
정리하면 이렇습니다. 필수 관계로 묶인 엔터티들의 변경은 한 트랜잭션으로 처리해야 하고, 선택 관계는 그럴 필요가 없습니다. 모델을 읽고 트랜잭션 경계를 그릴 수 있다는 것이 1과목이 노리는 실력입니다.
NULL, 값이 없다는 값
NULL은 0도 아니고 빈 문자열도 아니며, 아직 모르거나 해당하지 않는 상태입니다. 이 성질 때문에 규칙이 몇 가지 따라옵니다.
첫째, NULL이 들어간 산술 연산의 결과는 NULL입니다. 기본급에 수당을 더해 총액을 구하는데 수당이 비어 있으면 총액이 비어 버립니다. 그래서 값이 없을 때 0으로 바꿔 주는 함수를 씁니다. 둘째, NULL은 비교할 수 없습니다. 값이 없는지를 판단할 때 등호를 쓰면 참도 거짓도 아닌 결과가 나오므로 IS NULL을 써야 합니다. 셋째, 집계 함수는 NULL을 제외하고 계산합니다. SUM은 없는 값을 0으로 보지 않고 아예 빼며, 컬럼을 지정한 COUNT도 값이 있는 행만 셉니다. 그래서 평균을 낼 때 분모가 예상과 달라지는 일이 생깁니다.
모델링 관점에서 더 중요한 것은 NULL이 생기는 자리를 설계가 결정한다는 사실입니다. 선택 관계로 내려온 외래 식별자는 값이 없을 수 있고, 여러 유형을 한 테이블에 몰아넣으면 유형별로 쓰지 않는 컬럼이 전부 비게 됩니다. NULL이 많다는 것은 대개 한 집합에 성격이 다른 것들이 섞여 있다는 신호입니다. 정규화가 필요한 자리를 NULL이 알려 주는 셈이죠.
여기까지가 1과목입니다. 다음 과목부터는 이 모델을 실제로 물어보는 언어, SQL로 들어갑니다.
인출 문제
문제 1. 일대다 관계인 주문과 주문상세를 조인한 뒤 주문금액을 합계했더니 실제보다 큰 값이 나왔습니다. 원인으로 가장 적절한 것은?
일대다 조인의 결과 행 수는 다 쪽을 따르므로 부모 쪽 금액이 자식 건수만큼 반복됩니다. 이 상태에서 그대로 합계하면 값이 부풀어 오릅니다. 부모 기준 금액은 부모 집합에서 먼저 집계하거나 중복을 제거한 뒤 더해야 합니다.
문제 2. 주문이 한 건도 없는 고객까지 포함해 고객별 주문 건수를 구해야 합니다. 모델에서 확인해야 할 정보와 선택할 조인은?
자식이 없을 수 있는 선택 관계라면 내부 조인은 그 부모 행을 결과에서 떨어뜨립니다. 없는 쪽까지 남겨야 하므로 외부 조인이 필요하고, 그 판단의 근거는 모델의 선택성입니다.
문제 3. 필수 관계로 묶인 주문과 주문상세를 저장하는 작업에 대한 설명으로 가장 적절한 것은?
필수 관계는 둘이 함께 존재해야 한다는 규칙의 선언입니다. 따로 커밋하면 주문만 남고 상세가 없는 상태가 데이터에 남아 규칙이 깨집니다. 트랜잭션 경계의 근거는 모델에 이미 적혀 있습니다.
문제 4. NULL에 대한 설명으로 옳지 않은 것은?
NULL은 값이 아니라 상태이므로 등호로 비교할 수 없고 IS NULL을 써야 합니다. 등호를 쓰면 참도 거짓도 아닌 결과가 되어 조건에 걸리지 않습니다.
생각해볼 질문 (정답 없음)
어떤 컬럼에 NULL이 유난히 많다면 그것은 데이터가 덜 쌓인 탓일까요, 아니면 한 테이블에 서로 다른 성격의 집합을 몰아넣은 탓일까요. 둘을 구별하려면 무엇을 확인해야 할까요.
더 풀기
출제기준의 세세항목을 따라 이 강의 범위에서 새로 낸 문제입니다. 모의고사도 여기에서 뽑습니다.
묶음 1
문제 1. 모델의 관계선이 SQL에서 무엇으로 나타나는지 가장 잘 설명한 것은?
관계선 하나가 대개 조인 조건 하나가 됩니다. 관계가 없는 두 테이블을 이으려면 우회 경로를 찾아야 하고, 그것을 못 찾으면 카티션 곱이 됩니다.
문제 2. 모델에서 두 엔터티 사이의 경로가 여러 개일 때 SQL을 쓰는 사람이 겪는 어려움으로 가장 알맞은 것은?
경로가 다르면 결과의 의미가 달라집니다. 예를 들어 주문을 통해 고객에 닿는 것과 배송을 통해 닿는 것은 서로 다른 집합을 만듭니다.
문제 3. 관계가 필수인 쪽과 선택인 쪽이 조인에 미치는 영향으로 가장 알맞은 것은?
부서가 없는 사원이 있을 수 있다면 내부 조인으로 사원과 부서를 이을 때 그 사원이 빠집니다. 이럴 때 외부 조인이 필요합니다.
문제 4. 부서에 소속된 사원이 한 명도 없을 수 있는 상황에서 모든 부서와 그 부서의 사원 수를 구하려면?
내부 조인을 쓰면 사원이 없는 부서가 결과에서 사라집니다. 부서를 보존해야 하므로 부서 쪽을 남기는 외부 조인이 필요합니다.
문제 5. 사원 열 명이 있고 그중 두 명은 부서코드가 비어 있습니다. 사원과 부서를 부서코드로 내부 조인하면 결과 행 수는?
내부 조인은 양쪽에서 조건이 참인 행만 남깁니다. 부서코드가 NULL이면 어떤 부서와도 같지 않으므로 두 행이 빠져 여덟 행이 됩니다.
문제 6. 같은 상황에서 사원을 기준으로 왼쪽 외부 조인을 하면 결과 행 수는?
왼쪽 외부 조인은 기준 테이블의 모든 행을 남깁니다. 부서코드가 비어 있는 두 명도 부서 쪽 컬럼이 NULL인 채로 남아 열 행이 됩니다.
문제 7. 부서가 다섯 개 있고 사원이 스무 명 있는데 조인 조건을 빠뜨리고 두 테이블을 함께 조회하면 결과 행 수는?
조인 조건이 없으면 모든 조합이 만들어집니다. 다섯 곱하기 스물이므로 백 행입니다. 이것이 카티션 곱입니다.
문제 8. 모델에서 다대다 관계가 관계 엔터티로 풀렸다면 SQL에서 두 원래 엔터티를 잇기 위해 필요한 것은?
다대다는 중간 테이블을 통해서만 이어집니다. 학생과 과목을 이으려면 수강 테이블을 반드시 거쳐야 합니다.
문제 9. 식별 관계로 설계된 부모와 자식을 조인할 때의 특징으로 가장 알맞은 것은?
식별 관계에서는 부모 키가 자식 키의 일부이므로 조인 조건이 자연스럽게 키 컬럼으로 표현됩니다. 그래서 인덱스를 잘 타는 경우가 많습니다.
문제 10. 계층 구조를 가진 조직 데이터를 조회할 때 모델에서 확인해야 할 것으로 가장 알맞은 것은?
계층 질의를 쓰려면 부모를 가리키는 속성이 있어야 하고 최상위의 표현 규칙을 알아야 시작 조건을 쓸 수 있습니다.
묶음 2
문제 1. 트랜잭션을 가장 잘 설명한 것은?
트랜잭션의 기준은 문장 수가 아니라 업무의 단위입니다. 계좌 이체는 출금과 입금 두 문장이지만 하나의 트랜잭션입니다.
문제 2. 모델이 트랜잭션을 표현한다는 말의 뜻으로 가장 알맞은 것은?
주문과 주문 상세가 양쪽 모두 필수 관계라면 둘은 함께 생기고 함께 사라져야 합니다. 그 규칙이 곧 트랜잭션 범위의 실마리입니다.
문제 3. 주문과 주문 상세가 양쪽 모두 필수인 관계라면 어떤 규칙이 성립하나요?
양쪽 필수는 서로 없이는 존재할 수 없다는 뜻입니다. 상세가 없는 주문이 허용된다면 그 관계는 주문 쪽에서 선택이어야 합니다.
문제 4. 트랜잭션 범위를 모델에서 읽지 못하고 프로그램에만 맡겼을 때 생기는 문제로 가장 알맞은 것은?
어디까지가 한 덩어리인지 구조에 드러나 있지 않으면 개발자마다 다르게 판단합니다. 그 틈에서 주문만 있고 상세가 없는 데이터가 생깁니다.
문제 5. NULL이 뜻하는 것으로 가장 정확한 것은?
NULL은 값이 아니라 값이 없다는 표시입니다. 영이나 빈 문자열은 엄연히 값이므로 NULL과 다르게 취급됩니다.
문제 6. 값이 십과 이십과 NULL인 세 행에 대해 합계를 구하면 결과는?
집계 함수는 NULL을 제외하고 계산합니다. 십과 이십만 더해 삼십이 됩니다.
문제 7. 값이 십과 이십과 NULL인 세 행에 대해 평균을 구하면 결과는?
평균도 NULL을 뺀 나머지로 계산합니다. 합 삼십을 개수 둘로 나누어 십오가 됩니다. 셋으로 나눈 십이 아닙니다.
문제 8. 값이 십과 이십과 NULL인 세 행에 대해 행 수를 세는 방식으로 전체 행 수를 세면 결과는?
별표를 세는 방식은 행 자체를 세므로 셋입니다. 특정 컬럼을 지정해 세면 NULL을 제외해 둘이 됩니다.
문제 9. NULL에 숫자 십을 더하면 결과는?
알 수 없는 값에 무엇을 더해도 결과는 여전히 알 수 없습니다. 산술 연산에 NULL이 끼면 결과는 NULL입니다.
문제 10. NULL인지 확인하려 할 때 등호 비교를 쓰면 안 되는 이유로 가장 알맞은 것은?
NULL과의 비교는 참이 아니라 알 수 없음을 냅니다. 그래서 IS NULL이라는 전용 표현을 씁니다.
묶음 3
문제 1. 사원 테이블에 급여가 NULL인 행이 세 건 있고 나머지 일곱 건은 값이 있습니다. 급여가 백보다 크지 않은 행을 조건으로 걸면 NULL인 세 건은 어떻게 되나요?
부정 조건이라도 NULL과의 비교는 알 수 없음입니다. NULL 행을 포함하려면 IS NULL 조건을 따로 붙여야 합니다.
문제 2. 선택 관계로 설계된 외래키 컬럼에 나타날 수 있는 값의 상태는?
선택 관계는 관계가 없을 수 있다는 뜻이고 그것을 NULL로 표현합니다. 값이 있다면 반드시 부모에 존재해야 참조 무결성이 지켜집니다.
문제 3. 필수 관계인 외래키 컬럼에 NULL을 허용하면 생기는 문제로 가장 알맞은 것은?
필수라는 규칙이 구조로 지켜지지 않으면 프로그램의 실수 하나로 고아 데이터가 생깁니다. 필수 관계는 NOT NULL로 못 박아야 합니다.
문제 4. 두 개의 NULL을 서로 같은지 비교하면 결과는?
알 수 없는 값 둘이 같은지도 알 수 없습니다. 그래서 NULL 두 개는 등호로 같다고 판정되지 않습니다.
문제 5. 이력을 관리하는 엔터티에서 종료일자를 NULL로 두어 현재 유효한 행을 표현하는 방식의 장점으로 가장 알맞은 것은?
현재를 나타내는 특별한 표시가 있으면 조회가 단순해집니다. 다만 기간 비교를 할 때는 NULL 처리를 별도로 해 주어야 합니다.
문제 6. 이력 관리에서 종료일자를 NULL 대신 아주 먼 미래 날짜로 채워 두는 방식의 장점은?
NULL이 없으면 조건이 단순해지고 인덱스도 잘 탑니다. 대신 그 날짜가 실제 값이 아니라 약속이라는 사실을 문서로 남겨야 합니다.
문제 7. 트랜잭션의 원자성을 가장 잘 설명한 것은?
원자성은 전부 아니면 전무를 뜻합니다. 나머지는 각각 일관성과 고립성과 지속성의 설명입니다.
문제 8. 모델링 단계에서 데이터 무결성을 구조로 표현하는 수단으로 보기 어려운 것은?
구조로 표현하는 수단은 키와 제약과 관계입니다. 프로그램 검증은 필요하지만 데이터베이스 밖에 있어 우회될 수 있습니다.
문제 9. 배송 정보는 주문이 완료된 뒤에만 생긴다는 규칙을 모델에 표현하는 가장 알맞은 방법은?
배송 쪽에서 본 관계는 필수이고 주문 쪽에서 본 관계는 선택입니다. 이렇게 방향별 선택성을 정하면 규칙이 구조에 담깁니다.
문제 10. 다음 중 NULL을 다룰 때 가장 안전한 습관은?
영이나 공백으로 대체하면 값이 없다는 사실과 값이 영이라는 사실을 구분할 수 없게 됩니다. NULL을 쓰되 허용 범위를 좁히는 것이 안전합니다.