개발/기타 개인 공부

SQL 기본 6. 서브 쿼리(Subquery, Where 절)

잠수돌침대 2022. 12. 29. 21:15

당초에 해당 내용은 나중에 공부할 계획이었지만 SQL 구문의 여러 예시와 실습을 진행하면서 서브 쿼리가 간간히 사용되는 것을 알 수 있었다. 본인이 희망하는 분야인 데이터 분석(Analytic SQL) 파트에서는 그렇게 부각되는 내용은 아니다만, 다른 코드나 논리적인 쿼리문을 작성하고자 할 때 충분한 윤활유 역할을 해줄 수 있을 것 같아 추가적으로 서브 쿼리에 대한 공부를 시작하게 되었다.

 

서브 쿼리를 사용한다면 대용량 데이터를 처리하는 입장에서 성능이 저하될 우려가 있음. 이는 서브 쿼리 자체의 문제인 코드가 길어진다는 점과 결부된다.

 

서브 쿼리가 정확히 뭘까?

 

서브 쿼리는 하나의 쿼리 내에 또 다른 쿼리가 포함되는 쿼리를 의미한다. 서브 쿼리는 기본적으로 메인 쿼리(Main Query) 내에 포함되어 있는 관계이며, 업무적인 SQL 관계를 직관적으로 표현하면서 효과적으로 필터링하고 싶을 때 주로 사용한다(where 절로 사용할 경우).

 

서브가 있다면 주(Main) 또한 있다는 것. 상기 코드에서 where 절 이후에 진행되는 쿼리를 서브 쿼리, 서브 쿼리를 포함한 전체 코드를 메인 쿼리라고도 부른다. 

 

예전에는 where 절만을 이용하여 서브 쿼리를 작성할 수 있었는데, 현재는 select 절에 사용되는 스칼라 서브 쿼리, from 절에 사용되는 인라인 뷰를 사용하여 쿼리를 작성할 수 있게 되었다.    

 

아래의 예시는 where 절을 이용한 서브 쿼리의 예시이다.

select * from a_table where money >= (select avg(money) from a_table);
-- 전체 돈의 평균보다 많은 돈들만을 a_table에서 추출

 

아래의 예시는 select 절에 사용되는 서브 쿼리의 예시이다(스칼라 서브 쿼리).

select one_name
	(select two_name from two_class b where a.grade=b.grade) as two_name
from one_class a;
-- one_class와 two_class의 같은 학년을 묶는 작업을 포함하는 서브 쿼리
-- 이를 스칼라 서브 쿼리라고 부른다.

스칼라 서브 쿼리를 통하여 join의 역할을 겸하는 것을 볼 수 있다. 자세한 내용은 추후에 관련 공부를 진행할 때 언급하도록 하고 지금은 '이런 게 있구나' 맛만 보고 넘어가자.

 

마지막으로 from 절에 사용되는 서브 쿼리(인라인 뷰)의 예시이다.

select a_name
from (select * from b_table b);

from 절에서는 다른 서브 쿼리와 다르게 종속적인 메인/서브의 관계라 부르기 모호하다는 특징이 있다. 이를 서브 쿼리 개념보다는 인라인 뷰라는 하나의 개념으로 넘어가는 것이 좋을 것 같다(직접적으로 공부하지는 않을 예정).

 

이처럼 여러 서브 쿼리가 존재하지만 가장 예전부터, 그리고 보편적으로 사용되는 where 절을 이용한 서브 쿼리를 우선적으로 소개하고자 한다.

 

where 절을 이용한 서브 쿼리의 주요 특징

 

서브 쿼리는 메인 쿼리에 where 조건으로 값을 전달하거나 메인 쿼리와 연결되어 메인 쿼리의 복잡한 필터링 작업을 수행하는데 사용한다.

이때, 서브 쿼리에 사용되는 컬럼값만 메인 쿼리로 전달이 가능하며, 컬럼 자체는 메인 쿼리에 사용될 수 없음을 유의한다. 반대로 종속 관계이기에 메인 쿼리의 컬럼은 서브 쿼리에서 사용이 가능하다!

 

아래의 코드는 실행이 되는 코드와 되지 않는 쿼리의 예시를 보여주고 있다.

select a.*
from a_class a where a.grade in (select grade from b_class b where b.money >= 1000)
-- 가능

select a.*, b.name -- 서브 쿼리의 b 내용은 메인 쿼리에서 사용 불가
from a_class a where a.grade in (select grade from b_class b where b.money >= 1000)
-- 불가능

select a.* from a_class a
where exists (select grade from b_class b where a.grade = b.grade)
-- 서브 쿼리가 메인 쿼리의 컬럼(a)을 활용하는 것은 가능

 

또한 서브 쿼리와 메인 쿼리를 연결할 때 메인 쿼리의 집합 레벨이 변경되지 않는다. 메인 쿼리와 서브 쿼리를 연결할 때 서브 쿼리는 연결 컬럼으로 무조건 1 집합(unique)이 되므로 메인 쿼리의 집합 레벨을 변경하지 않는다.   

이는 서브쿼리의 반환값은 무조건 중복이 제거된 unique 집합임을 의미하기도 한다(상관 서브 쿼리).

 

distinct로 한 번 필터링을 거친 뒤 서브 쿼리 절이 반환됨을 알고 있으면 될 것 같다.

 

몇 가지 헷갈릴 수 있는 서브 쿼리

비상관 서브 쿼리 : 서브 쿼리에서 자체적으로 메인 쿼리에 값을 전달하는 형태를 비상관(non-correlated) 서브 쿼리라고 한다.

상관 서브 쿼리 : 메인 쿼리에서 서브 쿼리에 연결 컬럼으로 연결한 뒤(where 절을 이용한 등호 묶음 처리) 메인 쿼리에서 값을 서브 쿼리로 전달하는 형태를 상관(correlated) 서브 쿼리라 한다.

 

단일 행 서브 쿼리 : 서브 쿼리 연결 연산자가 단순 비교 연산자(같거나 대소 판별)라면 단일 행을 반환한다.

다중 행 서브 쿼리 : in, exists, all/any와 같은 연산자를 사용한다면 다중 행을 반환할 수 있다.

 

상기 내용을 표로 시각화한다면 아래의 표와 같다.

  비상관 서브 쿼리  상관 서브 쿼리
단일 행 서브 쿼리 비교 연산자(=, <, >, >=, <=, <>)  비교 연산자(=, <, >, >=, <=, <>) 
다중 행 서브 쿼리 in, not in exists, not exists

 

 

필자는 Python에 익숙해져있는지라 해당 내용을 다루면서 'avg 하나 사용하는데도 저렇게 긴 서브 쿼리를 작성해야 하나'라는 생각이 먼저 들었다.

 

복잡한 필터링 = 서브 쿼리 작성이라는 생각을 하며 쿼리를 짜는 습관을 들이면 좋을 것 같다.