Subquery(서브쿼리)

이수연·2024년 8월 9일

Subquery(서브쿼리)

  • Subquery(서브쿼리): 하나의 SQL문 안에 포함되어 있는 또 다른 SQL문을 말한다.
  • 메인쿼리가 서브쿼리를 포함하는 종속적인 관계이다.
    • 서브쿼리는 메인 쿼리의 칼럼 사용 가능
    • 메인쿼리는 서브쿼리의 칼럼 사용 불가
  • 서브쿼리 사용 시 주의점
    • 서브쿼리는 괄호로 묶어서 사용
    • 단일 행 또는 복수 행 비교 연산자와 함께 사용 가능
    • Subquery 에서는 Order By 사용 X
  • 서브쿼리 종류
    • Scalar Subquery(스칼라 서브쿼리): SELECT절에 사용
    • Inline View(인라인뷰): FROM절에 사용
    • Nested Subquery(중첩 서브쿼리): WHERE절에 사용

Scalar Subquery(스칼라 서브쿼리): SELECT절에 사용

  • 결과는 하나의 Column이어야 한다.
SELECT col1, (SELECT col2 FROM table2 WHERE condition)
FROM table1
WHERE condition
  • 예제: 서울은평경찰서의 강도 검거 건수와 서울시 경찰서 전체의 평균강도검거건수를 조회
mysql> select case_number,
    -> (select avg(case_number)
    -> from crime_status
    -> where crime_type='강도' and status_type like '검거') avg
    -> from crime_status
    -> where police_station like '은평' and crime_type like '강도' and status_type like '검거';
+-------------+--------+
| case_number | avg    |
+-------------+--------+
|           1 | 4.1935 |
+-------------+--------+
1 row in set (0.02 sec)

Inline View(인라인뷰): FROM절에 사용

  • 메인쿼리에서는 인라인 뷰에서 조회한 Column만 사용 가능
SELECT a.column, b.column
FROM table1 a, (SELECT col1, col2 FROM table2) b
WHERE condition
  • 예제: 경찰서별 가장 많이 발생한 범죄 건수와 이때의 범죄 유형을 조회
SELECT c.police_station, c.crime_type, c.case_number
FROM crime_status c, 
	(SELECT police_station, max(case_number) count
     FROM crime_status
     WHERE status_type LIKE '발생'
     GROUP BY police_station) m
WHERE c.police_station = m.police_station
	AND c.case_number = m.count 

mysql> SELECT c.police_station, c.crime_type, c.case_number
    -> FROM crime_status c, 
    -> (SELECT police_station, max(case_number) count
    ->      FROM crime_status
    ->      WHERE status_type LIKE '발생'
    ->      GROUP BY police_station) m
    -> WHERE c.police_station = m.police_station
    -> AND c.case_number = m.count ;
+----------------+------------+-------------+
| police_station | crime_type | case_number |
+----------------+------------+-------------+
| 중부           | 폭력       |         997 |
| 종로           | 폭력       |         964 |
| 남대문         | 절도       |         699 |
| 서대문         | 폭력       |        1292 |
| 혜화           | 폭력       |         747 |
| 용산           | 폭력       |        1617 |
| 성북           | 폭력       |         672 |
| 동대문         | 폭력       |        1784 |
| 마포           | 폭력       |        1844 |
| 영등포         | 폭력       |        2701 |
| 성동           | 폭력       |        1223 |
| 동작           | 폭력       |        1631 |
| 광진           | 폭력       |        1676 |
| 서부           | 폭력       |         748 |
| 강북           | 폭력       |        1817 |
| 금천           | 폭력       |        1471 |
| 중랑           | 폭력       |        2022 |
| 강남           | 폭력       |        2283 |
| 관악           | 폭력       |        2614 |
| 강서           | 폭력       |        2445 |
| 강동           | 폭력       |        1942 |
| 종암           | 폭력       |         758 |
| 구로           | 폭력       |        2204 |
| 서초           | 폭력       |        1750 |
| 양천           | 폭력       |        1582 |
| 송파           | 폭력       |        2675 |
| 노원           | 폭력       |        2163 |
| 방배           | 폭력       |         423 |
| 은평           | 폭력       |        1092 |
| 도봉           | 폭력       |        1234 |
| 수서           | 폭력       |        1394 |
+----------------+------------+-------------+
31 rows in set (0.02 sec)

Nested Subquery(중첩 서브쿼리): WHERE절에 사용

Single Row: 하나의 행을 검색하는 서브쿼리

  • 서브쿼리가 비교연산자(=, >, >=, <, <=, <>, !=)와 사용되는 경우, 서브쿼리의 검색 결과는 한 개의 결과값을 가져야 한다. (두개 이상인 경우 에러)
SELECT col_name
FROM tablename
WHERE col_name = (SELECT col_name //이것의 결과값이 딱 하나여야 함. 
                  FROM tablename
                  WHERE condition)   
               
mysql> select name from celeb where name = (select host from snl_show);
ERROR 1242 (21000): Subquery returns more than 1 row
// 비교연산자 '='를 사용했을 때, 아래와 같이 서브쿼리의 결과가 한 개 이상이기 때문에 에러가 발생

mysql> select host from snl_show;
+-----------+
| host      |
+-----------+
| 강동원    |
| 유재석    |
| 차승원    |
| 이수현    |
| 이병헌    |
| 하지원    |
| 제시      |
| 조정석    |
| 조여정    |
| 옥주현    |
+-----------+
10 rows in set (0.02 sec)

// 특정 id를 지정해서 하나의 결과만 가져오도록 하면 에러가 발생하지 않음.
mysql> select name from celeb where name = (select host from snl_show where id =1);
+-----------+
| name      |
+-----------+
| 강동원    |
+-----------+
1 row in set (0.02 sec)

Multiple Row: 하나 이상의 행을 검색하는 서브쿼리

IN

  • 서브쿼리 결과 중에 포함될 때: IN 을 사용한 서브쿼리
SELECT col_name
FROM tablename
WHERE col_name IN (SELECT col_name //이것의 결과값이 딱 하나여야 함. 
                   FROM tablename
                   WHERE condition)
// SNL에 출연한 영화배우를 조회
mysql> select host
    -> from snl_show
    -> where host in (select name
    -> 				  from celeb
    -> 				  where job_title like '%영화배우%');
+-----------+
| host      |
+-----------+
| 강동원    |
| 차승원    |
+-----------+
2 rows in set (0.02 sec)

EXISTS

  • EXISTS: 서브쿼리 결과에 값이 있으면 반환
    // 범죄 검거 혹은 발생 건수가 2000건 보다 큰 경찰서 조회
select name
from police_station p
where exists (select police_station
			  from crime_status c
              where p.name = c.reference and case_number > 2000);
select reference
from crime_status
where case_number > 2000);
+--------------------------+
| name                     |
+--------------------------+
| 서울강남경찰서           |
| 서울강서경찰서           |
| 서울관악경찰서           |
| 서울구로경찰서           |
| 서울노원경찰서           |
| 서울송파경찰서           |
| 서울영등포경찰서         |
| 서울중랑경찰서           |
+--------------------------+
8 rows in set (0.02 sec)

ANY

  • ANY: 서브쿼리 결과 중에 최소한 하나라도 만족하면 (비교연산자 사용)
  • IN과 다른 점은, 비교 연산자와 함께 사용이 가능하다는 점
    • '=ANY (Subquery)'는 'IN (Subquery)'와 동일함.
select name
from celeb
where name = ANY (select host
 				  from snl_show);
+-----------+
| name      |
+-----------+
| 강동원    |
| 유재석    |
| 차승원    |
| 이수현    |
+-----------+
4 rows in set (0.01 sec)

All

  • All: 서브쿼리 결과를 모두 만족하면 (비교연산자 사용)
select name
from celeb
where name = ANY (select host
 				  from snl_show
                  where id = 1);
+-----------+
| name      |
+-----------+
| 강동원    |
+-----------+
1 row in set (0.01 sec)

Multiple Column 서브쿼리(연관 서브쿼리): 하나 이상의 열을 검색하는 서브쿼리

  • 서브쿼리 내에 메인쿼리 컬럼이 같이 사용되는 경우
select column_names
from tablename a
where (a.col1, a.col2, ...) IN (select b.col1, b.col2, ...
								from tablename b
                                where a.column_name = b.column_name);

// 강동원과 성별, 소속사가 같은 연예인의 이름, 성별, 소속사를 조회
mysql> select name, sex, agency
    -> from celeb
    -> where (sex, agency) in (select sex, agency from celeb where name = '강동원');
+-----------+------+----------------------+
| name      | sex  | agency               |
+-----------+------+----------------------+
| 강동원    | M    | YG엔터테인먼트       |
| 차승원    | M    | YG엔터테인먼트       |
+-----------+------+----------------------+
2 rows in set (0.02 sec)

실습 예제

//1. oil_price 테이블에서 셀프주유의 평균가격과 SK에너지의 가장 비싼 가격을 Scalar Query를 사용하여 조회
mysql> select avg(가격) 셀프주유평균가격, (select max(가격) from oil_price where 상표 like '%SK%')  SK최고가격  from oil_price where 셀프='Y';
+--------------------------+----------------+
| 셀프주유평균가격         | SK최고가격     |
+--------------------------+----------------+
|                1517.6000 |           2160 |
+--------------------------+----------------+
1 row in set (0.07 sec)

//2. oil_price 테이블에서 상표별로 가장 비싼 가격과 상호를 Inline View를 사용하여 조회하세요. 
mysql> select o.상호, o.상표, m.max_price 
       from oil_price o, (
        	select 상표, max(가격) max_price 
            from oil_price group by 상표
            ) m 
       where o.상표=m.상표 and o.가격=m.max_price;
+-------------------+--------------------+-----------+
| 상호              | 상표               | max_price |
+-------------------+--------------------+-----------+
| ()MS주유소      | 현대오일뱅크       |      1498 |
| 쌍문주유소        | S-OIL              |      1509 |
| 뉴서울(강남)      | SK에너지           |      2160 |
| 신길주유소        | GS칼텍스           |      1498 |
+-------------------+--------------------+-----------+
4 rows in set (0.06 sec)

//3. 평균가격보다 높은 주유소 상호와 가격을 Nested Subquery를 사용하여 조회하세요.
mysql> select  상호, 가격 from oil_price where 가격 > (select avg(가격) from oil_price);
+--------------------+--------+
| 상호               | 가격   |
+--------------------+--------+
| 21세기주유소       |   1598 |
| 살피재주유소       |   1635 |
| 뉴서울(강남)       |   2160 |
+--------------------+--------+
3 rows in set (0.07 sec)

//4. 3번에서 조회한 주유소에서 주유한 연예인의 이름과 주유소, 주유일을 Nested Query를 사용하여 조회하세요. (refueling 테이블)
mysql> select 이름, 주유소, 주유일 from refueling where 주유소 in (select 상호 from oil_price where 가격 > (select avg(가격) from oil_price));
+-----------+-------------------+------------+
| 이름      | 주유소            | 주유일     |
+-----------+-------------------+------------+
| 유재석    | 뉴서울(강남)      | 2021-10-01 |
| 이미주    | 뉴서울(강남)      | 2021-10-01 |
| 유재석    | 뉴서울(강남)      | 2021-10-03 |
| 아이유    | 뉴서울(강남)      | 2021-10-14 |
| 아이유    | 뉴서울(강남)      | 2021-10-14 |
| 유재석    | 뉴서울(강남)      | 2021-10-16 |
+-----------+-------------------+------------+
6 rows in set (0.20 sec)

// 내코드: 위처럼 in을 쓰면 더 간단하게 할 수 있구나..!
mysql> select o.이름, o.주유소, o.주유일 from refueling o, (select  상호, 가격 from oil_price where 가격 > (select avg(가격) from oil_price)) s where o.주유
소=s.상호;
+-----------+-------------------+------------+
| 이름      | 주유소            | 주유일     |
+-----------+-------------------+------------+
| 유재석    | 뉴서울(강남)      | 2021-10-01 |
| 이미주    | 뉴서울(강남)      | 2021-10-01 |
| 유재석    | 뉴서울(강남)      | 2021-10-03 |
| 아이유    | 뉴서울(강남)      | 2021-10-14 |
| 아이유    | 뉴서울(강남)      | 2021-10-14 |
| 유재석    | 뉴서울(강남)      | 2021-10-16 |
+-----------+-------------------+------------+
6 rows in set (0.06 sec)

// 5. refueling 테이블과 oil_price 테이블에서 10만원 이상 주유한 연예인 이름, 상호, 상표, 주유 금액, 가격을 Inline View를 사용하여 조회하세요. 
mysql> select r.이름, r.주유소, r.금액, o.상표, o.가격 from oil_price o, 
    -> (select 이름, 주유소, 금액 from refueling where 금액 >= 100000) r
    -> where o.상호=r.주유소;
+-----------+--------------------+--------+-------------+--------+
| 이름      | 주유소             | 금액   | 상표        | 가격   |
+-----------+--------------------+--------+-------------+--------+
| 이상순    | 타이거주유소       | 140000 | SK에너지    |   1484 |
| 유재석    | 뉴서울(강남)       | 110000 | SK에너지    |   2160 |
| 아이유    | 뉴서울(강남)       | 120000 | SK에너지    |   2160 |
| 아이유    | 뉴서울(강남)       | 150000 | SK에너지    |   2160 |
| 이미주    | 뉴서울(강남)       | 120000 | SK에너지    |   2160 |
+-----------+--------------------+--------+-------------+--------+
5 rows in set (0.07 sec)
// 내 풀이: 내꺼가 좀 더 간단한듯 ㅎ
mysql> select r.이름, r.주유소, r.금액, o.상표, o.가격 from refueling r, oil_price o where r.금액>100000 and o.상호=r.주유소;
+-----------+--------------------+--------+-------------+--------+
| 이름      | 주유소             | 금액   | 상표        | 가격   |
+-----------+--------------------+--------+-------------+--------+
| 이상순    | 타이거주유소       | 140000 | SK에너지    |   1484 |
| 유재석    | 뉴서울(강남)       | 110000 | SK에너지    |   2160 |
| 아이유    | 뉴서울(강남)       | 120000 | SK에너지    |   2160 |
| 아이유    | 뉴서울(강남)       | 150000 | SK에너지    |   2160 |
| 이미주    | 뉴서울(강남)       | 120000 | SK에너지    |   2160 |
+-----------+--------------------+--------+-------------+--------+
5 rows in set (0.04 sec)

0개의 댓글