Skip to Content
독학사독학사 4단계데이터베이스06. 관계대수 확장 연산·중첩 질의·집합 연산

이번 문서의 목표: 이 파일을 다 읽으면 07편의 기본 연산만으로는 표현하기 어려운 “모든 조건을 만족하는” 질의를 디비전 연산으로 풀고, 집계·그룹화 연산으로 통계 질의를 관계대수로 표현하며, 관계대수 수식으로 중첩 질의(부정·전칭 질의)를 만들고 이를 12·13편에서 배울 SQL 서브쿼리·집합 연산과 정확히 대응시킬 수 있게 된다.

07편 복습: 예제로 쓸 릴레이션

이 편은 07편의 세 릴레이션을 그대로 이어 쓴다. 07편에서 이미 선택(σ\sigma)·투영(π\pi)·집합 연산·조인을 다뤘으므로, 이 편에서는 그 위에 새로운 연산을 얹는다.

STUDENT(학번, 이름, 학년, 학과)

학번이름학년학과
S01김민준2CS
S02이서연3CS
S03박도윤4EE
S04최지우1CS

COURSE(과목코드, 과목명, 학점)

과목코드과목명학점
C01데이터베이스3
C02운영체제3
C03자료구조2

ENROLL(학번, 과목코드, 성적)

이 편에서는 디비전 연산 설명을 위해 ENROLL에 김민준(S01)의 3과목 수강 기록을 추가로 채운 확장판을 쓴다.

학번과목코드성적
S01C01A
S01C02B
S01C03A
S02C01A
S02C02B
S03C03B

디비전 연산: “모든 것을 만족하는” 질의

왜 필요한가

“C01, C02, C03을 모두 수강한 학생을 찾아라”처럼 “어떤 집합의 원소 전부와 관련된” 질의는 선택·투영·조인만으로 표현하기 매우 번거롭다. 이런 전칭(全稱, 모든 것에 대해 성립한다는 뜻) 조건을 위해 설계된 것이 디비전(division, 나눗셈) 연산이다.

쉽게 말하면: 디비전은 “이 학생이 저 과목 목록에 있는 과목을 빠짐없이 다 들었는가”를 한 번에 걸러내는 연산이다.

정의

릴레이션 R(Z)R(Z)을 릴레이션 S(X)S(X)로 나누는 디비전은 R÷SR \div S로 표기한다. 이때 ZZXX를 포함하는 더 큰 속성 집합이고, Y=ZXY = Z - X(차집합)라고 하면 결과는 다음과 같이 정의된다.

R÷S={t[Y]tR  sS, (t[Y],s)R}R \div S = \{ t[Y] \mid t \in R \ \land \ \forall s \in S,\ (t[Y], s) \in R \}
  • R÷SR \div S: RRSS로 나눈 결과 릴레이션. 속성은 YY(ZZ에서 XX를 뺀 나머지)만 남는다.
  • t[Y]t[Y]: 튜플 tt에서 YY 속성 값만 꺼낸 것.
  • sS\forall s \in S: SS의 모든 튜플 ss에 대해(전칭 기호 \forall는 “포올(for all)“이라 읽으며 “모든 ~에 대해”라는 뜻이다.)
  • 조건의 의미: t[Y]t[Y] 값 하나가 SS에 있는 모든 값과 짝지어져 RR에 존재해야 결과에 포함된다.

디비전은 07편에서 배운 기본 연산(선택·투영·합집합·교집합·차집합·카티전 곱)만으로도 아래처럼 유도할 수 있어, “기본 6개 연산으로 표현 가능한 파생 연산”으로 분류된다.

R÷S=πY(R)πY((πY(R)×S)R)R \div S = \pi_Y(R) - \pi_Y((\pi_Y(R) \times S) - R)

이 유도식은 “일단 YY의 모든 후보를 구한 뒤, SS의 원소 중 하나라도 RR에서 짝을 못 찾는 후보를 걸러낸다”는 논리를 집합 연산으로 풀어낸 것이다. 시험에서는 이 유도식 자체를 외워 쓰기보다, 아래처럼 표를 직접 대조하는 절차로 계산하는 것이 실수가 적다.

계산·적용

“모든 과목(C01, C02, C03)을 수강한 학생의 학번을 구하라”는 질의를 디비전으로 표현한다. 먼저 두 입력 릴레이션을 준비한다.

R=π학번,과목코드(ENROLL)R = \pi_{학번, 과목코드}(ENROLL) S=π과목코드(COURSE)S = \pi_{과목코드}(COURSE)

RR은 ENROLL에서 학번·과목코드만 남긴 것이고(성적은 이 질의와 무관하므로 미리 투영으로 제거한다), SS는 COURSE에 존재하는 모든 과목코드 {C01, C02, C03}이다.

결과=R÷S결과 = R \div S
  1. RR에서 학번별로 어떤 과목코드를 갖는지 그룹으로 묶는다.
학번수강한 과목코드 집합
S01{C01, C02, C03}
S02{C01, C02}
S03{C03}
  1. 각 학번의 집합이 S={C01,C02,C03}S = \{C01, C02, C03\}부분집합으로 포함하는지(즉 SS와 같거나 SS를 포함하는지) 확인한다.
  2. S01: {C01, C02, C03}SS와 정확히 일치 → 포함 조건 만족.
  3. S02: {C01, C02}SS의 C03이 빠져 있음 → 불만족.
  4. S03: {C03}은 C01, C02가 빠져 있음 → 불만족.

결과 릴레이션은 다음과 같다.

학번
S01

결과 해석

디비전의 결과는 “S의 모든 원소와 완전히 짝지어지는 Y값”만 남기므로, S02처럼 일부만 만족하는 경우는 결과에서 완전히 배제된다. 이 점이 “하나라도 만족하면 포함”하는 선택 연산과 정반대의 성격을 가진다는 것이 디비전의 핵심이다.

자주 틀리는 함정: “S02도 C01, C02 두 과목이나 들었으니 부분적으로 결과에 포함되어야 한다”고 생각하는 것은 틀렸다. 디비전은 전부 아니면 전무(all-or-nothing) 연산이다. SS에 속한 과목 중 단 하나라도 수강 기록이 없으면 그 학번은 결과에서 완전히 제외된다. 또한 SS가 비어 있는 경우(공집합)를 나누면 정의상 RR의 모든 YY값이 결과에 포함된다는 예외도 함께 기억해 둘 필요가 있다(전칭 명제는 대상이 없으면 항상 참이 되는 논리와 같은 원리이며, 05편의 논리식에서 다룬 공허하게 참인 명제와 같은 개념이다).

집계와 그룹화: 관계대수의 확장 연산

왜 필요한가

07편의 기본 6개 연산은 “튜플을 고르고, 속성을 고르고, 집합처럼 합치는” 일만 할 뿐, “학과별 평균 학년”이나 “과목별 수강 인원”처럼 여러 튜플을 하나의 숫자로 요약하는 일은 하지 못한다. 이를 위해 기본 연산에 포함되지 않는 확장 연산(extended operation)인 집계·그룹화 연산이 추가로 정의된다.

정의

일반화 투영을 포함한 집계 연산은 그리스 문자 감마 G\mathcal{G}(스크립트 지(G), 이 책에서는 관례에 따라 G\mathcal{G}로 표기한다)를 사용해 다음과 같이 쓴다.

GGF(R){}_{G}\mathcal{G}_{F}(R)
  • GG: 그룹을 나눌 기준이 되는 속성 목록(그룹화 속성, grouping attribute). 생략하면 릴레이션 전체를 한 그룹으로 본다.
  • G\mathcal{G}: 집계 연산을 나타내는 기호.
  • FF: 적용할 집계 함수 목록. COUNT, SUM, AVG, MIN, MAX 등을 각 그룹에 적용한다.
  • RR: 연산을 적용할 대상 릴레이션.

계산·적용

“학과별 학생 수를 구하라”는 질의는 다음과 같이 쓴다.

학과GCOUNT(학번)(STUDENT){}_{학과}\mathcal{G}_{COUNT(학번)}(STUDENT)
  1. STUDENT를 학과 값이 같은 튜플끼리 그룹으로 나눈다: CS 그룹 {S01, S02, S04}, EE 그룹 {S03}.
  2. 각 그룹에 COUNT(학번)을 적용해 튜플 개수를 센다.

결과 릴레이션은 다음과 같다.

학과COUNT(학번)
CS3
EE1

결과 해석

이 결과는 SQL의 GROUP BY 절과 정확히 대응한다(14편에서 SQL GROUP BY·HAVING을 다룰 때 이 관계대수 표현을 다시 참조한다). 그룹화 속성 GG가 결과 릴레이션의 속성이 되고, 각 집계 함수의 계산값이 나머지 속성이 되는 구조가 동일하다.

자주 틀리는 함정: 그룹화 속성 GG를 생략한 GAVG(학년)(STUDENT){}_{}\mathcal{G}_{AVG(학년)}(STUDENT)와, GG에 학과를 넣은 학과GAVG(학년)(STUDENT){}_{학과}\mathcal{G}_{AVG(학년)}(STUDENT)의 결과 기수(튜플 개수)가 다르다는 점을 놓치기 쉽다. 전자는 전체를 한 그룹으로 보아 결과가 항상 1행이지만, 후자는 학과 개수만큼(이 예에서는 2행) 결과가 나온다.

관계대수로 중첩 질의 표현하기

왜 필요한가

“어떤 과목도 수강하지 않은 학생을 찾아라”처럼 “존재하지 않음”을 묻는 질의는 12·13편에서 SQL의 NOT IN·NOT EXISTS 서브쿼리로 다루지만, 그 이전에 관계대수로도 정확히 표현할 수 있어야 SQL 서브쿼리의 논리적 근거를 이해할 수 있다.

계산·적용: 부정 질의(수강하지 않은 학생)

관계대수에는 “존재하지 않는다”는 연산자가 따로 없지만, 차집합(07편)을 이용하면 정확히 같은 의미를 표현할 수 있다.

π학번(STUDENT)π학번(ENROLL)\pi_{학번}(STUDENT) - \pi_{학번}(ENROLL)
  1. π학번(STUDENT)\pi_{학번}(STUDENT): STUDENT의 전체 학번 목록 → {S01, S02, S03, S04}
  2. π학번(ENROLL)\pi_{학번}(ENROLL): 실제로 수강 기록이 있는 학번 목록 → {S01, S02, S03}
  3. 두 결과의 차집합을 구한다: {S01, S02, S03, S04} - {S01, S02, S03} = {S04}

결과 릴레이션은 다음과 같다.

학번
S04

결과 해석

“전체 학번에서 수강 기록이 있는 학번을 뺀 나머지”라는 논리가 곧 “수강하지 않은 학생”이며, 이것이 13편에서 배울 학번 NOT IN (SELECT 학번 FROM ENROLL)이 하는 일과 정확히 같다. 관계대수의 차집합 하나로 표현되는 이 질의가 SQL에서는 NOT IN이나 NOT EXISTS라는 서로 다른 두 가지 구문으로 번역될 수 있으며, 13편에서 다루듯 두 구문은 NULL 처리 방식이 다르다는 함정이 있다. 관계대수 자체는 이 값에 NULL이 섞이는 상황을 상정하지 않는 순수한 집합 모델이므로, 이 차이는 SQL 구현 단계에서 비로소 발생하는 문제라는 점을 기억해 둘 필요가 있다.

계산·적용: 전칭 질의를 디비전과 차집합 두 방식으로 비교하기

“모든 과목을 수강한 학생”이라는 앞선 디비전 예제는, 사실 차집합을 중첩해서도 똑같이 표현할 수 있다. “어떤 과목 하나라도 안 들은 학생을 찾아, 전체에서 빼면 모든 과목을 들은 학생만 남는다”는 논리다.

π학번(STUDENT)π학번((π학번(STUDENT)×π과목코드(COURSE))π학번,과목코드(ENROLL))\pi_{학번}(STUDENT) - \pi_{학번}((\pi_{학번}(STUDENT) \times \pi_{과목코드}(COURSE)) - \pi_{학번, 과목코드}(ENROLL))

이 수식은 안쪽부터 “학생-과목의 모든 조합”(카티전 곱)에서 “실제로 수강한 조합”(ENROLL)을 뺀 “듣지 않은 조합”을 구하고, 그 조합에 등장하는 학번을 다시 전체에서 빼는 방식이다. 계산 결과는 앞의 디비전 예제와 동일하게 {S01}이 되어야 하지만(이 편에서 사용한 ENROLL 확장판 기준), 이 방식은 여러 단계의 중첩이 필요해 실수하기 쉽다.

자주 틀리는 함정: 위 두 방식(디비전 vs 이중 차집합)은 논리적으로 동등하지만, 시험에서 “디비전 연산의 정의”를 직접 묻는 문항에 이중 차집합 수식을 답으로 쓰면 틀릴 수 있다. “어떤 연산으로 표현하라”는 지시가 있으면 반드시 그 연산을 사용해야 하며, 논리적으로 같은 결과를 내는 다른 표현으로 대체하면 안 된다는 점에 유의해야 한다.

SQL 집합 연산과의 대응

정의

관계대수의 집합 연산(07편)과 이 편의 확장 연산은 SQL의 특정 구문과 다음과 같이 대응된다. 이 대응표는 12·13·14편에서 실제 SQL 문법을 배울 때 관계대수 개념을 그대로 재사용하기 위한 다리 역할을 한다.

관계대수 연산대응하는 SQL 구문비고
σ\sigma(선택)WHERE07편
π\pi(투영)SELECT 목록07편, 관계대수는 항상 중복 제거하지만 SQL은 DISTINCT 필요
\cup(합집합)UNION관계대수는 항상 중복 제거, SQL은 UNION ALL로 중복 유지 선택 가능
\cap(교집합)INTERSECT13편
-(차집합)MINUS(Oracle 기준)13편, 표준 SQL은 EXCEPT
×\times(카티전 곱)CROSS JOIN 또는 콤마 조인13편
\Join(자연 조인)NATURAL JOIN 또는 INNER JOIN ... ON07·13편
GGF{}_{G}\mathcal{G}_{F}(그룹화·집계)GROUP BY + 집계 함수14편
R()R - (\ldots) 형태의 부정 질의NOT IN / NOT EXISTS 서브쿼리13편
R÷SR \div S(디비전)이중 NOT EXISTS 서브쿼리(전형적으로 “모든 것에 대해” 패턴)13편에서 실제 SQL로 재구성

결과 해석

이 대응표에서 가장 눈여겨볼 지점은 투영과 합집합의 중복 제거 방식이 관계대수와 SQL에서 다르다는 것이다. 관계대수는 릴레이션을 수학적 집합으로 정의하므로 투영·합집합 모두 자동으로 중복을 제거하지만, SQL 테이블은 집합이 아니라 다중집합(multiset, 중복을 허용하는 집합)으로 구현되어 있어 명시적으로 DISTINCT를 쓰거나 UNION(중복 제거)과 UNION ALL(중복 유지)을 구분해서 선택해야 한다.

자주 틀리는 함정: “관계대수와 SQL은 완전히 같은 결과를 낸다”고 단정하는 것이다. 개념적으로는 대응하지만, 위에서 짚은 중복 제거 방식의 차이 때문에 같은 논리의 질의라도 실제 반환되는 행 수가 다를 수 있다. 특히 “관계대수의 투영 결과 기수를 구하라”는 문항과 “이에 대응하는 SQL SELECT 문의 실행 결과 행 수를 구하라”는 문항은 서로 다른 답이 나올 수 있으므로, 두 모델의 차이를 구분해서 풀어야 한다.

핵심 정리

  • 디비전(R÷SR \div S)은 “SS의 모든 원소와 빠짐없이 짝지어지는” 전칭 질의를 표현하는 연산으로, 하나라도 만족하지 못하면 결과에서 완전히 제외되는 전부 아니면 전무 방식이다.
  • 디비전은 기본 6개 연산(특히 투영·카티전 곱·차집합)의 조합으로 유도할 수 있는 파생 연산이며, 시험에서 특정 연산을 지정하면 그 연산으로 표현해야 한다.
  • 집계·그룹화 연산 GGF(R){}_{G}\mathcal{G}_{F}(R)은 그룹화 속성 GG로 튜플을 묶고 각 그룹에 집계 함수 FF를 적용하며, SQL의 GROUP BY와 대응한다.
  • “존재하지 않음”을 묻는 부정 질의는 관계대수에서 차집합으로 표현되며, 이는 SQL의 NOT IN·NOT EXISTS로 번역되지만 NULL 처리 방식은 SQL 단계에서만 발생하는 별도 문제다.
  • 관계대수는 릴레이션을 순수 집합으로 다뤄 투영·합집합에서 항상 중복을 제거하지만, SQL 테이블은 다중집합이라 DISTINCT·UNION ALL 등을 명시해야 하므로 두 모델의 결과 행 수가 달라질 수 있다.

마무리 복습

문제 14지선다
디비전 연산 R ÷ S에 대한 설명으로 옳은 것은?
문제 24지선다
ENROLL(학번, 과목코드, 성적)에서 학번별 수강 과목 집합이 S01={C01,C02,C03}, S02={C01,C02}, S03={C03}이고 COURSE 전체 과목이 {C01,C02,C03}일 때, π학번,과목코드(ENROLL) ÷ π과목코드(COURSE)의 결과로 옳은 것은?
문제 34지선다
집계 연산 G학과γCOUNT(학번)(STUDENT)와 γCOUNT(학번)(STUDENT)(그룹화 속성 생략)의 차이로 옳은 것은?
문제 44지선다
'어떤 과목도 수강하지 않은 학생을 찾아라'는 질의를 관계대수로 표현할 때 사용하는 핵심 연산으로 가장 적절한 것은?
문제 54지선다
관계대수의 투영(π)과 SQL의 SELECT에 대한 설명으로 옳은 것은?
문제 64지선다
다음 중 관계대수 연산과 SQL 구문의 대응으로 옳지 않은 것은?

참고 자료

Last updated on