디스크의 사용자 데이터베이스와 시스템 카탈로그 접근을 맡는 구성 요소는 저장 데이터 관리자이다.
저장 데이터 관리자는 운영체제의 파일 관리 기능을 이용해 디스크의 데이터·카탈로그를 읽고 쓰며, 다른 모듈은 이를 거쳐서 저장 데이터에 접근한다.
- 질의 처리기: 사용자 질의를 해석·최적화해 실행 가능한 형태로 바꾸는 모듈로, 디스크 접근 자체를 책임지지 않는다.
- 트랜잭션 관리자: 무결성·권한 검사와 동시성 제어·회복을 담당한다.
- 런타임 데이터베이스 처리기: 컴파일된 질의를 실행하는 모듈이며, 실제 디스크 입출력은 저장 데이터 관리자에게 요청한다.
설계 순서는 요구 조건 분석 → 개념적 설계 → 논리적 설계 → 물리적 설계 → 구현이다.
개념적 설계에서 DBMS와 무관한 E-R 모델을 만들고, 논리적 설계에서 이를 관계 스키마 같은 특정 데이터 모델로 변환하며, 물리적 설계에서 저장 구조·인덱스·접근 경로를 정한다.
- 논리적 설계를 개념적 설계보다 앞에 둔 배열: 추상적인 개념 모델 없이 DBMS용 스키마를 먼저 만들 수 없다.
- 물리적 설계 다음에 개념적·논리적 설계를 둔 배열: 저장 구조는 논리 스키마가 정해진 뒤에야 결정할 수 있다.
- 물리적 설계 다음에 논리적·개념적 설계를 둔 배열: 설계 순서가 추상화 수준과 정반대로 뒤집혀 있다.
SELECT 사번, 월급
FROM 직원
ORDER BY 월급;
SELECT *
FROM 직원
WHERE 이름 LIKE '김%';
SELECT AVG(월급)
FROM 직원
WHERE 부서 = '인사팀';
SELECT SUM(부서, 월급)
FROM 직원
GROUP BY 부서;
옳지 않은 것은 SUM(부서, 월급)을 쓴 문장이다.
SUM 같은 집계 함수는 하나의 수치 속성(식)만 인자로 받으므로 두 속성을 넣으면 문법 오류이며, 부서별 합계는 SELECT 부서, SUM(월급) … GROUP BY 부서 형태로 써야 한다.
- 사번·월급을 월급 순으로 정렬하는 문장: ORDER BY를 사용한 정상 구문이다.
- 이름이 김으로 시작하는 직원 검색: LIKE '김%'는 와일드카드를 이용한 정상 패턴 검색이다.
- 인사팀 평균 월급: WHERE로 거른 뒤 AVG를 구하는 정상 구문이다.
ㄴ. read uncommitted
ㄷ. repeatable read
ㄹ. serializable
반복할 수 없는 읽기는 read uncommitted와 read committed에서 발생한다.
두 수준은 읽은 데이터를 트랜잭션 끝까지 보호하지 않아, 같은 행을 다시 읽는 사이 다른 트랜잭션이 수정·커밋하면 값이 달라진다.
- repeatable read: 읽은 행을 끝까지 보호하므로 반복 불가 읽기는 막고 팬텀 문제만 남는다.
- serializable: 오손 읽기·반복 불가 읽기·팬텀을 모두 막는 가장 엄격한 수준이다.

그림은 각 프로세서가 자기 전용 메모리를 가지면서 모든 디스크에는 공통 연결망으로 접근하는 구조이므로 공유 디스크(shared disk) 모델이다.
공유 디스크 구조에서는 메모리가 프로세서마다 분리되어 있어 메모리 버스 병목이 없고, 디스크는 공유되므로 한 프로세서가 고장 나도 다른 프로세서가 같은 데이터에 접근해 작업을 이어받을 수 있다.
- 비공유 모델: 각 노드가 프로세서·메모리·디스크를 모두 전용으로 가지며 노드끼리 연결망으로만 통신한다. 그림에서는 디스크가 공통 연결망에 묶여 공유되므로 해당하지 않는다.
- 공유 메모리 모델: 여러 프로세서가 하나의 공통 메모리를 함께 쓰는 구조인데, 그림의 메모리는 프로세서마다 하나씩 따로 붙어 있다.
- 공유 프로세서 모델: 병렬 데이터베이스의 표준 구조 모델(공유 메모리·공유 디스크·비공유·계층형)에 속하지 않는다.
ㄴ. 데이터 구조를 파악하기 위한 과정이 필요하다.
ㄷ. 데이터 내용 안에 구조 정보가 함께 저장된다.
반정형 데이터에 대한 옳은 설명은 ㄴ, ㄷ이다.
XML·JSON·HTML처럼 태그나 키로 구조 정보가 데이터 안에 함께 저장되며(ㄷ), 고정 스키마가 없으므로 이를 해석(파싱)해 구조를 파악하는 과정이 필요하다(ㄴ).
- ㄱ: 동영상·이미지·음성 파일은 구조 정보가 없는 비정형 데이터에 해당한다.
시스템 카탈로그에 저장되지 않는 것은 테이블에 저장된 레코드 값이다.
카탈로그는 데이터베이스 객체에 관한 정보, 즉 메타데이터를 저장하며 실제 사용자 데이터는 각 테이블에 저장된다.
- 속성의 데이터 타입: 스키마 정의 정보로 카탈로그에 저장된다.
- 각종 제약 조건: 기본키·외래키·CHECK 등의 정의가 카탈로그에 기록된다.
- 테이블·인덱스·뷰 이름: 객체 정의 정보로 카탈로그에 저장된다.
옳은 것은 map() 함수가 입력 레코드마다 0개 이상의 (키, 값) 쌍을 출력한다는 설명이다.
map()은 레코드 하나를 받아 조건에 따라 아무것도 내보내지 않거나 여러 쌍을 내보낼 수 있고, 이 출력이 키별로 묶여 reduce()의 입력이 된다.
- 병렬 처리 미지원: 맵리듀스와 HDFS는 데이터를 여러 노드에 나눠 병렬 처리하는 것이 핵심이다.
- reduce() 입력 키 타입이 다르다: reduce()는 map()이 낸 키로 묶인 값 목록을 받으므로 입력 키 타입은 map() 출력 키 타입과 같다.
- 메타데이터 서버가 데이터노드: 파일 블록 위치 등 메타데이터는 네임노드가 관리하고, 데이터노드는 실제 블록을 저장한다.
| 시간 ↓ | T1 | T2 | T3 |
|---|---|---|---|
| read(A) | |||
| read(B) | |||
| write(A) | |||
| read(B) | |||
| read(A) | |||
| write(B) | |||
| write(A) | |||
| write(B) |
충돌 연산이 아닌 것은 의 read(B)와 의 read(B), 즉 읽기끼리의 쌍이다.
두 연산이 충돌하려면 서로 다른 트랜잭션이 같은 데이터 항목에 접근하고 그중 하나 이상이 write여야 하며, 읽기끼리는 순서를 바꿔도 결과가 같다.
- 의 write(B)와 의 write(B): 같은 항목에 대한 쓰기-쓰기 충돌이다.
- 의 read(B)와 의 write(B): 같은 항목에 대한 읽기-쓰기 충돌이다.
- 의 write(A)와 의 read(A): 같은 항목에 대한 쓰기-읽기 충돌이다.
ㄴ. 조인 연산 최적화를 위한 해시 조인 알고리즘은 동등 조인(equijoin)에 적용할 수 없다.
ㄷ. 질의 처리는 관계 대수 표현식으로의 변환, 질의 최적화, 실행 계획 순으로 진행한다.
ㄹ. 두 테이블 중 한 개 또는 두 개가 정렬되어 있는 경우에는 정렬 합병 조인 알고리즘이 유용하다.
옳은 것은 ㄷ, ㄹ이다.
질의는 관계 대수식으로 변환된 뒤 최적화를 거쳐 실행 계획으로 수행되며(ㄷ), 입력이 이미 정렬돼 있으면 정렬 단계를 생략할 수 있어 정렬 합병 조인이 유리하다(ㄹ).
- ㄱ: 최적화 모듈은 통계 정보로 비용을 추정할 뿐 정확한 비용을 계산하지 않는다.
- ㄴ: 해시 조인은 같은 조인 값을 같은 버킷에 모으는 방식이므로 오히려 동등 조인에 적용되는 알고리즘이다.

시스템 장애 시점까지 완료(커밋)되지 못한 트랜잭션이 UNDO 대상이므로 T1과 T5가 취소되어야 한다.
체크포인트 회복에서는 체크포인트 이후의 로그만 검사해, 장애 전에 커밋된 트랜잭션은 REDO하고 장애 시점에 아직 실행 중인 트랜잭션은 UNDO한다. T1은 체크포인트 이전에 시작했지만 장애 시점까지 계속 실행 중이었고, T5는 체크포인트 이후 시작해 역시 장애 시점에 미완료 상태이다.
- T2: 체크포인트 이전에 이미 완료되어 변경 내용이 디스크에 반영되었으므로 아무 연산도 필요 없다.
- T3: 체크포인트 이전에 시작했으나 체크포인트 이후·장애 이전에 완료되었으므로 UNDO가 아니라 REDO 대상이다.
- T4: 체크포인트 이후 시작해 장애 이전에 완료되었으므로 REDO 대상이다.
| 선수 | ||||
|---|---|---|---|---|
| 선수번호 | 이름 | 포지션 | 점수 | 팀번호 |
| 2123 | 장영실 | CF | 30 | 2 |
| 3321 | 이순신 | RB | 12 | 1 |
| 2276 | 정약용 | LW | 6 | 1 |
| 1925 | 허준 | RW | 24 | 1 |
| 9842 | 김유신 | GK | 10 | 2 |
| 검색 결과 | ||
|---|---|---|
| 팀번호 | 합계점수 | 최대점수 |
| 2 | 40 | 30 |
SELECT 팀번호, SUM(점수) AS 합계점수, MAX(점수) AS
최대점수
FROM 선수
(가)
(가)에는 GROUP BY 팀번호 HAVING AVG(점수) >= 15;가 들어간다.
팀 2는 합계 30+10=40, 최대 30, 평균 20이고 팀 1은 합계 12+6+24=42, 최대 24, 평균 14이다.
따라서 팀 2만 남기는 조건이 필요하며 평균 15 이상 조건이 이를 만족한다.
- GROUP BY 팀번호만 쓴 경우: 팀 1(합계 42, 최대 24)도 함께 출력된다.
- GROUP BY 선수번호: 선수별로 묶여 행이 5개로 나뉘므로 팀 합계가 나오지 않는다.
- HAVING MAX(점수) >= 20: 팀 1의 최대점수 24도 조건을 만족해 두 팀이 모두 출력된다.
| 시간 ↓ | T1 | T2 |
|---|---|---|
| sum = sum + 0 | ||
| read(A) | ||
| sum = sum + A | ||
| read(X) | ||
| X = X − 100 | ||
| write(X) | ||
| read(X) | ||
| sum = sum + X | ||
| read(Y) | ||
| sum = sum + Y | ||
| read(Y) | ||
| Y = Y + 100 | ||
| write(Y) |
발생하는 문제는 부정확한 요약(incorrect summary)이다.
는 X를 이 100을 뺀 뒤에, Y는 이 100을 더하기 전에 읽어 합산하므로 합계가 실제 총액보다 100 적은 일관되지 않은 집계가 된다.
- 오손 읽기: 철회되어 무효가 된 갱신 값을 읽는 문제인데, 이 스케줄에는 의 철회가 없다.
- 갱신 손실: 두 트랜잭션이 같은 항목을 덮어써야 생기는데 는 쓰기를 하지 않는다.
- 팬텀 문제: 다른 트랜잭션의 삽입·삭제로 조건에 맞는 행 집합이 바뀌는 문제로, 여기에는 삽입·삭제가 없다.
○ 도서는 도서번호, 제목, 저자, 출판연도를 가지며, 도서번호를 기본키로 한다.
○ 대출은 대출번호, 회원번호, 도서번호, 대출일을 가지며, 대출번호를 기본키로 한다.
○ 반납은 반납번호, 대출번호, 반납일을 가지며, 반납번호를 기본키로 한다.
옳지 않은 것은 대출과 반납이 다대다 관계라는 설명이다.
반납은 대출번호를 외래키로 가지고 한 번의 대출은 한 번 반납되므로, 대출과 반납은 일대일(또는 일대다) 관계이지 다대다가 아니다.
- 주요 릴레이션 구성: 요구사항의 회원·도서·대출·반납이 각각 기본키를 가진 릴레이션이 된다.
- 도서와 반납의 일대다: 도서 하나가 여러 번 대출되고 대출마다 반납이 생기므로, 대출을 거쳐 보면 도서 하나에 여러 반납이 대응한다.
- 대출의 회원번호: 회원 릴레이션의 기본키를 참조하는 외래키이다.
RAID 1은 미러링으로 같은 데이터를 두 디스크에 복제해, 한 디스크가 고장 나도 미러 디스크에서 읽을 수 있다.
저장 효율은 낮지만 복구가 단순하고 읽기를 두 디스크로 분산할 수 있다.
- 해밍코드 사용: 비트 단위 분산과 해밍코드 오류 정정은 RAID 2의 특징이다.
- 교차 배치·복구 기능 없음: 스트라이핑만 하는 RAID 0의 특징이다.
- 블록 분할·패리티 복구: 블록 단위 스트라이핑에 패리티를 두는 RAID 4·5의 특징이다.
ㄴ. 분산 투명성에는 위치 투명성, 중복 투명성, 단편화 투명성이 포함된다.
ㄷ. 사용자는 원하는 데이터가 어느 지역에 저장되어 있는지를 명확하게 알 필요가 있다.
ㄹ. 한 릴레이션의 속성을 분할하여 두 개 이상의 서로 다른 장소에 저장하는 것은 수평적 단편화이다.
옳은 것은 ㄱ, ㄴ이다.
분산 데이터베이스의 트랜잭션은 한 사이트만 접근하는 지역 트랜잭션과 여러 사이트에 걸친 전역 트랜잭션으로 나뉘고(ㄱ), 분산 투명성에는 위치·중복·단편화 투명성 등이 포함된다(ㄴ).
- ㄷ: 위치 투명성 덕분에 사용자는 데이터가 저장된 지역을 알 필요가 없다.
- ㄹ: 속성(열) 단위로 나누는 것은 수직적 단편화이고, 수평적 단편화는 투플(행) 단위로 나눈다.

E2(A3, A4, RA1)
E2(A3, A4)
E2(A1, A3, A4)
E2(A1, A3, A4, RA1)
순환 관계 R2는 E1의 외래키 RA2로, 식별 관계 R1은 약한 개체 E2에 흡수되어 E1(A1, A2, RA2), E2(A1, A3, A4, RA1)이 되며 E2의 기본키는 (A1, A3)이다.
R2는 E1 자신과의 1:N 관계이므로 N쪽 튜플에 상위 E1의 키를 가리키는 외래키 RA2를 둔다. 이중 사각형 E2는 약한 개체이고 이중 마름모 R1은 식별 관계이므로, E2는 소유 개체 E1의 기본키 A1과 부분키 A3을 합쳐 기본키로 삼고, 1:M 관계의 속성 RA1도 M쪽인 E2로 옮긴다.
- RA2만 E1에 두고 RA1을 E2에 넣되 E2의 기본키를 A3만으로 잡은 스키마: 약한 개체는 소유 개체의 키 없이 식별될 수 없으므로 A1이 E2의 키에 포함되어야 한다.
- RA1·RA2를 모두 E1에 두고 E2를 (A3, A4)로만 구성한 스키마: E2가 E1을 참조할 외래키가 없어 R1 관계 자체가 표현되지 않는다.
- E2에 (A1, A3) 키를 두되 RA1을 E1에 둔 스키마: 1:M 관계의 속성은 M쪽에 두어야 각 관계 인스턴스마다 값을 가질 수 있으므로 RA1은 E2에 속해야 한다.
○ 밑줄 속성은 기본키이고 추천인아이디는 ‘회원’ 테이블의 아이디를 참조하는 외래키이다.
○ 추천인아이디를 제외한 나머지 속성은 NOT NULL로 정의된다.
○ 추천인이 없는 회원인 경우 추천인아이디 속성은 NULL로 저장된다.
SELECT e.이름, s.이름 AS 추천인이름
FROM 회원 e LEFT OUTER JOIN 회원 s
ON e.추천인아이디 = s.아이디;
SELECT e.이름, s.이름 AS 추천인이름
FROM 회원 e, 회원 s
WHERE e.추천인아이디 = s.아이디;
SELECT 이름, 추천인이름
FROM 회원 e
WHERE EXISTS
(SELECT 이름 AS 추천인이름
FROM 회원
WHERE e.아이디 = 추천인아이디);
SELECT e.이름, s.이름 AS 추천인이름
FROM 회원 e, 회원 s
WHERE e.추천인아이디 = s.아이디
AND e.추천인아이디 IS NOT NULL;
추천인이 없는 회원까지 출력하려면 회원 테이블을 자기 자신과 LEFT OUTER JOIN해야 한다.
e를 왼쪽에 두고 e.추천인아이디 = s.아이디로 외부 조인하면 짝이 없는 회원도 남고 s.이름은 NULL로 채워진다.
- FROM 회원 e, 회원 s의 동등 조인: 내부 조인이라 추천인아이디가 NULL인 회원이 결과에서 빠진다.
- EXISTS 서브쿼리: 추천인이름은 바깥 질의에서 참조할 수 없는 별칭이고, EXISTS는 존재 여부만 판정할 뿐 추천인 이름을 가져오지 못한다.
- IS NOT NULL 조건을 더한 동등 조인: 추천인이 없는 회원을 명시적으로 제외하므로 요구와 반대다.
| R1 | |
|---|---|
| A | B |
| a1 | b1 |
| a2 | b2 |
| a3 | b3 |
| R2 | ||
|---|---|---|
| B | C | D |
| b1 | c1 | d1 |
| b3 | c2 | d3 |
| b4 | c1 | d5 |
옳지 않은 것은 R2÷R1 연산에 대한 설명이다.
나눗셈 R2÷R1은 R1의 속성이 모두 R2에 포함될 때만 정의되는데, R1의 속성 A가 R2에 없으므로 연산 자체가 성립하지 않는다.
- R1∪R2 불가: 합집합은 차수와 도메인이 같은 릴레이션끼리만 가능한데 차수가 2와 3으로 다르다.
- R1.B가 b2인 선택 결과의 카디널리티: R1에서 b2 투플은 1개이고 R2를 B, D로 투영한 3개와 곱해지므로 3이다.
- 카티션 곱과 세타 조인의 차수: 세타 조인도 두 B 속성을 모두 유지하므로 둘 다 차수 5로 같다.
○ ‘EMP’와 ‘DEPT’ 테이블이 분산 저장되어 있다.
○ 사이트 1에 ‘EMP’ 테이블이 저장되며, SSN(10), NAME(30), DNO(5), ADDR(55)의 속성값으로 구성된 100 바이트 크기의 투플 10,000 개를 포함한다.
○ 사이트 2에 ‘DEPT’ 테이블이 저장되며, DNUM(5), DNAME(25), MGRSSN(10), DATE(10)의 속성값으로 구성된 50 바이트 크기의 투플 100 개를 포함한다.
가장 먼저 수행되는 연산은 DEPT의 조인 속성 DNUM 투영이다.
세미조인은 작은 릴레이션의 조인 속성만 상대 사이트로 보내 조인에 참여할 투플만 골라낸다.
DEPT는 투플 100개로 작아 DNUM 투영은 5바이트×100개=500바이트에 불과하므로, 이를 사이트 1로 보내 EMP 중 조인되는 투플만 걸러낸다.
- EMP의 DNO 투영: 투플 10,000개인 큰 EMP 쪽에서 시작하면 작은 DEPT로 큰 EMP를 줄이는 세미조인의 이점을 살릴 수 없다.
- EMP의 DNO·NAME 투영: 조인 속성 외의 NAME까지 보내 전송량이 커지므로 세미조인의 첫 단계가 아니다.
- DEPT의 DNUM·DNAME 투영: DNAME은 조인 판정에 필요 없어 첫 전송 대상에 넣을 이유가 없다.
옳은 것은 조인에 자주 쓰이는 속성은 인덱스로 적합하다는 설명이다.
조인·검색 조건에 자주 등장하는 속성에 인덱스를 두면 일치하는 투플을 빠르게 찾아 조인 비용이 줄어든다.
- 클러스터링 인덱스 2개 이상: 데이터 파일의 물리적 순서를 인덱스 키 순으로 정하므로 테이블당 하나만 만들 수 있다.
- 밀집 인덱스가 레코드 그룹당 엔트리: 밀집 인덱스는 레코드마다 엔트리를 두며, 블록(그룹)당 하나를 두는 것은 희소 인덱스다.
- 이진 탐색 트리가 가장 많이 활용: 디스크 접근 횟수를 줄이기 위해 B+ 트리가 가장 널리 쓰인다.
옳지 않은 것은 변경 사항을 즉시 디스크에 기록해 성능을 최적화한다는 설명이다.
로그 버퍼링은 로그 레코드를 메모리 버퍼에 모았다가 한꺼번에 출력해 디스크 출력 횟수를 줄이는 기법이므로, 즉시 기록은 그 반대이고 성능도 떨어뜨린다.
- 메모리에 오래 머문 로그의 손실 가능성: 버퍼는 휘발성이라 장애 시 아직 출력되지 않은 로그가 사라질 수 있다.
- 로그 우선 기록 규약 지원: 데이터 블록보다 관련 로그를 먼저 디스크에 기록해야 회복이 보장된다.
- 메모리 버퍼에 일시 저장해 성능 향상: 로그 버퍼링의 목적 그 자체를 설명한다.
CREATE VIEW 고객1(번호, 이름, 등급)
AS SELECT DISTINCT 고객번호, 고객이름, 등급
FROM 고객
WHERE 적립금 > 20000;
CREATE VIEW 고객2(고객이름, 등급, 적립금)
AS SELECT 고객이름, 등급, 적립금
FROM 고객
WHERE 직업 = '학생';
CREATE VIEW 고객3(등급, 평균나이)
AS SELECT 등급, AVG(나이) AS 평균나이
FROM 고객
GROUP BY 등급;
CREATE VIEW 고객4
AS SELECT 고객번호, 고객이름, 직업, 적립금
FROM 고객
WHERE 나이 >= 30 AND 등급 = 'GOLD';
데이터 삽입이 가능한 뷰는 고객4이다.
고객4는 단일 테이블에서 기본키 고객번호를 포함해 속성을 그대로 선택했으므로, 뷰에 없는 나이·등급은 NULL로 채워 기본 테이블에 삽입할 수 있다(WITH CHECK OPTION이 없어 WHERE 조건도 삽입을 막지 않는다).
- 고객1: DISTINCT를 사용해 뷰 행과 기본 테이블 행이 일대일로 대응하지 않아 삽입할 수 없다.
- 고객2: 기본키 고객번호가 빠져 있어 삽입하면 기본키가 NULL이 되므로 거부된다.
- 고객3: GROUP BY와 집계 함수로 만든 값이라 기본 테이블의 행에 대응시킬 수 없다.
| 고객 | ||
|---|---|---|
| 번호 | 이름 | 성별 |
| 1001 | 김홍도 | M |
| 1002 | 허균 | M |
| 1003 | 유관순 | F |
| 주문 | ||
|---|---|---|
| 고객번호 | 제품번호 | 개수 |
| 1001 | AA | 1 |
| 1003 | AA | 1 |
| 1001 | BA | 2 |
SELECT 이름, 제품번호, 개수
FROM 고객, 주문
WHERE 번호 = 고객번호
ORDER BY 이름, 제품번호;
SELECT 이름, 제품번호, 개수
FROM 고객 OUTER JOIN 주문
ON 번호 = 고객번호
ORDER BY 이름, 제품번호;
SELECT 이름, 제품번호, 개수
FROM 고객 RIGHT OUTER JOIN 주문
ON 번호 = 고객번호
ORDER BY 이름, 제품번호;
SELECT 이름, 제품번호, 개수
FROM 고객 LEFT OUTER JOIN 주문
ON 번호 = 고객번호
ORDER BY 이름, 제품번호;
SELECT 이름, 제품번호, 개수
FROM 고객, 주문
WHERE 번호 IN (SELECT 고객번호 FROM 주문)
ORDER BY 이름, 제품번호;
같은 결과를 내는 것은 RIGHT OUTER JOIN 문장이다.
원래 문장은 내부 조인으로 (김홍도, AA, 1), (김홍도, BA, 2), (유관순, AA, 1) 세 행을 낸다.
주문의 고객번호는 외래키라 모든 주문이 고객과 짝지어지므로, 주문 쪽을 보존하는 오른쪽 외부 조인도 추가 행 없이 같은 세 행이 된다.
- 방향 없는 OUTER JOIN: 표준 SQL은 LEFT·RIGHT·FULL을 지정해야 하며, FULL로 보더라도 주문 없는 허균 행이 추가된다.
- LEFT OUTER JOIN: 주문이 없는 허균이 제품번호·개수 NULL로 추가되어 네 행이 된다.
- WHERE 번호 IN 서브쿼리: 조인 조건 없이 카티션 곱을 거르므로 김홍도·유관순이 주문 3건 모두와 결합해 여섯 행이 된다.
| 부품상태 | ||||
|---|---|---|---|---|
| 상태번호 | 상태 | 재고위치 | 부품번호 | 수량 |
| S1 | 20 | 서울 | P1 | 300 |
| S1 | 20 | 서울 | P2 | 200 |
| S1 | 20 | 서울 | P3 | 400 |
| S1 | 20 | 서울 | P4 | 200 |
| S1 | 20 | 서울 | P5 | 100 |
| S1 | 20 | 서울 | P6 | 100 |
| S2 | 10 | 부산 | P1 | 300 |
| S2 | 10 | 부산 | P2 | 400 |
| S3 | 10 | 부산 | P2 | 200 |
| S4 | 20 | 서울 | P2 | 200 |
| S4 | 20 | 서울 | P4 | 300 |
| S4 | 20 | 서울 | P5 | 400 |
{부품번호, 수량} → {상태}
{상태번호, 부품번호} → {상태}
{상태번호, 상태} → {부품번호}
{상태번호, 부품번호} → {수량}
옳은 것은 {상태번호} → {상태, 재고위치}와 {상태번호, 부품번호} → {수량}이다.
S1과 S4는 항상 (20, 서울), S2와 S3은 (10, 부산)으로 상태번호가 같으면 상태·재고위치가 같고, 기본키 {상태번호, 부품번호}는 모든 속성을 결정하므로 수량도 결정한다.
- {부품번호, 수량} → {상태}: (P2, 200)이 S1에서는 상태 20, S3에서는 10이라 성립하지 않는다.
- {부품번호} → {상태번호}: P1이 S1과 S2에 모두 나타나 성립하지 않는다.
- {부품번호} → {수량}: P2의 수량이 200과 400으로 달라 성립하지 않는다.