수학 연산에서 순서가 복잡한 연산은 괄호”()”로 묶어서 연산하듯, SQL에서 복잡한 연산을 할 때, “서브쿼리” 를 사용합니다. “서브쿼리”는 구조적으로 다른 SQL 문장 내에 포함된 쿼리를 의미합니다.
서브쿼리 구조
간단한 예로 서브쿼리의 구조를 살펴보겠습니다.
부서별 급여가 평균 금액 이상인 직원 찾는다고 하면 아래와 같은 쿼리를 가집니다.
SELECT employee_id, employee_name, salary, department_id
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees GROUP BY department_id);
위의 구조에서 외부 쿼리 (메인 쿼리)는
SELECT employee_id, employee_name, salary, department_id FROM employees;
내부 쿼리 (서브 쿼리)는
(SELECT AVG(salary) FROM employees GROUP BY department_id)
입니다.
서브쿼리는 메인 쿼리의 실행 전에 실행되며, 메인 쿼리에서 이를 참조할 수 있습니다.
(다만, 최적화 엔진에 따라 실행 계획이 변경될 수 있으며, 서브쿼리와 메인 쿼리의 실행 순서가 변경될 수도 있습니다)
서브쿼리 사용 가능한 위치
1. SELECT 문
이 위치에서는 서브쿼리가 반환하는 값을 가져와 결과 세트에 포함시킵니다.
실행 순서: 주 쿼리가 실행되기 전에 서브쿼리가 먼저 실행되며, 결과는 주 쿼리에서 활용됩니다.
SELECT column1, (SELECT MAX(column2) FROM another_table) AS max_value
FROM your_table;
2. FROM 절
서브쿼리는 FROM 절에서 테이블처럼 사용될 수 있습니다.
실행 순서: 주 쿼리의 FROM 절에 정의된 테이블과 함께 사용되며, 서브쿼리 결과가 임시 테이블로 간주됩니다.
SELECT column1
FROM your_table,
(SELECT column2 FROM another_table WHERE condition) AS subquery_table
WHERE your_table.column2 = subquery_table.column2;
3. WHERE 절
실행 순서: 메인 쿼리의 WHERE 절에서 서브쿼리의 조건이 평가되며, 결과에 따라 메인 쿼리의 행이 선택됩니다.
SELECT column1, column2
FROM your_table
WHERE column2 > (SELECT AVG(column2) FROM another_table);
4. HAVING 절
실행 순서: 주 쿼리의 GROUP BY 절과 HAVING 절에 따라 그룹화된 결과에서 서브쿼리가 실행되고, 조건에 맞는 그룹이 선택됩니다.
SELECT column1, COUNT(*)
FROM your_table
GROUP BY column1
HAVING COUNT(*) > (SELECT AVG(count_column)
FROM another_table);
5. INSERT 문
서브쿼리는 INSERT 문에서 VALUES 절 대신 사용될 수 있습니다.
INSERT INTO your_table (column1, column2)
SELECT column3, column4
FROM another_table
WHERE condition;
서브쿼리가 필요한 경우
1. 복잡한 조건
앞서 언급했듯이, 서브쿼리는 복잡한 연산, 다시 말해, 복잡한 필터링 또는 조건을 걸 때 사용할 수 있습니다.
외부 쿼리에서 사용된 결과에 따라 내부 쿼리를 통해 더 정교한 조건을 적용해야 하는 경우.
예를 들어, 특정 조건을 만족하는 행만을 선택하는 등의 경우에 활용됩니다.
SELECT *
FROM orders
WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA');
2. 다중 테이블 간의 관계 활용
여러 테이블 간의 관계를 고려하여 데이터를 추출해야 하는 경우.
서브쿼리를 사용하여 외부 쿼리의 결과를 기반으로 다른 테이블과의 관계를 맺을 수 있습니다.
아래 쿼리문을 보면 메인쿼리에서는 employees 테이블을 서브쿼리에서는 department 테이블 데이터를 이용한 것을 확인할 수 있습니다.
SELECT *
FROM employees
WHERE department_id = (SELECT department_id FROM departments WHERE department_name = 'Sales');
서브쿼리 사용시 주의점
성능 문제
서브쿼리는 전체 쿼리의 성능에 영향을 미칠 수 있습니다.
서브쿼리가 복잡하거나 큰 데이터셋에서 실행될 경우 성능이 저하될 수 있습니다.
결과 집합 크기
서브쿼리의 결과 집합 크기에 주의해야 합니다.
서브쿼리에서 여러 행을 반환하는 경우 메인 쿼리에서는 비교 연산자(=, <, >
, 등)를 사용할 때 오류가 발생할 수 있습니다.
-- 잘못된 사용 예시
SELECT column1
FROM your_table
WHERE column2 = (SELECT column2 FROM another_table);
올바른 방법은 비교 연산자 대신 IN, ANY, ALL
등을 사용하여 서브쿼리 결과와 비교하는 것입니다.
-- 올바른 사용 예시
SELECT column1
FROM your_table
WHERE column2 IN (SELECT column2 FROM another_table);
NULL 처리
서브쿼리에서 반환된 결과가 NULL인 경우 주 쿼리에서 예상치 못한 결과가 발생할 수 있습니다. 이에 대한 적절한 처리가 필요합니다.
예를 들어, 서브쿼리 결과에 NULL이 포함되면 예상과 다른 결과가 나올 수 있습니다.
서브쿼리가 NULL 값을 반환한다면, 이 부분은 항상 UNKNOWN를 반환합니다. 비교 연산에서 UNKNOWN와의 비교는 참(true)이 됩니다.
-- 잘못된 사용 예시
SELECT column1
FROM your_table
WHERE column2 = (SELECT column2 FROM another_table WHERE condition);
IS NULL 또는 IS NOT NULL을 사용하여 처리합니다.
-- 올바른 사용 예시
SELECT column1
FROM your_table
WHERE column2 IS NOT NULL
AND column2 = (SELECT column2 FROM another_table WHERE condition);
중첩된 서브쿼리
여러 수준의 중첩된 서브쿼리를 사용할 때 코드의 가독성이 나빠질 수 있습니다.
결과 정렬
서브쿼리는 일반적으로 메인 쿼리의 일부로 동작하므로 서브쿼리 내부에서 결과를 정렬하는 것은 허용됩니다.
SELECT column1
FROM (
SELECT column1, column2
FROM your_table
ORDER BY column2 DESC
) AS subquery
WHERE condition;
그러나 주의해야 할 중요한 점은 일부 DBMS (MySQL, SQLite)에서는 서브쿼리의 ORDER BY가 무시되는 경우도 있습니다.
따라서 DBMS에 따라 문법이나 동작이 다를 수 있으므로 사용하는 데이터베이스에 특화된 문서를 참고하는 것이 좋습니다.