🗄️ MSSQL 서브쿼리 활용법과 실전 예제
📌 쿼리 안에 또 다른 SELECT를 넣어 조건을 유연하게 설계하는 방법
데이터 분석이나 비즈니스 로직을 구현할 때, 단일 SELECT 문으로는 표현하기 어려운 경우가 많습니다.
이럴 때 MSSQL 서브쿼리(Subquery)를 사용하면 쿼리 내부에서 또 다른 SELECT를 실행해 조건을 동적으로 만들 수 있습니다.
특히 WHERE 절의 IN 조건이나 SELECT 절, FROM 절 등 다양한 위치에서 활용 가능해 복잡한 로직도 간결하게 표현할 수 있습니다.
예를 들어, 특정 조건에 맞는 고객 ID 목록을 서브쿼리로 추출하고, 이를 기반으로 메인 쿼리에서 거래 내역을 조회할 수 있습니다.
서브쿼리는 단일 행(Subquery that returns a single value) 또는 다중 행(Subquery that returns multiple values) 형태로 나뉘며, 각각의 특성에 맞게 활용하면 코드 가독성과 유지보수성이 높아집니다.
또한, JOIN을 대체하거나 보완하는 용도로도 자주 사용되며, 복잡한 데이터를 효율적으로 가공하는 데 매우 유용합니다.
이번 글에서는 서브쿼리의 기본 개념부터 다양한 활용 사례, 그리고 성능 최적화 팁까지 실무 중심으로 정리하겠습니다.
📋 목차
🔍 서브쿼리의 개념과 종류
서브쿼리(Subquery)는 쿼리 안에 포함된 또 다른 SELECT 문을 의미합니다.
보통 메인 쿼리의 조건, 결과 집합, 또는 가상의 테이블 역할을 하며 복잡한 로직을 간결하게 만들 수 있습니다.
MSSQL뿐만 아니라 대부분의 관계형 데이터베이스에서 사용 가능한 기능입니다.
서브쿼리는 반환되는 결과와 위치에 따라 크게 다음과 같이 나눌 수 있습니다.
- 📌단일 행 서브쿼리(Single-row Subquery): 단 하나의 값만 반환
- 📌다중 행 서브쿼리(Multi-row Subquery): 여러 행을 반환 (IN, ANY, ALL과 함께 사용)
- 📌다중 열 서브쿼리(Multi-column Subquery): 여러 컬럼을 반환
- 📌상관 서브쿼리(Correlated Subquery): 메인 쿼리의 각 행마다 서브쿼리를 실행
다음은 다중 행 서브쿼리를 WHERE 절에서 사용하는 간단한 예시입니다.
SELECT OrderID, CustomerID, OrderDate
FROM Orders
WHERE CustomerID IN (
SELECT CustomerID
FROM Customers
WHERE Country = 'USA'
);
이 예제에서 서브쿼리는 미국에 거주하는 고객들의 ID 목록을 반환하며, 메인 쿼리는 해당 고객들의 주문 내역을 조회합니다.
이처럼 서브쿼리를 사용하면 조건을 보다 유연하게 설계할 수 있습니다.
💬 서브쿼리는 복잡한 JOIN을 단순화하거나, 특정 조건에 맞는 데이터만 빠르게 추출할 때 매우 유용합니다.
⚙️ WHERE 절에서의 서브쿼리 활용
서브쿼리가 가장 자주 쓰이는 위치 중 하나가 바로 WHERE 절입니다.
이 방식은 메인 쿼리에서 조건을 직접 지정하기 어려울 때, 서브쿼리를 통해 조건을 동적으로 생성할 수 있습니다.
특히 다중 행을 반환하는 서브쿼리는 IN, ANY, ALL 같은 키워드와 함께 사용하면 유용합니다.
다음 예제는 최근 30일 내에 주문을 한 고객의 정보를 조회하는 WHERE 절 서브쿼리입니다.
SELECT CustomerID, CompanyName, Country
FROM Customers
WHERE CustomerID IN (
SELECT CustomerID
FROM Orders
WHERE OrderDate >= DATEADD(DAY, -30, GETDATE())
);
이 쿼리는 서브쿼리를 통해 최근 30일 동안 주문이 발생한 고객 ID를 추출하고, 메인 쿼리에서 해당 고객의 정보를 가져옵니다.
이렇게 하면 주문 내역과 고객 정보가 따로 저장되어 있어도 간단히 원하는 결과를 얻을 수 있습니다.
💡 TIP: WHERE 절 서브쿼리의 결과가 많을 경우, 관련 컬럼에 인덱스를 설정하면 조회 속도가 크게 향상됩니다.
또한, 단일 행을 반환하는 서브쿼리는 비교 연산자(=, >, < 등)와 함께 사용할 수 있습니다.
예를 들어, 주문 금액이 평균 이상인 고객만 조회하려면 다음과 같이 작성할 수 있습니다.
SELECT CustomerID, CompanyName, Country
FROM Customers
WHERE CustomerID IN (
SELECT CustomerID
FROM Orders
GROUP BY CustomerID
HAVING SUM(Freight) >= (
SELECT AVG(TotalFreight)
FROM (
SELECT SUM(Freight) AS TotalFreight
FROM Orders
GROUP BY CustomerID
) AS T
)
);
이렇게 서브쿼리를 중첩해서 사용하면, 복잡한 집계 조건도 단일 쿼리 안에서 처리할 수 있습니다.
🧩 SELECT 절과 FROM 절의 서브쿼리
서브쿼리는 WHERE 절뿐만 아니라 SELECT 절과 FROM 절에서도 활용할 수 있습니다.
이렇게 사용하면 조회 대상 데이터의 범위를 사전에 가공하거나, 계산된 컬럼을 생성하는 데 유리합니다.
🔹 SELECT 절에서의 서브쿼리
SELECT 절에서 서브쿼리를 사용하면 각 행에 대해 계산된 값을 컬럼으로 추가할 수 있습니다.
예를 들어, 각 고객의 주문 건수를 함께 표시하려면 다음과 같이 작성합니다.
SELECT
CustomerID,
CompanyName,
(SELECT COUNT(*)
FROM Orders
WHERE Orders.CustomerID = Customers.CustomerID) AS OrderCount
FROM Customers;
이 방법은 JOIN 대신 각 행별로 필요한 계산 값을 조회할 때 유용합니다.
하지만 데이터 양이 많으면 성능 저하가 발생할 수 있어 주의해야 합니다.
🔹 FROM 절에서의 서브쿼리
FROM 절에서 서브쿼리를 사용하면 가상의 테이블(파생 테이블, Derived Table)을 만들어 메인 쿼리에서 재활용할 수 있습니다.
이는 복잡한 집계나 가공 로직을 깔끔하게 분리하는 데 유용합니다.
SELECT CustomerID, TotalFreight
FROM (
SELECT CustomerID, SUM(Freight) AS TotalFreight
FROM Orders
GROUP BY CustomerID
) AS FreightSummary
WHERE TotalFreight > 1000;
이 예제에서 FROM 절의 서브쿼리는 고객별 운송료 합계를 계산하고, 메인 쿼리는 합계가 1000 이상인 고객만 추출합니다.
이 구조는 복잡한 로직을 모듈화해 쿼리 가독성과 재사용성을 높이는 장점이 있습니다.
💡 TIP: FROM 절 서브쿼리는 반드시 별칭(Alias)을 지정해야 하며, 그렇지 않으면 SQL 구문 오류가 발생합니다.
🚀 상관 서브쿼리(Correlated Subquery)
상관 서브쿼리(Correlated Subquery)는 메인 쿼리의 각 행마다 서브쿼리를 실행하는 방식입니다.
일반 서브쿼리는 한 번만 실행되지만, 상관 서브쿼리는 행 단위로 반복 실행되므로 조건이 동적으로 변합니다.
이 방식은 JOIN으로 대체할 수 없는 복잡한 조건 로직을 구현할 때 유용합니다.
다음 예제는 각 고객이 마지막으로 주문한 날짜를 조회하는 상관 서브쿼리입니다.
SELECT
CustomerID,
CompanyName,
(SELECT MAX(OrderDate)
FROM Orders
WHERE Orders.CustomerID = Customers.CustomerID) AS LastOrderDate
FROM Customers;
이 쿼리는 Customers 테이블의 각 행마다 Orders 테이블에서 해당 고객의 가장 최근 주문일을 찾아 반환합니다.
JOIN으로도 유사한 결과를 만들 수 있지만, 상관 서브쿼리는 조건 로직을 독립적으로 유지하면서 필요한 데이터만 가져올 수 있다는 장점이 있습니다.
💎 핵심 포인트:
상관 서브쿼리는 각 행마다 별도의 조건을 적용해야 할 때 강력하지만, 데이터 양이 많으면 성능이 저하될 수 있으므로 인덱스 최적화가 필수입니다.
성능 저하를 최소화하려면 WHERE 절에서 비교하는 컬럼(여기서는 CustomerID)에 인덱스를 생성하는 것이 좋습니다.
또한, 상관 서브쿼리가 불필요하게 반복 실행되지 않도록 쿼리 구조를 점검하는 습관이 필요합니다.
⚠️ 주의: 수천 건 이상의 데이터에 상관 서브쿼리를 실행하면 성능 문제가 발생할 수 있으니, 가능한 경우 JOIN이나 윈도우 함수로 대체하는 방안을 검토하세요.
💡 성능 최적화와 주의사항
서브쿼리는 복잡한 로직을 간결하게 만들 수 있는 강력한 도구지만, 잘못 사용하면 성능 저하를 유발할 수 있습니다.
따라서 다음과 같은 성능 최적화 전략과 주의사항을 반드시 기억해야 합니다.
- ✅반복 실행되는 상관 서브쿼리는 JOIN 또는 윈도우 함수로 대체 고려
- ✅서브쿼리에 사용되는 컬럼에 적절한 인덱스 생성
- ✅불필요한 서브쿼리 중첩 피하기
- ✅실행 계획을 통해 성능 병목 구간 확인
다음은 실행 계획을 분석해 성능을 개선한 사례입니다.
기존 쿼리는 상관 서브쿼리를 사용해 각 고객의 총 주문 금액을 조회했는데, 실행 시간이 길었습니다.
이를 복합 인덱스와 JOIN으로 변경해 성능을 크게 향상시켰습니다.
-- 기존 상관 서브쿼리
SELECT CustomerID, CompanyName,
(SELECT SUM(Freight)
FROM Orders
WHERE Orders.CustomerID = Customers.CustomerID) AS TotalFreight
FROM Customers;
-- JOIN으로 변경
SELECT c.CustomerID, c.CompanyName, SUM(o.Freight) AS TotalFreight
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID
GROUP BY c.CustomerID, c.CompanyName;
두 번째 쿼리는 집계와 조인을 한 번에 처리해, 대량 데이터에서도 빠른 실행 속도를 보장합니다.
즉, 서브쿼리가 항상 최선의 선택은 아니므로, 상황에 맞게 다른 방법과 비교해 최적의 구조를 선택해야 합니다.
💎 핵심 포인트:
서브쿼리를 사용하되, 인덱스 최적화와 실행 계획 분석을 통해 성능을 보장하는 것이 중요합니다.
❓ 자주 묻는 질문 (FAQ)
서브쿼리와 JOIN의 차이점은 무엇인가요?
경우에 따라 서브쿼리를 JOIN으로 대체하면 성능이 향상될 수 있습니다.
WHERE 절 서브쿼리는 언제 사용하는 것이 좋나요?
예를 들어, 특정 기간에 주문이 있는 고객만 조회할 때 적합합니다.
SELECT 절에서 서브쿼리를 사용하면 성능에 문제가 없나요?
이런 경우 집계 JOIN이나 윈도우 함수를 고려하는 것이 좋습니다.
FROM 절 서브쿼리와 뷰(View)의 차이는 무엇인가요?
반복 사용이 잦으면 뷰를 만드는 것이 효율적입니다.
상관 서브쿼리를 사용할 때 주의할 점은 무엇인가요?
가능한 경우 JOIN이나 윈도우 함수로 대체하는 것이 좋습니다.
서브쿼리 안에서 ORDER BY를 사용해도 되나요?
TOP 또는 OFFSET-FETCH 절과 함께 사용할 때 특히 주의하세요.
서브쿼리 대신 CTE(Common Table Expression)를 쓰는 게 더 좋은가요?
그러나 성능 면에서는 서브쿼리와 큰 차이가 없으니 상황에 맞게 선택하면 됩니다.
서브쿼리 안에서 다른 서브쿼리를 중첩해도 되나요?
📊 서브쿼리 활용과 최적화 요약
MSSQL 서브쿼리는 쿼리 내부에서 또 다른 SELECT를 실행해 조건을 유연하게 구성하고, 복잡한 로직을 간결하게 표현할 수 있는 강력한 도구입니다.
WHERE 절에서는 조건 목록을 동적으로 생성해 필터링에 사용하고, SELECT 절에서는 각 행별 계산 값을 제공하며, FROM 절에서는 파생 테이블로 가공된 데이터를 제공합니다.
또한 상관 서브쿼리를 활용하면 각 행마다 다른 조건을 적용할 수 있지만, 성능 저하를 방지하기 위해 인덱스 최적화와 실행 계획 분석이 필수적입니다.
JOIN, CTE, 윈도우 함수 등 다른 기법과 비교하며 상황에 맞는 최적의 방식을 선택하는 것이 중요합니다.
이 글에서 소개한 예제와 팁을 참고해 실무 환경에서 서브쿼리를 효율적으로 활용해 보세요.
🏷️ 관련 태그 : MSSQL, 서브쿼리, SQL서브쿼리, 상관서브쿼리, 데이터필터링, 파생테이블, SQL성능, 인덱스최적화, SQL튜닝, CTE