관계대수로 읽는 SQL 질의 처리와 최적화

관계대수의 선택·투영·조인·집합 연산을 중심으로 SQL 질의 처리와 DBMS 실행 계획 최적화의 기반을 정리한다.

2026-08-15 · 최초 발행 2025-08-10

SQL 질의 처리의 바닥에 놓인 연산 체계

관계대수는 릴레이션, 즉 테이블을 대상으로 원하는 데이터를 구하는 절차적 언어다. SQL의 이론적 기반이며, DBMS가 질의를 변환하고 실행 계획을 구성하는 과정과도 연결된다.

연산의 입력과 결과가 모두 릴레이션이라는 점이 핵심이다. 하나의 연산 결과를 다시 다른 연산의 입력으로 사용할 수 있으므로, 복잡한 질의도 작은 연산을 중첩해 표현할 수 있다. 관계대수는 무엇을 얻을지뿐 아니라 그것을 얻는 방법까지 명시한다.

행·열·이름을 다루는 기본 연산

기본 연산자는 Select(σ), Project(π), Rename(ρ), Cartesian Product(×), Union(∪)로 구성된다.

조건에 맞는 행만 남기는 Select(σ)

Select는 조건을 만족하는 튜플, 즉 행을 골라 수평적 부분집합을 만든다. 형식은 σ<조건>(릴레이션)이다.

Employee 테이블에서 부서가 'IT'인 직원만 선택하는 식은 다음과 같다.

σdepartment='IT'(Employee)

SQL에서는 WHERE 절로 표현한다.

SELECT * FROM Employee WHERE department = 'IT';

필요한 속성만 꺼내는 Project(π)

Project는 지정한 속성, 즉 열만 추출해 수직적 부분집합을 만든다. 중복 튜플은 제거되며 형식은 π<속성 리스트>(릴레이션)이다.

Employee에서 이름과 급여 속성만 가져오려면 다음처럼 쓴다.

πname,salary(Employee)

SQL에서는 DISTINCT를 사용해 같은 의미를 나타낼 수 있다.

SELECT DISTINCT name, salary FROM Employee;

릴레이션과 속성 이름을 바꾸는 Rename(ρ)

Rename은 릴레이션 또는 속성의 이름을 변경한다. ρ<새이름>(릴레이션) 또는 ρ<새이름(속성1, 속성2, ...)>(릴레이션) 형태를 사용한다.

Employee 릴레이션을 Worker로 바꾸는 표현은 다음과 같다.

ρWorker(Employee)

SQL의 AS 키워드가 이에 해당한다.

SELECT * FROM Employee AS Worker;

가능한 조합을 만드는 Cartesian Product(×)

Cartesian Product는 두 릴레이션의 모든 튜플 조합을 생성한다. 결과 릴레이션의 크기는 두 입력 릴레이션 크기의 곱이 된다. 형식은 릴레이션1 × 릴레이션2다.

Employee와 Department의 카티션 프로덕트는 다음과 같이 표현한다.

Employee × Department

SQL에서는 CROSS JOIN을 사용한다.

SELECT * FROM Employee CROSS JOIN Department;

합병 가능한 릴레이션을 합치는 Union(∪)

Union은 두 릴레이션의 튜플을 합치고 중복 튜플을 제거한다. 이 연산을 적용하려면 두 릴레이션이 합병 호환(union compatible)이어야 한다.

정규직 직원과 계약직 직원 릴레이션의 합집합은 다음과 같다.

RegularEmployee ∪ ContractEmployee
SELECT * FROM RegularEmployee UNION SELECT * FROM ContractEmployee;

관련 데이터를 연결하거나 집합 조건을 적용하는 파생 연산

Join(⋈), Intersection(∩), Division(÷), Difference(-)는 기본 연산을 바탕으로 설명할 수 있는 파생 연산자다.

Join(⋈)으로 릴레이션 연결하기

Join은 관련된 튜플을 결합한다. 카티션 프로덕트를 수행한 뒤 선택 연산을 적용하는 것과 같은 결과를 낸다. Theta Join, Equi Join, Natural Join, Outer Join 등 여러 유형이 있다.

Employee와 Department를 부서 ID로 자연 조인하는 식은 다음과 같이 표현한다.

Employee ⋈ Department
SELECT * FROM Employee NATURAL JOIN Department;

조건을 명시하는 Theta Join은 다음처럼 쓴다.

Employee ⋈(Employee.dept_id = Department.id) Department
SELECT * FROM Employee JOIN Department ON Employee.dept_id = Department.id;
JoinJoinEmployee 테이블조인 연산Department 테이블결과 릴레이션

공통 튜플을 찾는 Intersection(∩)

Intersection은 두 릴레이션에 모두 존재하는 튜플만 남기는 교집합 연산이다. 이때도 두 릴레이션은 합병 호환이어야 한다. 형식은 릴레이션1 ∩ 릴레이션2다.

두 프로젝트에 모두 참여한 직원을 찾는 식은 다음과 같다.

Project1Employees ∩ Project2Employees
SELECT * FROM Project1Employees INTERSECT SELECT * FROM Project2Employees;

모든 대상과 연결된 값을 찾는 Division(÷)

Division은 첫 번째 릴레이션에서 두 번째 릴레이션의 모든 튜플과 관련된 튜플을 찾는다. “모든 ~에 대하여”라는 조건을 가진 질의에 주로 사용하며, 형식은 릴레이션1 ÷ 릴레이션2다.

모든 프로젝트에 참여한 직원을 찾는 표현은 다음과 같다.

EmployeeProject ÷ Project

SQL에는 직접 대응하는 연산자가 없으므로 복잡한 질의로 구현한다.

SELECT e.emp_id
FROM Employee e
WHERE NOT EXISTS (
    SELECT p.proj_id
    FROM Project p
    WHERE NOT EXISTS (
        SELECT *
        FROM EmployeeProject ep
        WHERE ep.emp_id = e.emp_id AND ep.proj_id = p.proj_id
    )
);

한쪽에만 있는 튜플을 남기는 Difference(-)

Difference는 첫 번째 릴레이션에는 있지만 두 번째 릴레이션에는 없는 튜플을 구한다. 형식은 릴레이션1 - 릴레이션2다.

Project1에는 참여했지만 Project2에는 참여하지 않은 직원을 찾는 경우는 다음과 같다.

Project1Employees - Project2Employees
SELECT * FROM Project1Employees EXCEPT SELECT * FROM Project2Employees;

중첩된 연산식으로 복합 질의 표현하기

실제 질의는 하나의 연산자로 끝나지 않는다. IT 부서에서 연봉이 $70,000 이상인 직원의 이름과 이메일을 조회하려면 선택 결과에 투영을 적용한다.

πname,email(σdepartment='IT' ∧ salary>=70000(Employee))

SQL로는 다음과 같이 작성한다.

SELECT name, email
FROM Employee
WHERE department = 'IT' AND salary >= 70000;

기본 관계대수를 넓히는 기능

기본 관계대수 외에는 집계 함수인 COUNT, SUM, AVG, MIN, MAX를 다루는 확장도 사용된다. SQL의 GROUP BY에 해당하는 그룹화 연산도 여기에 포함된다.

집계와 그룹화가 필요한 질의는 기본 관계대수의 선택·투영·조인만으로는 직접 표현하기 어렵다. 예를 들어 부서별 평균 급여가 전체 평균 급여보다 높은 부서를 찾으려면, 각 부서의 평균 급여를 DeptAvg로 명명하고 전체 직원 평균 급여를 TotalAvg로 계산한 뒤 DeptAvg에서 평균 급여가 TotalAvg보다 큰 부서를 선택해야 한다. SQL에서는 COUNT, SUM, AVG 같은 집계 함수와 GROUP BY, 서브쿼리를 조합해 작성할 수 있지만, 관계대수에서는 집계 함수와 그룹화 기능을 포함한 확장 관계대수가 필요하다.

외부 조인(Outer Join)은 조인 조건을 만족하지 않는 튜플까지 결과에 포함한다. 재귀 연산은 그래프 데이터에서 경로를 찾는 용도 등에 쓰인다.

관계대수와 SQL이 다른 지점

관계대수는 절차적 언어인 반면 SQL은 비절차적, 즉 선언적 언어다. 관계대수는 중복 튜플을 자동으로 제거하지만 SQL에서는 DISTINCT 키워드를 사용해야 한다.

NULL 처리 방식도 다르다. 관계대수는 원래 NULL 값을 고려하지 않았고, SQL은 NULL을 명시적으로 지원한다. 또한 기본 관계대수에는 집계 함수가 없지만 SQL은 다양한 집계 함수를 제공한다.

DBMS가 실행 계획으로 바꾸는 과정

DBMS는 SQL 쿼리를 관계대수 표현으로 옮긴 뒤, 같은 결과를 만드는 더 효율적인 형태로 변환한다. 그 결과를 바탕으로 실행 계획을 생성해 쿼리를 수행한다.

SQL 쿼리관계대수 표현쿼리 최적화최적화된 관계대수실행 계획쿼리 실행

관계대수는 이 과정에서 SQL 변환, 동등 변환, 실행 계획 생성의 기준이 된다.

설계와 분석 질의에서의 활용

관계대수는 스키마 설계와 정규화 과정의 관계 모델링에 쓰인다. 쿼리 최적화에서는 효율적인 실행 계획을 만드는 기반이 되며, 데이터 웨어하우징의 복잡한 분석 쿼리 설계와 분산 데이터베이스의 쿼리 분해·최적화에도 활용된다.

관계대수의 연산자는 데이터 조작을 구성하는 기본 단위다. SQL을 작성하거나 실행 계획을 검토할 때 선택, 투영, 조인, 집합 연산이 어떤 순서로 결합되는지 읽을 수 있으면 질의 처리 구조를 더 분명하게 파악할 수 있다.

관계대수관계형 데이터베이스SQLDBMS쿼리 최적화