본문 바로가기

Database/MySQL

[데이터베이스(강의)] 6. SQL로 데이터 조회 (subquery / IN, EXIST, ANY, ALL)

 

지금까지 만든 테이블

 

 


 

[ SELECT with subquery ]

 

 - ID가 14인 임직원보다 생일이 빠른 임직원의 ID, 이름, 생일을 알고 싶다.

 

 

(ID가 14인 임직원을 찾는 쿼리)

SELECT birth_date FROM employee WHERE id = 14;

 

SELECT id, name, birth_date FROM employee

WHERE birth_date < 1992-08-04;

 

두 개의 쿼리를 나눠서 실행했는데 한번에 실행해보면,

 

SELECT id, name, birth_date FROM employee

WHERE birth_date < (SELECT birth_date FROM employee WHERE id = 14);

 


 

* subquery (nested query or inner query) : SELECT, INSERT UPDATE, DELETE에 포함된 query.

 

* outer query (main query) : subquery 를 포함하는query.

 

* subquery 는 () 안에 기술된다.

 


 

 - ID가 1인 임직원과 같은 부서 같은 성별인 임직원들의 ID, 이름, 직군을 알고 싶다.

 

SELECT id, name, position FROM employee

WHERE (dept_id, sex) = (SELECT dept_id, sex FROM employee WHERE id = 1);

 

=> attribute가 여러개인 경우 괄호&콤마로 연결

 


 

- ID가 5인 임직원과 같은 프로젝트에 참여한 임직원들의 ID를 알고 싶다.

 

 

( ID가 5인 임직원의 프로젝트 )

SELECT proj_id FROM works_on WHERE empl_id = 5;

 

 

SELECT DISTINCT empl_id FROM works_on

WHERE empl_id !=5 AND (proj_id = 2001 OR proj_id = 2002);

 

* DISTINCT => 중복된 튜플 제거 (두개의 proj_id에 모두 해당되는 empl_id가 있는 경우 중복 발생)

 

 

 

SELECT DISTINCT empl_id FROM works_on

WHERE empl_id !=5 AND proj_id IN (2001, 2002);

 

 => OR 대신에 IN 사용 ******

 

 

 

SELECT DISTINCT empl_id FROM works_on

WHERE empl_id !=5 

AND proj_id IN (SELECT proj_id FROM works_on WHERE empl_id = 5);

 


 

* v IN (v1, v2, v3, ...) : v가 (v1, v2, v3, ...) 중에 하나와 값이 같다면 TRUE를 return한다.

  =>  (v1, v2, v3, ...) 는 명시적인 값들의 집합일 수도 있고 subquery의 결과 (set of multiset)일 수도 있다.

                                                                                                                   중복불허    중복허용       

* v NOT IN (v1, v2, v3, ...) : v가 (v1, v2, v3, ...)모든 값과 값이 다르다면 TRUE를 return 한다.

 

 

* unqualified attribute 가 참조하는 table은 

  해당 attribute가 사용된 query를 포함하여 그 query의 바깥쪽으로 존재하는 모든 queries 중에 

  해당 attribute 이름을 가지는 가장 가까이에 있는 table을 참조한다.

 


 

- ID가 5인 임직원과 같은 프로젝트에 참여한 임직원들의 ID를 알고 싶다.

  + 이름도 알고 싶다.

 

SELECT DISTINCT empl_id

FROM works_on

WHERE empl_id !=5 AND proj_id IN ( 

                                   SELECT proj_id 

                                   FROM works_on 

                                   WHERE empl_id = 5

                              );

 

 

 

works_on 테이블에서는 이름을 알 수 없다. 

지금까지 구한 empl_id를 통해 employee 테이블과 연결.

 

 

SELECT id, name

FROM employee

WHERE id = IN (

                               SELECT DISTINCT empl_id

                               FROM works_on

                               WHERE empl_id !=5 AND proj_id IN ( 

                                                                 SELECT proj_id 

                                                                  FROM works_on 

                                                                 WHERE empl_id = 5

                                                             )

                              );

 

 

다른 형태로도 만들어보면 (FROM 뒤에 subquery)

 

 

SELECT id, name

FROM employee,

              (

                     SELECT DISTINCT empl_id

                     FROM works_on

                     WHERE empl_id !=5 AND proj_id IN ( 

                                             SELECT proj_id 

                                             FROM works_on 

                                             WHERE empl_id = 5

                                     )

              ) AS DISTINCT_E

WHERE id = DISTINCT_E.empl_id;

 

 

=> 서브쿼리로 가상의 테이블을 만들고 ' DISTINCT_E ' 라는 이름을 붙여줌.

두개의 테이블(employee테이블, 가상테이블) 에서 id와 name을 조회.

                                   id                 DISTINCT_E.empl_id

 

 

 

=> subquery는 WHERE 절 / FROM 절  둘다 사용 가능.

 


 

- ID가 7 혹은 12인 임직원이 참여한 프로젝트의 ID와 이름을 알고 싶다.

 

 

(  ID가 7 혹은 12인 임직원이 참여한 프로젝트 )

SELECT proj_id FROM works_on WHERE empl_id IN (7, 12);

 

 

SELECT P.id, P.name

FROM project P           --- AS 생략

WHERE EXISTS(

                        SELECT *

                        FROM works_on W

                        WHERE W.proj_id = P.id AND W.empl_id IN (7, 12)

                 );

 

=> 서브쿼리의 WHERE 조건에 만족하는 튜플이 하나라도 있다면(EXISTS) 반환.

=> subquery가 project P라는 바깥쪽 쿼리 테이블을 참조.

 


(EXIST 대신 IN을 사용한다면)

 

SELECT P.id, P.name

FROM project P

WHERE id IN

                        SELECT W.proj_id

                        FROM works_on W

                        WHERE W.empl_id IN (7, 12)

                 );

 


 

* correlated query : subquery가 바깥쪽 query의 attribute를 참조할 떄, correlated subquery라 부름.

 

* EXISTS : subquery 의 결과가 최소 하나의 row라도 있다면 TRUE를 반환.

 

* NOT EXISTS : subquery 의 결과가 단 하나의  row도 없다면 TRUE를 반환.

 


 

 

- 2000년대생이 없는 부서의 ID와 이름을 알고 싶다.

 

SELECT D.id, D.name

FROM department D

WHERE NOT EXISTS 

                        SELECT *

                        FROM employee E

                        WHERE E.dept_id = D.id AND E.birth_date >= '2000-01-01'

                 );

 


 

(EXIST 대신 IN을 사용한다면)

 

SELECT D.id, D.name

FROM department D

WHERE D.id NOT IN

                        SELECT E.dept_id

                        FROM employee E

                        WHERE E.birth_date >= '2000-01-01'

                 );

 

 

=> IN 과 EXIST , NOT IN 과 NOT EXIST 는 대부분의 경우는 서로 바꿔가면서 사용 가능.

 

성능의 차이가 있지만 RDBMS의 종류와 버전에 따라 다르며, 

최근 버전은 많은 개선이 이루어져서 IN과 EXIST의 성능 차이가 거의 없음.

 


 

- 리더보다 높은 연봉을 받는 부서원을 가진 리더의 ID와 이름과 연봉을 알고 싶다.

 

 

SELECT E.id, E.name, E.salary

FROM department D, employee E

WHERE D.leader_id = E.id AND E.salary < ANY

                        SELECT salary

                        FROM employee

                        WHERE id <> D.leader_id AND dept_id = E.dept_id

                 );

 

=> 서브쿼리로 해당 부서원(리더제외)들의 salary를 가져와

리더의 salary보다 하나라도 큰 salary가 있다면 반환.

 

 

=> 리더인 MESSI 와 JENNY의 부서에는 각 리더의 연봉보다 많이 받는 부서원이 있음.

 


 

* v comparison_operator ANY (subquery) : subquery 가 반환한 결과들 중에

   단 하나라도 v와의 비교 연산이 TRUE라면 TURE를 반환한다.

 

 

* SOMEANY와 같은 역할을 한다.

 


 

- 리더보다 높은 연봉을 받는 부서원을 가진 리더의 ID와 이름과 연봉을 알고 싶다.

  + 해당 부서의 최고 연봉도 알고 싶다.

 

 

SELECT E.id, E.name, E.salary,

                  (                                               --- 추가된부분

                        SELECT max(salary)

                        FROM employee

                        WHERE dept_id = E.dept_id          __ 해당 리더가 속한 dept_id

                 ) AS dept_max_salary

FROM department D, employee E

WHERE D.leader_id = E.id AND E.salary < ANY ( 

                        SELECT salary

                        FROM employee

                        WHERE id <> D.leader_id AND dept_id = E.dept_id

                 );

 

=> FROM , WHERE 절은 변화 없음.

 

 

=> dept_max_salary 라는 새로운 attribute 생성.


 

 

- ID가 13인 임직원과 한번도 같은 프로젝트에 참여하지 못한 임직원들의 ID, 이름, 직군을 알고 싶다.

 

 

(ID가 13인 임직원이 참여한 프로젝트)

SELECT proj_id FROM works_on WHERE empl_id = 13;

 

 

SELECT DISTINCT E.id, E.name, E.position

FROM employee E, works_on W

WHERE E.id = W.empl_id AND W.proj_id <> ALL (

                        SELECT proj_id

                        FROM works_on

                        WHERE empl_id = 13 

                 );


 

* v comparison_operator ALL (subquery) : subquery 가 반환한 결과들과

                                                                                                v와의 비교 연산이 모두 TRUE라면 TRUE를 반환한다.

 

 

 


https://www.youtube.com/watch?v=lwmwlA2WhFc&list=PLcXyemr8ZeoREWGhhZi5FZs6cvymjIBVe&index=6