SubQuery(서브쿼리)와 View(뷰) 대해 알아보자.



SubQuery

서브쿼리 SQL문 안에 포함된 SQL문. 쿼리 안에 쿼리가 중첩되어 있는 구조

바깥부분에 있는 쿼리를 MainQuery, 안에 있는 쿼리를 SubQuery라고 부름

SubQuery는 일반적으로 WHERE절에서 조건의 일환으로 사용된다.

ex) 신장이 가장 큰 선수의 정보 조회 -> 키가 가장 큰 사람을 찾아야하고, 그 사람의 이름을 찾아야함. 질문 2개

1
2
3
1.
SELECT MAX(HEIGHT)
FROM PLAYER;
1
2
3
4
2.
SELECT PLAYER_NAME, POSITION, HEIGHT
FROM PLAYER
WHERE HEIGHT = 194;

1번 SQL문으로 키가 가장 큰 선수를 찾아낸 뒤에 그 값을 2번 쿼리의 WHERE에 집어넣어서 그 사람의 정보를 가져오는 것이다.

2번이 MainQuery가 되는 것.

1
2
3
SELECT PLAYER_NAME, POSITION, HEIGHT
FROM PLAYER
WHERE HEIGHT = (SELECT MAX(HEIGHT) FROM PLAYER)



SubQuery의 종류

쓰이는 위치에 따라

  • SELECT절에 사용하는 Scalar SubQuery

  • FROM절에 사용하는 Inline View
    • FROM절에서 서브쿼리를 사용하면 VIEW와 유사하게 동작하지만 조금 다른 특징을 가진다해서 Inline View라고 부름
  • WHERE절에 사용하는 중첩SubQuery

결과 칼럼 / 행의 수에 따라

  • 단일 컬럼 서브쿼리 - SELECT 뒤에 컬럼이 한 개

  • 다중 컬럼 서브쿼리 - SELECT 뒤에 컬럼이 여러개

  • 단일 행 서브쿼리 - 결과로 나오는 행이 한 개

  • 다중 행 서브쿼리 - 결과로 나오는 행이 여러개

결과의 수를 가지고 4가지 타입으로 구분을 할 수 있다.

단일 컬럼 서브쿼리 - 단일 행 서브쿼리

단일 컬럼 서브쿼리 - 다중 행 서브쿼리

다중 컬럼 서브쿼리 - 단일 행 서브쿼리

다중 컬럼 서브쿼리 - 다중 행 서브쿼리

메인쿼리와의 연관성에 따라

  • 연관(상관) 서브쿼리 : 메인쿼리의 칼럼을 서브쿼리가 사용을 하게될 때

  • 비연관 서브쿼리 : 메인쿼리의 칼럼을 서브쿼리에서 사용하지 않을 때. 연관이 안되어있을 때

서브쿼리는 메인쿼리의 칼럼 모두 사용 가능하지만 메인쿼리는 서브쿼리의 칼럼을 사용할 수 없다.

(Inline View에 정의된 칼럼만 사용 가능)



중첩 서브쿼리

  • WHERE절에 사용하는 중첩 서브쿼리
  • 성능의 문제와는 무관
    • 서브쿼리로 쿼리를 짜든 조인으로 풀어서 짜든 어떤 것이 유리한지 옵티마이저가 알아서 판단을 해서 실행해서 작성을 해주기 때문에 성능과는 무관함


결과 칼럼/행의 수에 따른 구분

서브쿼리 종류 설명
단일행 서브쿼리의 실행결과로 항상 1건 이하의 행을 반환, 단일행 비교 연산자(=, < , <= , >, >=, <>)와 함께 사용
다중행 서브쿼리의 실행결과로 여러 건의 행 반환 가능, 다중행 비교 연산자(IN, ALL, ANY, SOME, EXISTS)와 함께 사용
단일칼럼 서브쿼리의 실행결과로 하나의 칼럼을 반환
다중칼럼 서브쿼리의 실행 결과로 여러 칼럼을 반환, 서브쿼리와 메인쿼리의 비교 연산 수행 시, 비교하는 칼럼 개수와 위치가 동일해야 함

단일행 서브쿼리

  • 서브커리의 결과 건수가 반드시 1건 이하
  • 단일행 비교 연산자(=, < , <= , >, >=, <>)와 함께 사용
    • 결과가 2건 이상이면 Run Time Error 발생
    • 서브쿼리의 결과가 예를 들어 3건이 나왔는데 단일행 비교연산자이면 Run Time Error가 발생하는 것임

ex) ‘2007182’번 선수와 같은 팀에 속하는 선수의 이름, 포지션, 팀ID 출력 - 선수의 팀을 찾는 것이 서브쿼리가 되고, 그 팀에 속하는 선수의 정보를 출력하는 것이 메인쿼리가 됨

1
2
3
SELECT TEAM_ID
FROM PLAYER
WHERE PLAYER_ID = '2007182';

위 쿼리의 결과는 1개가 나올 것이니까 위 쿼리가 서브쿼리로 사용되면 그 때는 단일행 비교연산자를 사용해도 된다!

1
2
3
4
5
6
SELECT PLAYER_NAME, POSITION, TEAM_ID
FROM PLAYER
WHERE TEAM_ID = 
        (SELECT TEAM_ID
        FROM PLAYER
        WHERE PLAYER_ID = '2007182');


Q) EMP테이블의 사번이 7499인 직원의 입사일을 사번 7369인 직원의 입사일과 같게 변경하는 질의

전)

- ENPNO ENAME DATE
1 7369 Smith 0512
2 7499 Allen 0000

후)

- ENPNO ENAME DATE
1 7369 Smith 0512
2 7499 Allen 0512
1
2
UPDATE EMP SET DATE = (SELECT DATE FROM EMP WHERE EMPNO = 7369)
WHERE EMPNO = 7499;

다중행 서브쿼리

  • 서브쿼리의 결과 건수가 2건 이상일 가능성이 있을 때
  • 다중행 비교 연산자(IN, ALL, ANY, SOME, EXISTS)와 함께 사용
    • 2건 이상일 가능성은 있지만 결과 건수가 우연히 1개인 경우 -> 단일행 비교 연산자도 에러는 발생하지 않음
IN(subquery) 임의의 결과 중 하나만 만족해도 참(Multiple OR 조건)
비교연산자 ALL(subquery) 결과의 모든 값을 만족해야 하는 조건
비교연산자 ANY/SOME(subquery) 결과의 어느 하나의 값이라도 만족하면 되는 조건 (ANY = SOME)
EXISTS(subquery) 조건을 만족하는 값이 존재하는지 여부를 확인, 조건을 만족하는 건을 하나라도 찾으면 검색 중지(속도 빠름)


IN 연산자

1
2
3
4
5
6
SELECT PLAYER_NAME, HEIGHT, BACK_NO
FROM PLAYER
WHERE HEIGHT =
    (SELECT HEIGHT
    FROM PLAYER
    WHERE BACK_NO = 15);

등번호(BACK_NO)가 15인 사람의 결과행이 1개보다 많이 때문에 = 를 사용하면 에러가 발생. -> IN을 사용해야함.

1
2
3
4
5
6
SELECT PLAYER_NAME, HEIGHT, BACK_NO
FROM PLAYER
WHERE HEIGHT IN
    (SELECT HEIGHT
    FROM PLAYER
    WHERE BACK_NO = 15);


ALL 연산자

  • 결과의 모든 값을 만족해야 하는 조건
    • ex) x > ALL(1, 2, 3, 4, 5) 라면 x > 5가 되어야함
1
2
3
4
5
6
SELECT PLAYER_NAME, HEIGHT, BACK_NO
FROM PLAYER
WHERE HEIGHT > ALL
    (SELECT HEIGHT
    FROM PLAYER
    WHERE BACK_NO = 15);

서브쿼리 결과 HEIGHT가 180, 176, 184. 이 3개의 값보다 키가 큰 선수의 정보를 출력하게 됨 ( > 184)


ANY(=SOME) 연산자

  • 결과의 어느 하나의 값이라도 만족하면 되는 조건
    • ex) x > ANY(1, 2, 3, 4, 5) 라면 x > 1이면 됨
1
2
3
4
5
6
SELECT PLAYER_NAME, HEIGHT, BACK_NO
FROM PLAYER
WHERE HEIGHT >= ANY
    (SELECT HEIGHT
    FROM PLAYER
    WHERE BACK_NO = 15);

180, 176, 184 중 최소값만 만족해도 됨


EXIST 연산자

  • 조건을 만족하는 값이 존재하는지 여부를 확인
  • 조건이 만족되는 1건만 찾으면 더 이상 검색하지 않음 (속도가 빠름)
  • 주로 참/거짓의 조건 판단용으로 사용됨
1
2
3
4
5
6
SELECT PLAYER_NAME, HEIGHT, BACK_NO
FROM PLAYER
WHERE EXISTS
    (SELECT 1
    FROM PLAYER
    WHERE BACK_NO = 15);

서브쿼리의 결과가 참 -> 모든 결과가 출력됨



연관 서브쿼리

  • 메인쿼리의 칼럼이 서브쿼리에서 사용된 쿼리
1
2
3
4
5
SELECT ENAME, SAL, DEPTNO
FROM EMP M
WHERE SAL > (SELECT AVG(S.SAL)
            FROM EMP S
            WHERE M.DEPTNO = S.DEPTNO);

어떤 직원이 있을 때 그 직원이 속한 부서의 모든 직원의 급여 평균을 구한 뒤에, 평균보다 큰 급여인 사원의 이름, 급여, 부서번호를 출력

  • 메인쿼리에서 EMP M 을 서브쿼리에 전달
  • 서브쿼리에서 EMP M과 같은 부서인 EMP S의 평균 급여를 게산하여 메인쿼리에 전달
  • 메인쿼리에서 EMP M의 급여와 서브쿼리에서 전달받은 급여를 비교

연관 서브쿼리의 특징

  • 메인쿼리의 칼럼이 서브쿼리에서 사용된 쿼리
    • cf) 비연관 서브쿼리 : 서브쿼리에서 메인쿼리의 칼럼을 사용하지 않음
  • 메인쿼리가 먼저 수행되고, 그 후에 서브쿼리가 수행됨
    • 테이블의 별칭(Alias)을 이용하여 메인쿼리에서 서브쿼리로 정보 전달
    • 서브쿼리가 메인쿼리의 값을 이용, 그 후에 서브쿼리의 결과를 메인쿼리가 이용
  • 서브쿼리에서 메인쿼리의 칼럼과 서브쿼리의 칼럼 간 비교가 이루어짐
    • 메인쿼리에서는 서브쿼리의 칼럼 사용 불가


단일행 다중칼럼 서브쿼리

  • 서브쿼리의 결과로 여러 칼럼, 단일행이 반환됨
    • ex) PLAYER_ID가 2007188인 선수와 키, 포지션이 같은 선수 조회
1
2
3
4
5
6
SELECT PLAYER_NAME, HEIGHT, POSITION, BACK_NO
FROM PLAYER
WHERE (HEIGHT, POSITION) =
    (SELECT HEIGHT, POSITION
    FROM PLAYER
    WHERE PLAYER_ID = '2007188');


다중행 다중칼럼 서브쿼리

  • 서브쿼리의 결과로 여러 칼럼, 다중행이 반환됨
1
2
3
4
5
6
SELECT PLAYER_NAME, HEIGHT, POSITION
FROM PLAYER
WHERE (HEIGHT, POSITION) IN
    (SELECT HEIGHT, POSITION
    FROM PLAYER
    WHERE PLAYER_NAME = '김충호');
- PLAYER_NAME HEIGHT POSITION
1 김충호 185 DF
2 김충호 185 GK

Q) 부서별로 최고 급여를 받는 사원의 사원명, 부서번호, 급여를 출력하는 질의

1
2
3
4
5
6
SELECT ENAME, DEPTNO, SAL
FROM EMP
WHERE (DEPTNO, SAL) IN 
    (SELECT DEPTNO, MAX(SAL)
    FROM EMP
    GROUP BY DEPTNO);
- ENAME DEPTNO SAL
1 Blake 30 2850
2 Scott 20 3000
3 King 10 5000
4 Ford 20 3000



Scalar SubQuery

스칼라 서브쿼리 하나의 값을 반환하는 서브쿼리
  • 단일 행, 단일 칼럼
  • 하나의 값을 반환한다는 점에서 함수(Function)의 특성을 가짐
  • 공집합을 반환하는 경우 NULL이 대응됨

  • 칼럼이 올 수 있는 대부분의 곳에서 사용가능
    • SELECT절, WHERE절, 함수의 인자, ORDER BY절, CASE절, HAVING절 등
    • 주로 SELECT절에서 사용함
1
2
3
SELECT EMPNO, ENAME,
        (SELECT DNAME FROM DEPT WHERE DEPTNO = A.DEPTNO) AS DNAME
FROM EMP A;

함수의 인자로도 사용 가능

1
2
3
SELECT EMPNO, ENAME, SUBSTR( (SELECT DNAME FROM DEPT WHERE DEPTNO = A.DEPTNO), 1, 3)
       AS DNAME
FROM EMP A;


뷰 (View)

  • 테이블은 실제로 데이터를 갖고 있지만, 뷰는 실제 데이터를 갖지 않음
    • 뷰 정의(View Definition, SQL txt 파일)만 갖고 있음
    • 쿼리에서 뷰가 사용되면 DBMS 내부적으로 질의를 재작성(Rewrite)
  • 실제 데이터를 가지고 있지 않지만 테이블의 역할 수행
    • 가상 테이블(Virtual Table)이라고도 함
  • CREATE VIEW문을 통해 VIEW 생성 - TABLE을 만드는 것과 유사함
    1
    2
    3
    4
    
    CREATE VIEW V_PLAYER_TEAM AS
    SELECT P.PLAYER_NAME, P.BACK_NO, P.TEAM_ID, T.TEAM_NAME
    FROM PLAYER P INNER JOIN TEAM T
    ON P.TEAM_ID = T.TEAM_ID;
    
  • VIEW의 확인
    1
    2
    
    SELECT *
    FROM V_PLAYER_TEAM
    
  • VIEW의 제거
    1
    
    DROP VIEW V_PLAYER_TEAM
    
  • 생성된 뷰는 테이블과 동일한 형태로 사용 가능
  • 파싱 시점에 DBMS가 내부적으로 뷰에 해당하는 DDL을 SQL문으로 재작성해줌
    1
    2
    3
    
    SELECT PLAYER_NAME, BACK_NO, TEAM_ID, TEAM_NAME
    FROM V_PLAYER_TEAM
    WHERE PLAYER_NAME LIKE '이%';
    
    1
    2
    3
    4
    5
    
    SELECT PLAYER_NAME, BACK_NO, TEAM_ID, TEAM_NAME
    FROM (SELECT P.PLAYER_NAME, P.BACK_NO, P.TEAM_ID, T.TEAM_NAME
          FROM PLAYER P INNER JOIN TEAM T
          ON P.TEAM_ID = T.TEAM_ID)
    WHERE PLAYER_NAME LIKE '이%';
    


계층적 뷰 생성

뷰로 부터 또 다른 뷰를 생성할 수 있음

Q) 사원과 부서 테이블로부터 사원번호, 사원명, 부서번호, 부서명을 추출한 뷰 V_EMP_DEPT를 작성하고 이 뷰로부터 사원명과 부서명 만을 다시 추출한 V_EMP_DEPT2를 작성

(“사원과 부서 테이블로부터” -> JOIN)

1
2
3
4
CREATE VIEW V_EMP_DEPT AS
SELECT ENAME, EMPNO, DEPTNO, DNAME
FROM EMP JOIN DEPT
USING (DEPTNO);
1
2
3
CREATE VIEW V_EMP_DEPT2 AS
SELECT ENAME, DNAME
FROM V_EMP_DEPT;


뷰의 장점

장점 설명
독립성 테이블 구조가 변경시, 뷰만 변경되고 뷰를 사용하는 응용 프로그램은 변경될 필요가 없음
편리성 복잡한 질의를 뷰로 생성하여 질의의 가독성을 높임
보안성 민감한 정보(급여정보 등)를 제외하고 뷰를 생성하여, 사용자로부터 정보를 보호할 수 있음



Inline View

  • FROM 절에서 사용되는 서브쿼리
  • 실행 순간에만 임시적으로 생성되며 DB에 저장되지 않음
    • 인라인 뷰(Inline View) = 동적 뷰(Dynamic View) / 쿼리가 끝나면 날라감
    • 일반 뷰 = 정적 뷰(Static View) / 저장이 됨
  • Inline View의 SELECT 문에서 정의된 칼럼만 메인쿼리에서 사용 가능
    • cf) 일반적으로 서브쿼리에서 정의된 칼럼은 메인쿼리에서 사용 불가능함
1
2
SELECT EMPNO
FROM (SELECT EMPNO, ENAME FROM EMP ORDER BY MGR);

Q) 급여가 2,000 초과인 직원들에 대해 직원번호, 직원명, 급여, 부서명을 출력하고자 한다. 다음의 질의에서 오류를 수정하시오.

1
2
3
SELECT E.EMPNO, E.ENAME, E.SAM, D.DNAME
FROM (SELECT EMPNO, ENAME, SAL FROM EMP WHERE SAL > 2000) E, DEPT D
WHERE E.DEPTNO = D.DEPTNO;

A)

1
2
3
SELECT E.EMPNO, E.ENAME, E.SAM, D.DNAME
FROM (SELECT EMPNO, ENAME, SAL, DEPTNO FROM EMP WHERE SAL > 2000) E, DEPT D
WHERE E.DEPTNO = D.DEPTNO;

Leave a comment