- 서브쿼리는 하나의 SQL 문 안에 포함되어 있는 또 다른 SQL문을 말함
- 알려지지 않은 기준을 이용한 검색을 위해 사용
- 조인에 참여하는 모든 테이블이 대등한 관계, 참여하는 모든 테이블의 컬럼을 어느 위치에서 자유롭게 사용 가능
- 1:1 관계의 테이블이 조인하면 레벨의 집합이 생성, 1:M 관계의 체이블을 조인하면 M 레벨의 집합 생성
M:N 관계 테이블을 조인하면 MN 레벨의 집합이 결과로 생성
① 서브쿼리를 괄호로 감싸서 사용한다.
② 서브쿼리는 단일 행(Single Row) 또는 복수 행(Multiple Row) 비교 연산자와 함께 사용 가능하다.
단일 행 비교 연산자는 서브쿼리의 결과가 반드시 1건 이하이어야 하고
복수 행 비교 연산자는 서브쿼리의 결과 건수와 상관 없다.
③ 서브쿼리에서는 ORDER BY를 사용하지 못한다.
ORDER BY절은 SELECT절에서 오직 한 개만 올 수 있기 때문에
ORDER BY절은 메인쿼리의 마지막 문장에 위치해야 한다.
서브쿼리가 SQL문에서 사용이 가능한 곳
: SELECT 절, FROM 절, WHERE 절, HAVING 절, ORDER BY 절, INSERT문의 VALUES 절, UPDATE문의 SET 절
* 단일 행 서브쿼리
- 서브쿼리가 단일 행 비교 연산자와 함께 사용할 때는 서브쿼리의 결과 건스가 반드시 1건 이하
- 서브쿼리의 결과 건수가 2 이상이면 SQL문은 실행시간 오류 발생 = runtime, complie time error
[예제] 정남일 선수가 소속된 팀의 선수들에 대한 정보를 표시하는 문제
SELECT PLAYER_NAME 선수명, POSITION 포지션, BACK_NO 백넘버
FROM PLAYER
WHERE TEAM_ID = (SELECT TEAM_ID
FROM PLAYER
WHERE PLAYER_NAME = '정남일')
ORDER BY PLAYER_NAME;
선수명 포지션 백넘버
------- ----- -----
강철 DF 3
김반 MF 14
김영수 MF 30
김정래 GK 33
김창원 DF 5
김회택 TM
꼬레아 FW 16
노병준 MF 22
51개의 행이 선택되었다.
[예제] 선수들의 평균키를 알아내는 SQL문(서브쿼리 부분)과 이 결과를 이용해서
키가 평균 이하의 선수들의 정보를 출력하는 SQL문(메인쿼리 부분)으로 구성
SELECT PLAYER_NAME 선수명, POSITION 포지션, BACK_NO 백넘버
FROM PLAYER
WHERE HEIGHT <= (SELECT AVG(HEIGHT)
FROM PLAYER)
ORDER BY PLAYER_NAME;
* 다중 행 서브쿼리
[예제] 선수들 중에서 ‘정현수’라는 선수가 소속되어 있는 팀 정보를 출력
SELECT REGION_NAME 연고지명, TEAM_NAME 팀명, E_TEAM_NAME 영문팀명
FROM TEAM
WHERE TEAM_ID = (SELECT TEAM_ID
FROM PLAYER
WHERE PLAYER_NAME = '정현수')
ORDER BY TEAM_NAME;
ORA-01427: 단일 행 하위 질의에 2개 이상의 행이 리턴되었다.
서브쿼리의 결과로 2개 이상의 행이 반환되어
단일 행 비교 연산자인 '='로는 처리가 불가능하기 때문에 에러가 반환됨
따라서 다중 행 비교 연산자로 바꾸어서 SQL문을 작성
SELECT REGION_NAME 연고지명, TEAM_NAME 팀명, E_TEAM_NAME 영문팀명
FROM TEAM
WHERE TEAM_ID IN (SELECT TEAM_ID
FROM PLAYER
WHERE PLAYER_NAME = '정현수')
ORDER BY TEAM_NAME;
* 다중 컬럼 서브쿼리
- 서브쿼리의 결과로 여러 개의 칼럼이 반환되어 메인쿼리의 조건과 동시에 비교되는 것을 의미
- 소속팀별 키가 가장 작은 사람들의 정보는 GROUP BY를 이용하여 찾을 수 있음
SELECT TEAM_ID 팀코드, PLAYER_NAME 선수명, POSITION 포지션, BACK_NO 백넘버, HEIGHT 키
FROM PLAYER
WHERE (TEAM_ID, HEIGHT) IN (SELECT TEAM_ID, MIN(HEIGHT)
FROM PLAYER GROUP BY TEAM_ID)
ORDER BY TEAM_ID, PLAYER_NAME;
* 연관 서브쿼리
- 서브쿼리 내에 메인쿼리 칼럼이 사용된 서브쿼리
- 선수 자신이 속한 팀의 평균 키보다 작은 선수들의 정보를 출력
SELECT T.TEAM_NAME 팀명, M.PLAYER_NAME 선수명, M.POSITION 포지션,
M.BACK_NO 백넘버, M.HEIGHT 키
FROM PLAYER M, TEAM T
WHERE M.TEAM_ID = T.TEAM_ID AND M.HEIGHT < ( SELECT AVG(S.HEIGHT)
FROM PLAYER S
WHERE S.TEAM_ID = M.TEAM_ID
AND S.HEIGHT IS NOT NULL GROUP BY S.TEAM_ID )
ORDER BY 선수명;
- EXISTS 서브쿼리를 사용하여 '20120501' 부터 '20120502' 사이에 경기가 있는 경기장을 조회
SELECT STADIUM_ID ID, STADIUM_NAME 경기장명
FROM STADIUM A
WHERE EXISTS (SELECT 1
FROM SCHEDULE X
WHERE X.STADIUM_ID = A.STADIUM_ID
AND X.SCHE_DATE BETWEEN '20120501' AND '20120502')
* 그 밖의 위치에서 사용하는 서브쿼리
- SELECT 절에 서브쿼리 사용하기 = 스칼라 서브쿼리
스칼라 서브쿼리는 한 행, 한 칼럼(1 Row 1 Column)만을 반환하는 서브쿼리
선수들의 정보를 출력하는 SQL문(메인쿼리 부분)과 해당 선수의 소속팀별 평균키를 알아내는 SQL문(서브쿼리 부분)
여기서 선수의 소속팀별 평균키를 알아내는 스칼라 서브쿼리는 메인쿼리의 결과 건수만큼 반복수행
SELECT PLAYER_NAME 선수명, HEIGHT 키, (SELECT AVG(HEIGHT)
FROM PLAYER X
WHERE X.TEAM_ID = P.TEAM_ID) 팀평균키
FROM PLAYER P
스칼라 서브쿼리 또한 단일 행 서브쿼리이기 때문에 결과가 2건 이상 반환되면 SQL문은 오류를 반환
- FROM 절에서 서브쿼리 사용하기 = 인라인 뷰
인라인 뷰는 SQL문이 실행될 때만 임시적으로 생성되는 동적인 뷰
일반적인 뷰를 정적 뷰(Static View)라고 하고 인라인 뷰를 동적 뷰(Dynamic View)
K-리그 선수들 중에서 포지션이 미드필더(MF)인 선수들의 소속팀명 및 선수 정보를 출력
SELECT T.TEAM_NAME 팀명, P.PLAYER_NAME 선수명, P.BACK_NO 백넘버
FROM (SELECT TEAM_ID, PLAYER_NAME, BACK_NO
FROM PLAYER
WHERE POSITION = 'MF') P, TEAM T
WHERE P.TEAM_ID = T.TEAM_ID
ORDER BY 선수명;
TOP-N 쿼리를 수행하기 위해서는 정렬 작업과 정렬 결과 중에서 일부 데이터만을 추출할 수 있는 방법이 필요
Oracle에서는 ROWNUM이라는 연산자를 통해서 결과로 추출하고자 하는 데이터 건수를 제약
Oracle SELECT PLAYER_NAME 선수명, POSITION 포지션, BACK_NO 백넘버, HEIGHT 키
FROM (SELECT PLAYER_NAME, POSITION, BACK_NO, HEIGHT
FROM PLAYER
WHERE HEIGHT IS NOT NULL ORDER BY HEIGHT DESC)
WHERE ROWNUM <= 5;
SQL Server SELECT TOP(5) PLAYER_NAME AS 선수명, POSITION AS 포지션,
BACK_NO AS 백넘버, HEIGHT AS 키
FROM PLAYER
WHERE HEIGHT IS NOT NULL ORDER BY HEIGHT DESC
인라인 뷰에서 선수의 키를 내림차순으로 정렬한 후 메인쿼리에서 ROWNUM을 사용해서 5명의 선수의 정보만을 추출
다른 선수 중에서 키가 192인 선수가 더 존재하더라도 해당 SQL문에서는 데이터가 출력되지 않음
이런 데이터까지 추출하고자 한다면 분석함수의 RANK관련 함수를 사용
- HAVING 절에서 서브쿼리 사용하기
그룹함수와 함께 사용될 때 그룹핑된 결과에 대해 부가적인 조건을 주기 위해서 사용
평균키가 삼성 블루윙즈팀의 평균키보다 작은 팀의 이름과 해당 팀의 평균키를 구하는 SQL문
SELECT P.TEAM_ID 팀코드, T.TEAM_NAME 팀명, AVG(P.HEIGHT) 평균키
FROM PLAYER P, TEAM T
WHERE P.TEAM_ID = T.TEAM_ID
GROUP BY P.TEAM_ID, T.TEAM_NAME HAVING AVG(P.HEIGHT) < (SELECT AVG(HEIGHT)
FROM PLAYER
WHERE TEAM_ID ='K02')
- UPDATE 문의 SET 절에서 사용하기
TEAM 테이블에 STADIUM_NAME을 추가(ALTER TABLE ADD COLUMN)하였다고 가정
TEAM 테이블에 추가된 STADIUM_NAME의 값을 STADIUM 테이블을 이용하여 변경
UPDATE TEAM A SET A.STADIUM_NAME = (SELECT X.STADIUM_NAME
FROM STADIUM X
WHERE X.STADIUM_ID = A.STADIUM_ID);
서브쿼리의 결과가 NULL을 반환할 경우 해당 컬럼의 결과가 NULL이 될 수 있기 때문에 주의
- INSERT 문의 VALUES 절에서 사용하기
PLAYER 테이블에 '홍길동'이라는 선수를 삽입
PLAYER_ID의 값을 현재 사용중인 PLAYER_ID에 1을 더한 값으로 넣고자 함
INSERT INTO PLAYER(PLAYER_ID, PLAYER_NAME, TEAM_ID)
VALUES((SELECT TO_CHAR(MAX(TO_NUMBER(PLAYER_ID))+1)
FROM PLAYER),
'홍길동', 'K06');
* 뷰
CREATE VIEW V_PLAYER_TEAM AS SELECT P.PLAYER_NAME, P.POSITION, P.BACK_NO,
P.TEAM_ID, T.TEAM_NAME
FROM PLAYER P, TEAM T
WHERE P.TEAM_ID = T.TEAM_ID;
선수 정보와 해당 선수가 속한 팀명을 함께 추출
CREATE VIEW V_PLAYER_TEAM_FILTER AS SELECT PLAYER_NAME, POSITION, BACK_NO, TEAM_NAME
FROM V_PLAYER_TEAM
WHERE POSITION IN ('GK', 'MF');
V_PLAYER_TEAM_FILTER 뷰는 선수 포지션이 골키퍼(GK), 미드필더(MF)인 선수만을 추출하고자 하는 뷰
SELECT PLAYER_NAME, POSITION, BACK_NO, TEAM_ID, TEAM_NAME
FROM V_PLAYER_TEAM
WHERE PLAYER_NAME LIKE '황%'
V_PLAYER_TEAM 뷰에서 성이 '황'씨인 선수만을 추출하는 SQL문
SELECT PLAYER_NAME, POSITION, BACK_NO, TEAM_ID, TEAM_NAME
FROM (SELECT P.PLAYER_NAME, P.POSITION, P.BACK_NO, P.TEAM_ID, T.TEAM_NAME
FROM PLAYER P, TEAM T
WHERE P.TEAM_ID = T.TEAM_ID)
WHERE PLAYER_NAME LIKE '황%'
뷰를 제거하기 위해서는 DROP VIEW문을 사용
DROP VIEW V_PLAYER_TEAM;
DROP VIEW V_PLAYER_TEAM_FILTER;
'SQLD' 카테고리의 다른 글
[SQLD : Ⅳ. SQL 활용] 6. 윈도우 함수 (0) | 2020.08.21 |
---|---|
[SQLD : Ⅳ. SQL 활용] 5. 그룹 함수 (0) | 2020.08.21 |
[SQLD : Ⅳ. SQL 활용] 3. 계층형 질의와 셀프 조인 (0) | 2020.08.21 |
[SQLD : Ⅳ. SQL 활용] 2. 집합 연산자 (0) | 2020.08.21 |
[SQLD : Ⅳ. SQL 활용] 1-2. 표준조인 (0) | 2020.08.18 |