본문 바로가기

관계 모델과 관계대수의 집합 연산

릴레이션·선택·투영·합집합·교집합·차집합·곱을 작은 중간 결과로 계산하고 MySQL 8.4의 중복 규칙과 비교합니다.

SQL을 쓸 줄 알아도 SELECT가 관계대수의 어떤 생각을 구현하는지 설명하기는 어렵습니다. 관계대수는 표 모양의 입력을 받아 다시 표 모양의 결과를 만드는 연산 체계입니다. 연산 결과를 다음 연산의 입력으로 쓸 수 있는 닫힘이 있으므로 작은 질의를 조합해 큰 질의를 설명할 수 있습니다.

릴레이션의 용어와 SQL의 차이

릴레이션은 같은 속성 구조를 가진 튜플의 집합입니다. 튜플은 한 행, 속성은 열, 도메인은 그 속성이 가질 수 있는 값의 범위입니다. 회원(회원번호, 이름, 도시)는 스키마이고 오늘 저장된 세 행은 인스턴스입니다. 스키마의 열 수를 차수, 인스턴스의 튜플 수를 카디널리티라고 합니다. 여기서 카디널리티는 ERD의 1:N과 문맥이 다릅니다.

수학적 릴레이션에서는 같은 튜플을 두 번 세지 않고 행 순서에 뜻을 부여하지 않습니다. SQL 조회는 기본적으로 중복을 보존하는 다중집합이며 ORDER BY가 없으면 결과 순서를 약속하지 않습니다. SQL의 NULL과 세 값 논리도 여기의 NULL 없는 집합 예제와 구분합니다.

회원번호이름도시
1민서울
2준서울
3솔부산

이 관계는 차수 3, 카디널리티 3입니다. 이름을 알면 도시가 정해진다고 이 세 행만으로 일반화할 수는 없습니다. 키와 함수 종속은 앞으로 가능한 데이터까지 포함하는 업무 규칙입니다.

선택과 투영의 중간 결과

선택 σ조건(R)은 조건에 맞는 행을 남깁니다. 투영 π속성목록(R)은 필요한 속성만 남기고 집합의 중복을 제거합니다. 관계대수의 선택은 SQL 문장 이름 SELECT와 같지 않습니다. SQL에서는 주로 WHERE가 선택, SELECT 목록이 투영에 대응합니다.

서울 회원을 선택하면 회원번호 1·2의 두 행, 그 결과에서 도시만 투영하면 {서울} 한 튜플입니다. 먼저 도시만 투영하면 회원번호·이름 정보가 없어져 뒤에서 그 속성을 조건에 사용할 수 없습니다.

독립 관계대수 실습 데이터
CREATE DATABASE IF NOT EXISTS cs_db_lab;
USE cs_db_lab;
DROP TABLE IF EXISTS cs_ra_people;
DROP TABLE IF EXISTS cs_ra_a;
DROP TABLE IF EXISTS cs_ra_b;
CREATE TABLE cs_ra_people (
  person_id INT NOT NULL PRIMARY KEY,
  name VARCHAR(20) NOT NULL,
  city VARCHAR(20) NOT NULL
) ENGINE=InnoDB;
INSERT INTO cs_ra_people VALUES (1,'민','서울'),(2,'준','서울'),(3,'솔','부산');
CREATE TABLE cs_ra_a (person_id INT NOT NULL) ENGINE=InnoDB;
CREATE TABLE cs_ra_b (person_id INT NOT NULL) ENGINE=InnoDB;
INSERT INTO cs_ra_a VALUES (1),(2),(2);
INSERT INTO cs_ra_b VALUES (2),(3);
SELECT person_id,name,city FROM cs_ra_people WHERE city='서울' ORDER BY person_id;
SELECT city FROM cs_ra_people ORDER BY person_id;
SELECT DISTINCT city FROM cs_ra_people ORDER BY city;

두 번째 조회에는 서울이 두 번 있습니다. 세 번째는 도시 두 종류를 남깁니다. DISTINCT를 빼면 순수 관계대수의 투영과 중복 정책이 달라집니다. 실습은 앞 장의 board_lab를 변경하지 않는 별도 cs_db_lab만 사용합니다.

합집합·교집합·차집합

집합 A를 {1,2}, B를 {2,3}으로 봅니다. 합집합은 {1,2,3}, 교집합은 {2}, A−B는 {1}입니다. B−A는 {3}이므로 차집합은 순서를 바꿔도 같은 연산이 아닙니다.

두 입력의 열 수와 대응 도메인이 호환되어야 합니다. 회원번호 집합에 이름 집합을 같은 의미의 값처럼 합치지 않습니다. SQL이 타입을 암묵 변환해 실행해 주더라도 의미의 호환성은 작성자가 정해야 합니다.

MySQL 8.4의 집합 연산
SELECT person_id FROM cs_ra_a
UNION SELECT person_id FROM cs_ra_b ORDER BY person_id;
SELECT person_id FROM cs_ra_a
INTERSECT SELECT person_id FROM cs_ra_b ORDER BY person_id;
SELECT person_id FROM cs_ra_a
EXCEPT SELECT person_id FROM cs_ra_b ORDER BY person_id;
SELECT person_id FROM cs_ra_a
UNION ALL SELECT person_id FROM cs_ra_b ORDER BY person_id;

각 결과는 1,2,3 / 2 / 1 / 1,2,2,2,3입니다. 마지막 결과의 2 세 개는 A의 두 행과 B의 한 행입니다. MySQL 8.4는 INTERSECT와 EXCEPT도 지원합니다. 여러 연산을 섞을 때는 우선순위를 추측하지 말고 괄호로 의도한 중간 집합을 명시합니다. MySQL 집합 연산 문서

카티션 곱과 조인의 출발점

카티션 곱 A × B는 A의 각 튜플을 B의 모든 튜플과 짝짓습니다. 집합이면 2×2=4쌍 (1,2),(1,3),(2,2),(2,3)입니다. SQL fixture의 A는 3행이므로 DISTINCT 없는 CROSS JOIN은 3×2=6행입니다.

SELECT a.person_id AS a_id,b.person_id AS b_id
FROM cs_ra_a AS a CROSS JOIN cs_ra_b AS b
ORDER BY a_id,b_id;
SELECT DISTINCT a.person_id AS a_id,b.person_id AS b_id
FROM cs_ra_a AS a CROSS JOIN cs_ra_b AS b
ORDER BY a_id,b_id;

두 번째 조회가 집합의 네 쌍을 남깁니다. 실제 테이블 조인은 곱에서 관계 조건을 만족하는 쌍을 선택한다고 설명할 수 있습니다. 옵티마이저가 모든 곱을 먼저 물리적으로 생성한다는 뜻은 아닙니다.

선택은 행을, 투영은 속성을 남긴다

회원 관계를 읽는다. 도시 속성을 집합으로 투영한다. 각 단계의 값과 전환 이유를 비교합니다.

선택은 행을, 투영은 속성을 남긴다

서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다.

선택은 행을, 투영은 속성을 남긴다서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다. 입력 관계:회원 3행;행 선택:서울 회원 2행;도시 투영:집합: 서울 1개。화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.입력 관계회원 3행행 선택서울 회원 2행도시 투영집합: 서울 1개
선택은 행을, 투영은 속성을 남긴다서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다. 입력 관계:회원 3행;행 선택:서울 회원 2행;도시 투영:집합: 서울 1개。화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.입력 관계회원 3행행 선택서울 회원 2행도시 투영집합: 서울 1개
화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 입력 관계부터 집합 투영까지 진행합니다.
관계도의 보충 설명
전체 도표의 표기·관계·조건
구분전체 도표의 내용
관계 설명서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다. 입력 관계: 회원 3행; 행 선택: 서울 회원 2행; 도시 투영: 집합: 서울 1개. 화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
입력 관계

회원 관계를 읽는다.

  • 관계·속성 1: 회원 3행
  • 원본 관계 유지
선택

서울 회원만 남긴다.

  • 관계·속성 1: 서울 회원 2행
  • 행 필터
집합 투영

도시 속성을 집합으로 투영한다.

  • 관계·속성 1: 서울 1개
  • 관계·속성 2: SQL DISTINCT 없으면 2행
  • 논리 관계 연산; 저장 원본 변경 없음

선택은 행을, 투영은 속성을 남긴다

회원 관계를 읽는다. 도시 속성을 집합으로 투영한다. 각 단계의 값과 전환 이유를 비교합니다.

선택은 행을, 투영은 속성을 남긴다

서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다.

선택은 행을, 투영은 속성을 남긴다서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다. 입력 관계:회원 3행;행 선택:서울 회원 2행;도시 투영:집합: 서울 1개。화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.입력 관계회원 3행행 선택서울 회원 2행도시 투영집합: 서울 1개
선택은 행을, 투영은 속성을 남긴다서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다. 입력 관계:회원 3행;행 선택:서울 회원 2행;도시 투영:집합: 서울 1개。화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.입력 관계회원 3행행 선택서울 회원 2행도시 투영집합: 서울 1개
화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 입력 관계부터 집합 투영까지 진행합니다.

관계도의 보충 설명

전체 도표의 표기·관계·조건
구분전체 도표의 내용
관계 설명서울 회원 두 행의 도시를 집합으로 투영하면 서울 한 튜플이 남습니다. 입력 관계: 회원 3행; 행 선택: 서울 회원 2행; 도시 투영: 집합: 서울 1개. 화살표는 논리 연산의 입력→출력입니다. SQL SELECT 목록은 DISTINCT가 없으면 서울 두 행을 보존합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
입력 관계

회원 관계를 읽는다.

  • 관계·속성 1: 회원 3행
  • 원본 관계 유지
선택

서울 회원만 남긴다.

  • 관계·속성 1: 서울 회원 2행
  • 행 필터
집합 투영

도시 속성을 집합으로 투영한다.

  • 관계·속성 1: 서울 1개
  • 관계·속성 2: SQL DISTINCT 없으면 2행
  • 논리 관계 연산; 저장 원본 변경 없음

독립 연습과 복습

  1. 집합 A={1,2,4}, B={2,3,4}에서 A−B와 B−A, 교집합을 구하세요.
  2. SQL의 A에 4를 두 번 추가하면 UNION과 UNION ALL의 행 수는 어떻게 달라질까요?
  3. 서울 회원 이름을 구할 때 도시만 먼저 투영하면 잃는 정보는 무엇인가요?
힌트

차집합은 왼쪽에만 있는 값을 찾습니다. UNION ALL은 원본의 모든 행을 셉니다. 투영은 버린 열을 다시 만들어 주지 않습니다.

풀이

1번은 {1}, {3}, {2,4}입니다. 원래 SQL A 3행에 4 두 행을 추가하면 A 5행+B 2행이므로 UNION ALL은 7행, UNION은 1·2·3·4의 4행입니다. 도시만 남기면 이름과 회원번호를 알 수 없어 같은 도시의 어느 회원인지 복원할 수 없습니다.

관계대수는 집합을 조합하는 논리 설명이고, SQL 결과는 중복·NULL·정렬을 별도로 설계합니다. 다음 본문에서는 관계 조건과 “모두 만족” 조건을 이 원리로 구성합니다.