데이터를 표(릴레이션)로 저장했으니, 이제 그 표에서 원하는 데이터를 꺼내는 방법을 정의할 차례다.
관계 데이터 연산은 "릴레이션을 입력받아 릴레이션을 출력하는" 연산들의 체계다. 다소 수학적이고 추상적이라 처음엔 "이걸 왜 배우나" 싶었다.
그런데 우리가 매일 쓰는 SQL의 SELECT가 사실 이 연산들이 옷을 갈아입은 모습이었다. 이 토대를 알아두니 SQL이 내부에서 무슨 일을 하는지가 보였다.
1. 관계 대수 vs 관계 해석
원하는 데이터를 표현하는 방식에는 두 갈래가 있다. 이 구분은 프로그래밍의 "명령형 vs 선언형"과 정확히 같다.
| 구분 | 성격 | 표현하는 것 |
|---|---|---|
| 관계 대수(Relational Algebra) | 절차적 | 어떻게(How) 얻을지, 즉 연산 순서를 명시 |
| 관계 해석(Relational Calculus) | 비절차적 | 무엇(What)을 원하는지만 명시 |
- 관계 대수: "A·B 테이블을 조인한 뒤, 나이 20 이상인 행을 고르고, 이름 열만 추출하라"처럼 절차를 일일이 지시한다.
- 관계 해석: "나이 20 이상인 사람의 이름"이라는 결과 조건만 기술하고 절차는 맡긴다.
흥미로운 건 둘의 표현력이 동일하다는 점이다(관계적으로 완전, relationally complete). 같은 질의를 한쪽은 절차로, 한쪽은 조건으로 쓸 뿐 결과가 같다.
그리고 우리가 쓰는 SQL은 겉모습은 관계 해석(선언적)에 가깝지만, DBMS 내부에서는 이를 관계 대수 연산으로 바꿔 실행한다. 이 변환과 최적화가 데이터베이스 엔진의 핵심 일이다(아래 '한 걸음 더' 참고).
2. 일반 집합 연산자
릴레이션은 결국 튜플의 집합이므로, 수학의 집합 연산을 그대로 적용할 수 있다.
| 연산 | 기호 | 의미 |
|---|---|---|
| 합집합(Union) | R ∪ S | R 또는 S에 있는 튜플 |
| 교집합(Intersection) | R ∩ S | R과 S 모두에 있는 튜플 |
| 차집합(Difference) | R − S | R에 있고 S에는 없는 튜플 |
| 카티션 프로덕트(Cartesian Product) | R × S | 두 릴레이션 튜플의 모든 조합 |
합집합·교집합·차집합을 하려면 두 릴레이션이 합병 가능해야 한다. 즉 ① 차수(열 개수)가 같고, ② 대응하는 속성의 도메인이 일치해야 한다. 열 구성이 다른 두 표의 행을 한데 합치면 어느 칸에 무엇을 넣을지 정의되지 않기 때문이다.
카티션 프로덕트만은 합병 가능 조건이 필요 없다. 두 표의 모든 행을 짝지어 조합하는 연산이라, R이 m행·S가 n행이면 결과는 m×n행이 된다.
그 자체로는 잘 안 쓰지만, 다음에 볼 조인이 사실은 이 카티션 프로덕트에서 조건에 맞는 행만 골라낸 것이라 개념적으로 중요하다.
3. 순수 관계 연산자
집합 연산과 달리, 관계 모델을 위해 특별히 정의된 연산들이다. 데이터를 자르는 방향으로 이해하니 깔끔했다.
| 연산 | 기호 | 의미 | 자르는 방향 |
|---|---|---|---|
| 셀렉트(Select) | σ (시그마) | 조건을 만족하는 튜플(행) 추출 | 수평(행) |
| 프로젝트(Project) | π (파이) | 지정한 속성(열) 추출 | 수직(열) |
| 조인(Join) | ⋈ | 공통 속성으로 두 릴레이션을 결합 | (해당 없음) |
| 디비전(Division) | ÷ | S의 모든 튜플과 관련된 R의 튜플 | (해당 없음) |
가장 헷갈리는 한 쌍이다. 셀렉트(σ)는 행을 가로로 잘라내고("여학생 행만"), 프로젝트(π)는 열을 세로로 잘라낸다("이름·나이 열만"). 이름과 달리 셀렉트가 SQL의 SELECT 절이 아니라 WHERE 절에 해당한다는 점이 함정이다.
세 연산을 SQL로 옮기니 정체가 분명해졌다. 예를 들어 SELECT 이름, 나이 FROM 학생 WHERE 성별='여'는 다음으로 분해된다.
WHERE 성별='여'→ 셀렉트 σ (조건에 맞는 행 선택)SELECT 이름, 나이→ 프로젝트 π (원하는 열 선택)FROM A, B ... WHERE A.학번=B.학번→ 조인 ⋈ (여러 테이블 결합)
즉 무심코 쓰는 SQL 한 줄이 σ·π·⋈의 조합으로 분해된다.
이 중 조인은 실무에서 가장 자주, 가장 무겁게 쓰는 연산이다. 본질은 "카티션 프로덕트 후 조인 조건을 만족하는 행만 셀렉트"한 것이다.
그래서 조인이 비효율적으로 수행되면 m×n개의 거대한 중간 결과가 생길 수 있고, 이를 어떻게 피하느냐가 쿼리 성능의 핵심이 된다.
마지막으로 디비전(÷)은 "~의 모든 것을 만족하는"을 표현한다. 예를 들어 "개설된 모든 과목을 수강한 학생"을 찾는 질의가 디비전이다. 자주 쓰진 않지만, "전부 다(for all)"라는 조건을 대수로 표현하는 유일한 연산이라 빠지지 않는다.
오늘 느낀 점
- "이걸 왜 배우나" 싶던 관계 대수가, 내가 매일 쓰는 SQL 한 줄을 σ·π·⋈로 분해해보고 나서야 의미가 잡혔다. 추상적인 이론이 실제 쓰는 도구의 속살이었다.
- 조인이 "카티션 프로덕트 + 셀렉트"라는 걸 알고 나니, 조인이 왜 무겁고 왜 조건을 먼저 거는 게 빠른지가 자연히 이해됐다.
- 관계 대수(How)와 관계 해석(What)의 구분이 프로그래밍의 명령형/선언형과 같다는 점에서, 분야가 달라도 같은 사고 틀이 반복된다는 걸 느꼈다.
한 걸음 더
- SQL은 선언적 언어라 우리는 "무엇을" 원하는지만 쓴다. 그러면 DBMS의 질의 최적화기(query optimizer)가 이를 관계 대수 식으로 바꾸고, 같은 결과를 내는 여러 실행 방법 중 가장 빠른 것을 고른다. 예를 들어 "조인 먼저 하고 셀렉트"보다 "셀렉트로 행을 먼저 줄이고 조인"하는 편이 중간 결과가 작아 대개 빠른데(셀렉션 푸시다운), 이런 변환이 전부 관계 대수의 등가 규칙(연산 순서를 바꿔도 결과가 같다) 위에서 이뤄진다.
- 조인에도 종류가 많다. 공통 속성 값이 일치하는 행만 남기는 내부 조인(inner join), 짝이 없는 행도 NULL로 채워 보존하는 외부 조인(outer join), 두 테이블의 모든 조합을 만드는 교차 조인(cross join, = 카티션 프로덕트) 등이 있다. 외부 조인 결과에는 NULL이 곳곳에 등장하므로, NULL 처리를 모르면 결과를 잘못 읽기 쉽다.
- 관계 해석은 기준으로 삼는 변수에 따라 튜플 관계 해석과 도메인 관계 해석으로 나뉜다. 학문적 구분이지만, 이 선언적 사고방식이 곧 SQL의
WHERE조건을 쓰는 감각과 직결된다. 우리는 절차가 아니라 "참이어야 할 조건"을 기술하고 있는 것이다.
'data > database' 카테고리의 다른 글
| [데이터베이스] 04. 관계 데이터 모델 (0) | 2026.08.18 |
|---|---|
| [데이터베이스] 03. 데이터 모델링 (개체-관계 모델) (0) | 2026.07.09 |
| [데이터베이스] 02. DBMS 데이터베이스 시스템 (0) | 2026.06.25 |
| [데이터베이스] 01. 데이터베이스 기본 개념 (0) | 2026.06.24 |