쿼리실습 - 서브쿼리

imjingu·2023년 8월 26일
0

개발공부

목록 보기
419/481

서브 쿼리 SubQuery는 쿼리문 안에 또 다른 쿼리문이 포함된 구문.
단발적인 질문이 아닌 '복합적이고 단계적인 질문을 할 때는 여러 개의 쿼리를 중첩해서 사용.'
리턴하는 행과 열의 개수에 따라 다음과 같이 분류.

  • 단일행 서브쿼리 : 하나의 값만 리턴
  • 다중행 서브쿼리 : 여러개의 행을 리턴
  • 다중행열 서브쿼리 : 여러개의 열로 구성된 여러 개의 행인 테이블을 리턴

또 외부쿼리와의 관계에 따라 독립 서브쿼리와 연관 서브쿼리로도 구분. 서브 쿼리부터는 난이도가 올라감.

1. 단일행 서브쿼리

SELECT 명령은 DB 엔진에게 정보를 요구하는 질문.
SELECT로 할 수 있는 질문은 아주 짧은 단문만 가능하며 FROM 절이 하나밖에 없어 한 테이블에 있는 정보만 조사할 수 있었음.
하지만 실제 작업을 할때는 복잡한 여러 단계의 질문을 한꺼번에 하는 경우가 많이 생김. DB에서도 복잡한 쿼리를 실행할 수 있으며 그 방법이 바로 서브쿼리.
예를 들어 집계함수에서 다룬 최대 인구수를 가진 도시를 구하는 문제를 다시 풀어 보자면,

SELECT MAX(popu), name FROM tCity;


인구의 최대수는 MAX(popu)로 구할 수 있고, 도시의 이름은 name 필드를 읽으면 됨.
그러나 MAX 함수의 결과는 집계한 하나의 값이고 name 필드는 도시명 여러 개여서 이 둘을 같이 출력을 할 수 없음.
MAX(popu)는 최대 인구수를 집계한 것이 맞지만 name 도시중 어떤 도시가 그 인구수를 가진것인지는 알 수 없 음.
즉 왼쪽 열과 오른쪽 열이 연관성이 없음.
기계가 이 명령을 알아 들으려면 문법적으로 가능한 질문을 순서대로 해야 함.

SELECT name FROM tCity WHERE popu = 974;


결과는 서울.
최대 인구수를 조사하고 이 수로부터 도시의 이름을 구해야 하니 두 번의 쿼리가 필요.
목적은 달성했지만 사람이 첫 번째 쿼리의 결과를 확인한 후 두 번쨔 쿼리의 조건문을 직접 기입하는 식이라 불편.
두 개의 쿼리를 하나로 묶어 실행할 수 있다면 쉽게 풀 수 있는 문제.
이럴 때 쓰는 것이 서브쿼리.
서브쿼리는 다른 쿼리문안에 내장되어 있는 SELECT 문이며 연속적으로 실행할 쿼리를 하나로 합침. 이 때 서브쿼리는 감싸는 전체 쿼리를 외부쿼리라고 부름.
외부쿼리와 구분하고 실행 순서를 명확히 지정하기 위해 서브쿼리를 괄호로 감쌈.
서브쿼리로 최대 인구수를 가진 도시명을 구함.

SELECT name FROM tCity WHERE popu = (SELECT MAX(popu) FROM tCity);


한번에 서울을 구함. DB 엔진은 괄호 안의 서브쿼리를 먼저 실행하여 최대 인구수를 구함.
그리고 서브쿼리를 포함하고 있는 외부쿼리를 실행하여 popu가 서브쿼리가 구한 최대 인구수와 같은 도시 를 조사하여 출력.

상품 목록인 tItem에서 재고량이 가장 많은 상품을 조사.
먼저 최대 재고량을 찾음.

SELECT MAX(num) FROM tItem;


tItem 테이블의 num 필드 중 최대값은 80.
이제 이 최대량으로 부터 num 필드가 80개인 상품을 조사.

SELECT item FROM tItem WHERE num = 80;


결과는 청바지.
두 명령을 하나로 합침.
최대 재고량이 80인 자리에 이 값을 조사하는 서브 쿼리를 작성하여 두 쿼리를 하나로 합침.

SELECT item FROM titem WHERE num = (SELECT MAX(num) FROM titem);


단일행 서브쿼리는 하나의 결과만 리턴하며 주로 WHERE, HAVING 등의 조건절에 사용.
복합 질문의 앞쪽 질문에 해당하는 값을 서브 쿼리로 조사해 놓고 외부쿼리에서 그 결과값을 사용하는 식으로 작성.

2. 서브쿼리 중첩

서브쿼리는 독립적인 하나의 명령이기 때문에 외부쿼리와는 '다른 테이블을 읽을 수도 있음'. 어짜피 순차적으로 실행되므로 '두 퀴리문의 FROM 절에 각각 다른 테이블을 지정해도 상관이 없음'.
그래서 더 복잡한 형태의 질문도 가능,
청바지 배송비가 얼마인지 조사.
청바지는 상품 테이블에 있지만 배송비는 유형 테이블에 있어서 두 테이블을 읽어야 함. 먼저 tItem 테이블에서 청바지는 어떤 유형의 상품인지 조사.

SELECT category FROM tItem WHERE item= '청바지';


청바지는 '패션' 유형.
다음은 패션 유형의 배송비를 조사.
tCategory 테이블에서 앞 쿼리의 결과로 나온 '패션'유형의 배송비를 조사.

SELECT delivery FROM tCategory WHERE category = '패션';


배송비는 2,000원.
원하는 답을 구하는 쿼리문을 다 작성했으므로 이제 둘을 하나로 합침.
WHERE 절의 '패션' 자리에 상수 대신 '패션'이라는 결과를 만들어 내는 서브쿼리를 작성하고 괄호를 둘러쌈.

SELECT delivery FROM tCategory WHERE category = (SELECT category FROM tItem WHERE item = '청바지');


DB 엔진이 서브쿼리를 먼저 실행하고 그 결과를 외부쿼리에 쓰듯이 사고의 흐름 순서대로 명령문을 하나씩 작 성하면 됨.
이 질문을 말로 표현해 보면 '청바지의 유형을 조사하고 그 유형의 배송비를 출력하라' 임.

서브쿼리의 중첩 횟수에는 제약이 없어 서브쿼리내에 또 다른 서브쿼리를 포함할 수 있음. 서브쿼리의 중첩을 사용하면 언제든지 복잡한 질문도 할 수 있는데 단계별로 '7만원짜리 상품을 구입한 사람의 나이' 를 구해봄.
먼저 7만원짜리 상품이 무엇인지 조사.

SELECT item FROM tItem WHERE price = 70000;


상품 정보는 tItem 테이블에 있고, 70,000원짜리 상품을 조사해 보면 전자담배가 나옴. 다음은 이 상품을 구입한 사람이 누구인지 조사. 구입내역은 tOrder 테이블에 있음.

SELECT member FROM tOrder WHERE item = (SELECT item FROM tItem WHERE price = 70000);


상품명을 tOrder 테이블의 item 필드와 비교하여 member 필드를 읽으면 방자가 구입했음을 알 수 있음. 마지막으로 방자의 나이를 tMember에서 읽음.

SELECT age FROM tMember WHERE member = (SELECT member FROM tOrder WHERE item = (SELECT item FROM tItem WHERE price = 70000));

28세로 조사됨,
가장 안쪽에 있는 쿼리부터 순서대로 실행하여 최종적으로 원하는 값을 구해 출력.
이 질문에 쿼리가 세 개씩이나 필요한 이유는 상품, 주문, 회원에 대한 정보가 세 개의 테이블에 흩어져 있기 때문.
각 테이블을 순서대로 검색
최초 알고 있던 정보는 70,000원이라는 가격뿐인데 이 정보로부터 상품을 찾고, 상품으로 사람을 찾고, 사람으로부터 나이를 차례로 찾음.
만약 70,000원짜리 상품이 없다거나 구입한 사람이 없으면 결과셋은 없음. 반대로 상품이 여러 개이거나 구입한 사람이 둘 이상이라며면 에러 처리되는데 이때는 TOP 1 (LIMIT 1) 을 넣어 첫 구입자를 찾으면 됨.

3. 다중행 서브쿼리

서브쿼리의'결과가 하나뿐인 유형'을 단일행 서브쿼리.
단일값이므로 조건절에서 =, <, > 등의 비교 연산자와 함께 사용할 수 있음.
비교 연산자는 필드의 값과 비교하는 것이어서 '우변이 반드시 하나의 확정된 값'이어야 함.
이에 비해 여러 개의 결과를 리턴하는 것이 다중행 서브쿼리.
단일값이 아닌 목록을 리턴하기 때문에 값끼리 비교하는 비교 연산자와 함께 사용할 수 없음.

= 연산자로 비교하려면 조건절의 쿼리문은 반드시 하나의 단일 값을 리턴해야만 함. 꼭 조사하려면 결과 셋 중 하나의 값만 조사해서 비교하면 됨.
구입한 상품 중 하나의 상품만 리턴하면 = 연산자로 비교할 수 있음.
그러나 이것이 원하는 결과였는지는 잘 생각해봐야 함.
애초에 질문대로 향단이가 구입한 모든 상품의 가격을 알고 싶다면 = 연산자로 비교해서는 안되며 IN 연산자를 사용.

SELECT item, price FROM tItem WHERE item IN (SELECT item FROM tOrder WHERE member = '향단');

IN은 = 과 달리 여러 개의 값과 비교.
서브쿼리가 두 개 이상의 값을 리턴하면 결과셋을 괄호 안에 나열하며 IN 연산자는 이 값을 순서대로 비교. 서브쿼리까지 실행한 직후의 외부쿼리는 다음과 같음.
2개의 상품에 대해 이름과 가격을 같이 출력.

SELECT item, price FROM tItem WHERE item IN ('대추', '사과');


단일행을 리턴하는 서브 쿼리는 = 연산자와 비교하고 다중행을 리턴하는 서브쿼리는 IN 연산자와 비교. 물론 IN 연산자를 쓰면 하나든, 여러 개이든 비교할 수 있지만 집계 함수의 결과는 항상 하나여서 이때는 = 연산자를 쓰는 것이 더 쉬움.
결과값이 아예 없는 것은 어떤 연산자로 비교해도 상관 없음.

4. 다중열 서브쿼리

단일행, 다중행 서브쿼리는 '결과셋의 컬럼이 하나'밖에 없으며 그래서 특정값과 비교할 수 있음. 이에 비해 다중열 서브쿼리는 결과셋의 컬럼이 여러 개이며 한꺼번에 여러 값과 비교.
오라클과 마리아디비는 다중열 서브쿼리를 잘 지원하지만 SQL Server는 아직 지원하지 않음.
이런 서브쿼리가 왜 필요한지 단계별로 문제를 풀어 봄.
윤봉길과 같은 부서에 근무하는 같은 성별의 직원 목록을 조사.
이 문제를 풀려면 먼저 윤봉길이 어느 부서에 근무하고 성별이 무엇인지 조사해야 하며 그 결과로부터 조건에 맞는 직원을 찾으면 됨.

SELECT depart, gender FROM tStaff WHERE name = '윤봉길';

SELECT * FROM tStaff WHERE depart = '영업부' AND gender = '남';


조사해 보니 윤봉길은 영업부에 근무하는 남자 직원. 이 조건에 맞는 직원 목록을 조사해서 출력하면 됨. 윤봉길 자신까지 포함하여 5명의 직원이 있음.
쉬운 문제이지만 작업을 두 단계로 나누어 사람이 결과를 확인한 후 다시 명형을 내려야 하니 불편.

SELECT * FROM tStaff WHERE depart = (SELECT depart FROM tStaff WHERE name = '윤봉길') AND gender = (SELECT gender FROM tStaff WHERE name = '윤봉길');

임의의 직원에 대해 똑같은 조사를 하려면 두 번 손이 가게 되어서 번거로움.
이럴 때 조사 후 조건에 맞는 목록 출력까지 한 번에 수행하는 서브쿼리가 필요.
다음 서브쿼리의 직원명을 바꾸면 같은 부서, 같은 성별의 직원 목록을 바로 조사할 수 있음.
이번에는 안중근을 조사.
SELECT * FROM tStaff WHERE depart = (SELECT depart FROM tStaff WHERE name = '안중근') AND gender = (SELECT gender FROM tStaff WHERE name = '안중근');


직관적이고 쉽지만 부서와 성별을 각각의 서브쿼리로 개별 조사하다 보니 총 3번의 쿼리를 수행. 다중열 서브쿼리를 사용하면 두 개의 컬럼을 리턴한 후 한꺼번에 비교.

SELECT * FROM tStaff WHERE (depart, gender) = (SELECT depart, gender FROM tStaff WHERE name = '안중근');


WHERE 절에 비교 대상 필드를 괄호 안에 (depart, gender)로 적으면 서브쿼리의 컬럼과 1:1로 비교하여 두 필 드가 일치하는 레코드를 검색.
여러 필드를 한꺼번에 비교할 수 있어 편리.
단 일괄 비교가 성립하려면 비교 대상과 서브쿼리의 컬럼 개수는 반드시 일치해야 함.

0개의 댓글