이번 문서의 목표: 이 파일을 다 읽으면 07편의 기본 연산만으로는 표현하기 어려운 “모든 조건을 만족하는” 질의를 디비전 연산으로 풀고, 집계·그룹화 연산으로 통계 질의를 관계대수로 표현하며, 관계대수 수식으로 중첩 질의(부정·전칭 질의)를 만들고 이를 12·13편에서 배울 SQL 서브쿼리·집합 연산과 정확히 대응시킬 수 있게 된다.
07편 복습: 예제로 쓸 릴레이션
이 편은 07편의 세 릴레이션을 그대로 이어 쓴다. 07편에서 이미 선택()·투영()·집합 연산·조인을 다뤘으므로, 이 편에서는 그 위에 새로운 연산을 얹는다.
STUDENT(학번, 이름, 학년, 학과)
| 학번 | 이름 | 학년 | 학과 |
|---|---|---|---|
| S01 | 김민준 | 2 | CS |
| S02 | 이서연 | 3 | CS |
| S03 | 박도윤 | 4 | EE |
| S04 | 최지우 | 1 | CS |
COURSE(과목코드, 과목명, 학점)
| 과목코드 | 과목명 | 학점 |
|---|---|---|
| C01 | 데이터베이스 | 3 |
| C02 | 운영체제 | 3 |
| C03 | 자료구조 | 2 |
ENROLL(학번, 과목코드, 성적)
이 편에서는 디비전 연산 설명을 위해 ENROLL에 김민준(S01)의 3과목 수강 기록을 추가로 채운 확장판을 쓴다.
| 학번 | 과목코드 | 성적 |
|---|---|---|
| S01 | C01 | A |
| S01 | C02 | B |
| S01 | C03 | A |
| S02 | C01 | A |
| S02 | C02 | B |
| S03 | C03 | B |
디비전 연산: “모든 것을 만족하는” 질의
왜 필요한가
“C01, C02, C03을 모두 수강한 학생을 찾아라”처럼 “어떤 집합의 원소 전부와 관련된” 질의는 선택·투영·조인만으로 표현하기 매우 번거롭다. 이런 전칭(全稱, 모든 것에 대해 성립한다는 뜻) 조건을 위해 설계된 것이 디비전(division, 나눗셈) 연산이다.
쉽게 말하면: 디비전은 “이 학생이 저 과목 목록에 있는 과목을 빠짐없이 다 들었는가”를 한 번에 걸러내는 연산이다.
정의
릴레이션 을 릴레이션 로 나누는 디비전은 로 표기한다. 이때 는 를 포함하는 더 큰 속성 집합이고, (차집합)라고 하면 결과는 다음과 같이 정의된다.
- : 을 로 나눈 결과 릴레이션. 속성은 (에서 를 뺀 나머지)만 남는다.
- : 튜플 에서 속성 값만 꺼낸 것.
- : 의 모든 튜플 에 대해(전칭 기호 는 “포올(for all)“이라 읽으며 “모든 ~에 대해”라는 뜻이다.)
- 조건의 의미: 값 하나가 에 있는 모든 값과 짝지어져 에 존재해야 결과에 포함된다.
디비전은 07편에서 배운 기본 연산(선택·투영·합집합·교집합·차집합·카티전 곱)만으로도 아래처럼 유도할 수 있어, “기본 6개 연산으로 표현 가능한 파생 연산”으로 분류된다.
이 유도식은 “일단 의 모든 후보를 구한 뒤, 의 원소 중 하나라도 에서 짝을 못 찾는 후보를 걸러낸다”는 논리를 집합 연산으로 풀어낸 것이다. 시험에서는 이 유도식 자체를 외워 쓰기보다, 아래처럼 표를 직접 대조하는 절차로 계산하는 것이 실수가 적다.
계산·적용
“모든 과목(C01, C02, C03)을 수강한 학생의 학번을 구하라”는 질의를 디비전으로 표현한다. 먼저 두 입력 릴레이션을 준비한다.
은 ENROLL에서 학번·과목코드만 남긴 것이고(성적은 이 질의와 무관하므로 미리 투영으로 제거한다), 는 COURSE에 존재하는 모든 과목코드 {C01, C02, C03}이다.
- 에서 학번별로 어떤 과목코드를 갖는지 그룹으로 묶는다.
| 학번 | 수강한 과목코드 집합 |
|---|---|
| S01 | {C01, C02, C03} |
| S02 | {C01, C02} |
| S03 | {C03} |
- 각 학번의 집합이 를 부분집합으로 포함하는지(즉 와 같거나 를 포함하는지) 확인한다.
- S01:
{C01, C02, C03}은 와 정확히 일치 → 포함 조건 만족. - S02:
{C01, C02}는 의 C03이 빠져 있음 → 불만족. - S03:
{C03}은 C01, C02가 빠져 있음 → 불만족.
결과 릴레이션은 다음과 같다.
| 학번 |
|---|
| S01 |
결과 해석
디비전의 결과는 “S의 모든 원소와 완전히 짝지어지는 Y값”만 남기므로, S02처럼 일부만 만족하는 경우는 결과에서 완전히 배제된다. 이 점이 “하나라도 만족하면 포함”하는 선택 연산과 정반대의 성격을 가진다는 것이 디비전의 핵심이다.
자주 틀리는 함정: “S02도 C01, C02 두 과목이나 들었으니 부분적으로 결과에 포함되어야 한다”고 생각하는 것은 틀렸다. 디비전은 전부 아니면 전무(all-or-nothing) 연산이다. 에 속한 과목 중 단 하나라도 수강 기록이 없으면 그 학번은 결과에서 완전히 제외된다. 또한 가 비어 있는 경우(공집합)를 나누면 정의상 의 모든 값이 결과에 포함된다는 예외도 함께 기억해 둘 필요가 있다(전칭 명제는 대상이 없으면 항상 참이 되는 논리와 같은 원리이며, 05편의 논리식에서 다룬 공허하게 참인 명제와 같은 개념이다).
집계와 그룹화: 관계대수의 확장 연산
왜 필요한가
07편의 기본 6개 연산은 “튜플을 고르고, 속성을 고르고, 집합처럼 합치는” 일만 할 뿐, “학과별 평균 학년”이나 “과목별 수강 인원”처럼 여러 튜플을 하나의 숫자로 요약하는 일은 하지 못한다. 이를 위해 기본 연산에 포함되지 않는 확장 연산(extended operation)인 집계·그룹화 연산이 추가로 정의된다.
정의
일반화 투영을 포함한 집계 연산은 그리스 문자 감마 (스크립트 지(G), 이 책에서는 관례에 따라 로 표기한다)를 사용해 다음과 같이 쓴다.
- : 그룹을 나눌 기준이 되는 속성 목록(그룹화 속성, grouping attribute). 생략하면 릴레이션 전체를 한 그룹으로 본다.
- : 집계 연산을 나타내는 기호.
- : 적용할 집계 함수 목록.
COUNT,SUM,AVG,MIN,MAX등을 각 그룹에 적용한다. - : 연산을 적용할 대상 릴레이션.
계산·적용
“학과별 학생 수를 구하라”는 질의는 다음과 같이 쓴다.
- STUDENT를 학과 값이 같은 튜플끼리 그룹으로 나눈다: CS 그룹
{S01, S02, S04}, EE 그룹{S03}. - 각 그룹에
COUNT(학번)을 적용해 튜플 개수를 센다.
결과 릴레이션은 다음과 같다.
| 학과 | COUNT(학번) |
|---|---|
| CS | 3 |
| EE | 1 |
결과 해석
이 결과는 SQL의 GROUP BY 절과 정확히 대응한다(14편에서 SQL GROUP BY·HAVING을 다룰 때 이 관계대수 표현을 다시 참조한다). 그룹화 속성 가 결과 릴레이션의 속성이 되고, 각 집계 함수의 계산값이 나머지 속성이 되는 구조가 동일하다.
자주 틀리는 함정: 그룹화 속성 를 생략한 와, 에 학과를 넣은 의 결과 기수(튜플 개수)가 다르다는 점을 놓치기 쉽다. 전자는 전체를 한 그룹으로 보아 결과가 항상 1행이지만, 후자는 학과 개수만큼(이 예에서는 2행) 결과가 나온다.
관계대수로 중첩 질의 표현하기
왜 필요한가
“어떤 과목도 수강하지 않은 학생을 찾아라”처럼 “존재하지 않음”을 묻는 질의는 12·13편에서 SQL의 NOT IN·NOT EXISTS 서브쿼리로 다루지만, 그 이전에 관계대수로도 정확히 표현할 수 있어야 SQL 서브쿼리의 논리적 근거를 이해할 수 있다.
계산·적용: 부정 질의(수강하지 않은 학생)
관계대수에는 “존재하지 않는다”는 연산자가 따로 없지만, 차집합(07편)을 이용하면 정확히 같은 의미를 표현할 수 있다.
- : STUDENT의 전체 학번 목록 →
{S01, S02, S03, S04} - : 실제로 수강 기록이 있는 학번 목록 →
{S01, S02, S03} - 두 결과의 차집합을 구한다:
{S01, S02, S03, S04} - {S01, S02, S03} = {S04}
결과 릴레이션은 다음과 같다.
| 학번 |
|---|
| S04 |
결과 해석
“전체 학번에서 수강 기록이 있는 학번을 뺀 나머지”라는 논리가 곧 “수강하지 않은 학생”이며, 이것이 13편에서 배울 학번 NOT IN (SELECT 학번 FROM ENROLL)이 하는 일과 정확히 같다. 관계대수의 차집합 하나로 표현되는 이 질의가 SQL에서는 NOT IN이나 NOT EXISTS라는 서로 다른 두 가지 구문으로 번역될 수 있으며, 13편에서 다루듯 두 구문은 NULL 처리 방식이 다르다는 함정이 있다. 관계대수 자체는 이 값에 NULL이 섞이는 상황을 상정하지 않는 순수한 집합 모델이므로, 이 차이는 SQL 구현 단계에서 비로소 발생하는 문제라는 점을 기억해 둘 필요가 있다.
계산·적용: 전칭 질의를 디비전과 차집합 두 방식으로 비교하기
“모든 과목을 수강한 학생”이라는 앞선 디비전 예제는, 사실 차집합을 중첩해서도 똑같이 표현할 수 있다. “어떤 과목 하나라도 안 들은 학생을 찾아, 전체에서 빼면 모든 과목을 들은 학생만 남는다”는 논리다.
이 수식은 안쪽부터 “학생-과목의 모든 조합”(카티전 곱)에서 “실제로 수강한 조합”(ENROLL)을 뺀 “듣지 않은 조합”을 구하고, 그 조합에 등장하는 학번을 다시 전체에서 빼는 방식이다. 계산 결과는 앞의 디비전 예제와 동일하게 {S01}이 되어야 하지만(이 편에서 사용한 ENROLL 확장판 기준), 이 방식은 여러 단계의 중첩이 필요해 실수하기 쉽다.
자주 틀리는 함정: 위 두 방식(디비전 vs 이중 차집합)은 논리적으로 동등하지만, 시험에서 “디비전 연산의 정의”를 직접 묻는 문항에 이중 차집합 수식을 답으로 쓰면 틀릴 수 있다. “어떤 연산으로 표현하라”는 지시가 있으면 반드시 그 연산을 사용해야 하며, 논리적으로 같은 결과를 내는 다른 표현으로 대체하면 안 된다는 점에 유의해야 한다.
SQL 집합 연산과의 대응
정의
관계대수의 집합 연산(07편)과 이 편의 확장 연산은 SQL의 특정 구문과 다음과 같이 대응된다. 이 대응표는 12·13·14편에서 실제 SQL 문법을 배울 때 관계대수 개념을 그대로 재사용하기 위한 다리 역할을 한다.
| 관계대수 연산 | 대응하는 SQL 구문 | 비고 |
|---|---|---|
| (선택) | WHERE 절 | 07편 |
| (투영) | SELECT 목록 | 07편, 관계대수는 항상 중복 제거하지만 SQL은 DISTINCT 필요 |
| (합집합) | UNION | 관계대수는 항상 중복 제거, SQL은 UNION ALL로 중복 유지 선택 가능 |
| (교집합) | INTERSECT | 13편 |
| (차집합) | MINUS(Oracle 기준) | 13편, 표준 SQL은 EXCEPT |
| (카티전 곱) | CROSS JOIN 또는 콤마 조인 | 13편 |
| (자연 조인) | NATURAL JOIN 또는 INNER JOIN ... ON | 07·13편 |
| (그룹화·집계) | GROUP BY + 집계 함수 | 14편 |
| 형태의 부정 질의 | NOT IN / NOT EXISTS 서브쿼리 | 13편 |
| (디비전) | 이중 NOT EXISTS 서브쿼리(전형적으로 “모든 것에 대해” 패턴) | 13편에서 실제 SQL로 재구성 |
결과 해석
이 대응표에서 가장 눈여겨볼 지점은 투영과 합집합의 중복 제거 방식이 관계대수와 SQL에서 다르다는 것이다. 관계대수는 릴레이션을 수학적 집합으로 정의하므로 투영·합집합 모두 자동으로 중복을 제거하지만, SQL 테이블은 집합이 아니라 다중집합(multiset, 중복을 허용하는 집합)으로 구현되어 있어 명시적으로 DISTINCT를 쓰거나 UNION(중복 제거)과 UNION ALL(중복 유지)을 구분해서 선택해야 한다.
자주 틀리는 함정: “관계대수와 SQL은 완전히 같은 결과를 낸다”고 단정하는 것이다. 개념적으로는 대응하지만, 위에서 짚은 중복 제거 방식의 차이 때문에 같은 논리의 질의라도 실제 반환되는 행 수가 다를 수 있다. 특히 “관계대수의 투영 결과 기수를 구하라”는 문항과 “이에 대응하는 SQL SELECT 문의 실행 결과 행 수를 구하라”는 문항은 서로 다른 답이 나올 수 있으므로, 두 모델의 차이를 구분해서 풀어야 한다.
핵심 정리
- 디비전()은 “의 모든 원소와 빠짐없이 짝지어지는” 전칭 질의를 표현하는 연산으로, 하나라도 만족하지 못하면 결과에서 완전히 제외되는 전부 아니면 전무 방식이다.
- 디비전은 기본 6개 연산(특히 투영·카티전 곱·차집합)의 조합으로 유도할 수 있는 파생 연산이며, 시험에서 특정 연산을 지정하면 그 연산으로 표현해야 한다.
- 집계·그룹화 연산 은 그룹화 속성 로 튜플을 묶고 각 그룹에 집계 함수 를 적용하며, SQL의
GROUP BY와 대응한다. - “존재하지 않음”을 묻는 부정 질의는 관계대수에서 차집합으로 표현되며, 이는 SQL의
NOT IN·NOT EXISTS로 번역되지만NULL처리 방식은 SQL 단계에서만 발생하는 별도 문제다. - 관계대수는 릴레이션을 순수 집합으로 다뤄 투영·합집합에서 항상 중복을 제거하지만, SQL 테이블은 다중집합이라
DISTINCT·UNION ALL등을 명시해야 하므로 두 모델의 결과 행 수가 달라질 수 있다.