04 - 2 서브 쿼리
서브 쿼리(subquery)는 쿼리 안에 포함되는 또 다른 쿼리를 말합니다. 서브 쿼리는 조인하지 않은 상태에서 다른 테이블과 일치하는 행을 찾거나, 조인 겨로가를 다시 조인할 때 사용할 수 있습니다.
- 서브 쿼리의 특징
- 반드시 소괄호로 감싸 사용한다.
- 주 쿼리를 실행하기 전에 1번만 실행된다.
- 비교 연산자에 서브 쿼리를 사용하는 경우 서브 쿼리를 오른쪽에 기술해야 한다.
- 내부에는 정렬 구문인 ORDER BY 문을 사용할 수 없다.
WHERE 문에 서브 쿼리 사용하기
서브 쿼리 중에서도 WHERE 문에 사용하는 서브 쿼리를 중첩 서브 쿼리(nested subquery)라고 부릅니다. 중첩 서브 쿼리는 조건문의 일부로 사용하는데, 다시 말해 또 다른 SELECT 문을 사용한 결과를 주 쿼리의 조건값으로 사용합니다.그리고 서브쿼리를 비교 연산자 =, <, <=, >, >=, <>와 함께 사용할 때는 반드시 서브 쿼리의 반환 결과가 1건 이하여야 합니다. 만약 서브 쿼리의 반환 결과가 2건 이상인 경우에는 비교 연산자가 아닌 다중 행 연산자를 사용해야 합니다.
다중 행 연산자 | 설명 |
IN | 서브 쿼리의 결과에 존재하는 임의의 값과 같은 조건 검색 |
ANY | 서브 쿼리의 결과에 존재하는 어느 하나의 값이라도 만족하는 조건 검색 |
EXISTS | 서브 쿼리의 결과를 만족하는 값이 존재하는지 여부 확인 |
ALL | 서브 쿼리의 결과에 존재하는 모든 값을 만족하는 조건 검색 |
단일 행 서브 쿼리
단일 행 서브 쿼리란 서브 쿼리의 결과가 1행만 반환되는 쿼리입니다.
-- WHERE 문에 사용하는 단일 행 서브 쿼리의 기본 형식
SELECT [열 이름]
FROM [테이블]
WHERE [열] = (SELECT [열] FROM [테이블])
-- 단일 행 서브 쿼리 : 검색 성공
SELECT * FROM nasdaq_company
WHERE symbol = (SELECT symbol FROM nasdaq_company WHERE symbol IN ('MSFT'))
-- 단일 행 서브 쿼리 : 비교 연산자 규칙에 어긋나 오류 발생
SELECT * FROM nasdaq_company
WHERE symbol = (SELECT symbol FROM nasdaq_company WHERE symbol IN ('MSFT', 'AMD', 'AMZN'))
다중 행 서브 쿼리
다중 행 서브 쿼리란 서브 쿼리에서 반환되는 결과가 2행 이상인 경우를 말합니다. 이처럼 서브 쿼리가 다중 행을 반환하는 경우 여러 가지 방법을 사용할 수 있습니다.
IN 문, NOT IN 문
-- WHERE 문에 사용하는 다중 행 서브 쿼리의 기본 형식
SELECT [열 이름]
FROM [테이블]
WHERE [열] IN (SELECT [열] FROM [테이블])
-- 단일 행 서브 쿼리와 다중 행 서브 쿼리의 사용법 비교
SELECT * FROM nasdaq_company
WHERE symbol IN ('MSFT', 'AMD', 'AMZN')
SELECT * FORM nasdaq_company
WHERE symbol IN (SELECT symbol FROM nasdaq_company WHERE symbol IN ('MSFT', 'AMD', 'AMZN'))
-- 다중 행 서브 쿼리 : IN 사용
-- 3개 테이블 조인
SELECT
a.industry,
c.symbol,
c.company_name,
c.ipo_name,
c.sector
FROM industry_group AS a
INNER JOIN industry_group_symbol AS b ON a.num = b.num
INNER JOIN nasdaq_company AS c ON b.symbol = c.symbol
WHERE a.industry = N'자동차'
ORDER BY symbol
-- IN 문 서브 쿼리
SELECT *
FROM nasdaq_company
WHERE symbol IN (
SELECT symbol FRORM industry_group AS a
INNER JOIN industry_group_symbol AS b ON a.num = b.num
WHERE a.industry = N'자동차'
)
-- 다중 행 서브 쿼리 : NOT IN 사용
-- NOT IN 문 서브 쿼리
SELECT *
FROM nasdaq_company
WHERE symbol NOT IN (
SELECT symbol FROM industry_group AS a
INNER JOIN industry_group_symbol AS b ON a.num = b.num
WHERE a.industry = N'자동차'
)
ANY 문
ANY 문은 서브 쿼리 결과에서 값이 하나라도 만족하는 조건을 검색합니다. 언뜻 보면 비교 연산자가 잘못 쓰인 듯 보일 수 있지만, ANY 문을 함께 사용했으므로 서브 쿼리의 결괏값이 여러 개여도 일치하는 모든 행을 주 쿼리에서 검색해 반환할 수 있습니다.
-- 다중 행 서브 쿼리 : ANY로 같은 값 반환
SELECT * FROM nasdaq_company
WHERE symbol = ANY (
SELECT symbol FROM nasdaq_company
WHERE symbol IN ('MSFT', 'AMD', 'AMZN')
)
-- 다중 행 서브 쿼리 : < ANY로 최솟값 반환
SELECT * FROM nasdaq_company
WHERE symbol < ANY (
SELECT symbol FROM nasdaq_company
WHERE symbol IN ('MSFT', 'LTCH', 'ZY')
)
-- 다중 행 서브 쿼리 : > ANY로 최댓값 반환
SELECT * FROM nasdaq_company
WHERE symbol > ANY (
SELECT symbol FROM nasdaq_company
WHERE symbol IN ('MSFT', 'LTCH', 'ZY')
)
EXISTS 문, NOT EXISTS 문
EXISTS 문은 조건의 결괏값이 있는지 없는지를 확인해 1행이라도 있으면 TRUE, 없으면 FALSE를 반환합니다.
-- 다중 행 서브 쿼리 : EXISTS 문의 결괏값이 TRUE일 때
SELECT * FROM nasdaq_company
WHERE EXISTS (
SELECT symbol FROM nasdaq_company
WHERE symbol IN ('MSFT', 'AMD', 'AMZN')
)
-- 다중 행 서브 쿼리 : EXISTS 문의 결괏값이 FALSE일 때
SELECT * FROM nasdaq_company
WHERE EXISTS (
SELECT symbol FROM nasdaq_company
WHERE symbol IN ('123', '456', '789')
)
-- 다중 행 서브 쿼리 : NOT EXISTS 문의 결괏값이 TRUE일 때
SELECT * FROM nasdaq_company
WHERE NOT EXISTS (
SELECT symbol FROM nasdaq_company
WHERE symbol IN ('123', '456', '789')
)
ALL 문
ALL 문은 서브 쿼리 결괏값에 있는 모든 값을 만족하는 조건을 주 쿼리에서 검색해 결과를 반환합니다.
-- 다중 행 서브 쿼리 : ALL 문 사용
SELECT * FROM nasdaq_company
WHERE symbol = ALL (
SELECT symbol FROM nasdaq_company
WHERE symbol IN ('MSFT', 'AMD', 'AMZN')
)
FROM 문에 서브 쿼리 사용하기
FROM 문에 사용하는 서브 쿼리 결과는 조인할 수 있으므로 쿼리를 논리적으로 격리할 수 있습니다. 보통 인라인뷰(inline view)라고 하며 이후 용어를 혼용합니다.
-- FROM 문에 사용하는 서브 쿼리는 기본 형식
SELECT [열 이름]
FROM [테이블] AS a
INNER JOIN -- OUTER JOIN도 사용할 수 있다
(SELECT [열] FROM [테이블] WHERE [열] = [값]) AS b ON [a.열] = [b.열]
WHERE [열] = [값]
-- 인라인뷰에 INNER JOIN을 활용한 서브 쿼리
-- 테이블 조인
SELECT
a.symbol, a.company, a.ipo_year, a.sector, a.industry,
b.date, b.[open], b.[high], b.[low], b.[close], b.adj_close, b.volume
FROM nasdaq_company AS a
INNER JOIN stock AS b ON a.symbol = b.symbol
WHERE a.symbol = 'MSFT'
AND b.date >= '2021-10-01'
AND b.date < '2021-11-01'
-- 서브 쿼리
SELECT
a.symbol, a.company, a.ipo_year, a.sector, a.industry,
b.date, b.[open], b.[high], b.[low], b.[close], b.adj_close, b.volume
FROM nasdaq_company AS a
INNER JOIN (SELECT symbol, date, [open], [high], [low], [close], adj_close, volume
FROM stock
WHERE symbol = 'MSFT'
AND date >= '2021-10-01'
AND date < '2021-11-01'
) AS b ON a.symbol = b.symbol
SELECT 문에 서브 쿼리 사용하기
SELECT 문에 사용하는 서브 쿼리는 반드시 1개의 행을 반환해야 하므로 SUM, COUNT, MIN, MAX 등의 집계 함수와 함께 사용하는 경우가 많습니다. 하지만 이렇게 하면 성능 문제가 생기기 쉬우므로 SELECT 문에서는 서브 쿼리를 집계 함수와 함께 사용하지 않는 게 좋습니다. SELECT 문에 사용된 서브 쿼리는 스칼라 서브 쿼리(scalar subquery)라고 부르며, 스칼라 서브 쿼리는 1개 이상 사용할 수 있습니다.
-- 스칼라 서브 쿼리의 기본 형식
SELECT [열 이름],
(SELECT <집계 함수> [열 이름] FROM [테이블 2]
WHERE [테이블 2.열] = [테이블 1.열]) as Alias
FROM [테이블 1]
WHERE [조건]
-- INNER JOIN 문을 적용한 쿼리와 스칼라 서브 쿼리 비교
-- 테이블 조인
SELECT
a.symbol, a.company, a.ipo_year, a.sector, a.industry,
b.date, b.[open], b.[high], b.[low], b.[close], b.adj_close, b.volume
FROM nasdaq_company AS a
INNER JOIN stock AS b ON a.symbol = b.symbol
WHERE a.symbol = 'MSFT'
AND b.date >= '2021-10-01'
AND b.date < '2021-11-01'
-- 서브 쿼리
SELECT
a.symbol,
(SELECT company_name FROM nasdaq_company AS b WHERE b.symbol = a.symbol) AS company_name,
(SELECT ipo_year FROM nasdaq_company AS b WHERE b.symbol = a.symbol) AS ipo_year,
(SELECT sector FROM nasdaq_company AS b WHERE b.symbol = a.symbol) AS sector,
(SELECT industry FROM nasdaq_company AS b WHERE b.symbol = a.symbol) AS industry,
a.date, a.[open], a.[high], a.[low], a.[close], a.adj_close, a.volume
FROM stack AS a
WHERE a.symbol = 'MSFT'
AND b.date >= '2021-10-01'
AND b.date < '2021-11-01'
실전 SQL
퀴즈 3. nasdaq_company 테이블에서 sector 열의 값이 Energy인 데이터 중에 industry_group_symbol이 테이블에 포함되지 않은 symbol, company_name을 검색하세요.
-- 인라인뷰
SELECT
a.symbol, a.company_name, s_b.symbol
FROM nasdaq_company
LEFT OUTER JOIN (
SELECT symbol
FROM industry_group AS a
INNER JOIN industry_group_symbol AS b ON a.num = b.num
WHER a.industry = 'Oil') AS s_b ON a.symbol = s_b.symbol
WHERE a.sector = 'Energy'
AND s_b.symbol IS NULL
-- 중첩 서브 쿼리
SELECT symbol, company_name
FROM nasdaq_company
WHERE sector = 'Energy'
AND symbol NOT IN (
SELECT symbol
FROM industry_group AS a
INNER JOIN industry_group_symbol AS b ON a.num = b.num
WHERE a.industry = 'Oil'
)