메뉴 닫기

MSSQL 서브쿼리 활용법과 실전 예제

🗄️ 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 절에서 사용하는 간단한 예시입니다.

CODE BLOCK
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 절 서브쿼리입니다.

CODE BLOCK
SELECT CustomerID, CompanyName, Country
FROM Customers
WHERE CustomerID IN (
    SELECT CustomerID
    FROM Orders
    WHERE OrderDate >= DATEADD(DAY, -30, GETDATE())
);

이 쿼리는 서브쿼리를 통해 최근 30일 동안 주문이 발생한 고객 ID를 추출하고, 메인 쿼리에서 해당 고객의 정보를 가져옵니다.
이렇게 하면 주문 내역과 고객 정보가 따로 저장되어 있어도 간단히 원하는 결과를 얻을 수 있습니다.

💡 TIP: WHERE 절 서브쿼리의 결과가 많을 경우, 관련 컬럼에 인덱스를 설정하면 조회 속도가 크게 향상됩니다.

또한, 단일 행을 반환하는 서브쿼리는 비교 연산자(=, >, < 등)와 함께 사용할 수 있습니다.
예를 들어, 주문 금액이 평균 이상인 고객만 조회하려면 다음과 같이 작성할 수 있습니다.

CODE BLOCK
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 절에서 서브쿼리를 사용하면 각 행에 대해 계산된 값을 컬럼으로 추가할 수 있습니다.
예를 들어, 각 고객의 주문 건수를 함께 표시하려면 다음과 같이 작성합니다.

CODE BLOCK
SELECT 
    CustomerID, 
    CompanyName,
    (SELECT COUNT(*) 
     FROM Orders 
     WHERE Orders.CustomerID = Customers.CustomerID) AS OrderCount
FROM Customers;

이 방법은 JOIN 대신 각 행별로 필요한 계산 값을 조회할 때 유용합니다.
하지만 데이터 양이 많으면 성능 저하가 발생할 수 있어 주의해야 합니다.

🔹 FROM 절에서의 서브쿼리

FROM 절에서 서브쿼리를 사용하면 가상의 테이블(파생 테이블, Derived Table)을 만들어 메인 쿼리에서 재활용할 수 있습니다.
이는 복잡한 집계나 가공 로직을 깔끔하게 분리하는 데 유용합니다.

CODE BLOCK
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으로 대체할 수 없는 복잡한 조건 로직을 구현할 때 유용합니다.

다음 예제는 각 고객이 마지막으로 주문한 날짜를 조회하는 상관 서브쿼리입니다.

CODE BLOCK
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으로 변경해 성능을 크게 향상시켰습니다.

CODE BLOCK
-- 기존 상관 서브쿼리
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은 두 개 이상의 테이블을 결합하여 하나의 결과 집합을 만들고, 서브쿼리는 쿼리 내부에서 또 다른 SELECT 문을 실행해 조건이나 데이터를 제공합니다.
경우에 따라 서브쿼리를 JOIN으로 대체하면 성능이 향상될 수 있습니다.
WHERE 절 서브쿼리는 언제 사용하는 것이 좋나요?
메인 쿼리의 조건이 동적으로 변하거나, 다른 테이블에서 조건 목록을 가져와야 할 때 효과적입니다.
예를 들어, 특정 기간에 주문이 있는 고객만 조회할 때 적합합니다.
SELECT 절에서 서브쿼리를 사용하면 성능에 문제가 없나요?
데이터 양이 적으면 문제가 없지만, 대량 데이터에서는 각 행마다 서브쿼리가 실행되어 성능이 저하될 수 있습니다.
이런 경우 집계 JOIN이나 윈도우 함수를 고려하는 것이 좋습니다.
FROM 절 서브쿼리와 뷰(View)의 차이는 무엇인가요?
FROM 절 서브쿼리는 쿼리 실행 시 즉시 생성되는 가상의 테이블이고, 뷰는 데이터베이스에 저장된 논리적 테이블입니다.
반복 사용이 잦으면 뷰를 만드는 것이 효율적입니다.
상관 서브쿼리를 사용할 때 주의할 점은 무엇인가요?
각 행마다 서브쿼리가 실행되므로 데이터 양이 많을 경우 성능이 크게 저하됩니다.
가능한 경우 JOIN이나 윈도우 함수로 대체하는 것이 좋습니다.
서브쿼리 안에서 ORDER BY를 사용해도 되나요?
가능합니다. 하지만 일부 경우에는 ORDER BY가 무시되거나 쿼리 구조에 따라 실행 계획에 영향을 줄 수 있습니다.
TOP 또는 OFFSET-FETCH 절과 함께 사용할 때 특히 주의하세요.
서브쿼리 대신 CTE(Common Table Expression)를 쓰는 게 더 좋은가요?
CTE는 가독성과 재사용성이 뛰어나며, 복잡한 쿼리 구조를 단계적으로 표현할 수 있습니다.
그러나 성능 면에서는 서브쿼리와 큰 차이가 없으니 상황에 맞게 선택하면 됩니다.
서브쿼리 안에서 다른 서브쿼리를 중첩해도 되나요?
가능합니다. 하지만 중첩이 깊어질수록 쿼리가 복잡해지고 성능이 떨어질 수 있으므로, 꼭 필요한 경우에만 사용하는 것이 좋습니다.

📊 서브쿼리 활용과 최적화 요약

MSSQL 서브쿼리는 쿼리 내부에서 또 다른 SELECT를 실행해 조건을 유연하게 구성하고, 복잡한 로직을 간결하게 표현할 수 있는 강력한 도구입니다.
WHERE 절에서는 조건 목록을 동적으로 생성해 필터링에 사용하고, SELECT 절에서는 각 행별 계산 값을 제공하며, FROM 절에서는 파생 테이블로 가공된 데이터를 제공합니다.
또한 상관 서브쿼리를 활용하면 각 행마다 다른 조건을 적용할 수 있지만, 성능 저하를 방지하기 위해 인덱스 최적화와 실행 계획 분석이 필수적입니다.
JOIN, CTE, 윈도우 함수 등 다른 기법과 비교하며 상황에 맞는 최적의 방식을 선택하는 것이 중요합니다.
이 글에서 소개한 예제와 팁을 참고해 실무 환경에서 서브쿼리를 효율적으로 활용해 보세요.


🏷️ 관련 태그 : MSSQL, 서브쿼리, SQL서브쿼리, 상관서브쿼리, 데이터필터링, 파생테이블, SQL성능, 인덱스최적화, SQL튜닝, CTE