DB/SQL

SUBQUERY

제주니어 2022. 9. 2. 17:42

SUBQUERY

  • SQL 문장 안에 포함되어 있는 또 다른 SQL 문장을 서브 쿼리라 하며, 메인 쿼리를 보조하는 역할을 하는 쿼리문이다.
  • 서브 쿼리는 반드시 괄호 ( ) 안에 넣어 표현해야 하고 메인 쿼리가 실행되기 전 한 번만 실행된다.
-- 서브 쿼리 예시 1) 
-- 1. 노옹철 사원과 같은 부서원들을 조회
-- 1-1) 노옹철 사원의 부서 코드 조회 (D9) -- SUBQUERY → 먼저 실행
SELECT DEPT_CODE
FROM EMPLOYEE
WHERE EMP_NAME = '노옹철'; -- D9

-- 1-2) 부서 코드가 노옹철 사원의 부서 코드와 동일한 사원들을 조회 - MAINQUERY
SELECT EMP_NAME, DEPT_CODE
FROM EMPLOYEE
WHERE DEPT_CODE = 'D9';

-- 위의 2단계를 하나의 쿼리로 작성
SELECT EMP_NAME, DEPT_CODE
FROM EMPLOYEE
WHERE DEPT_CODE = (
      SELECT DEPT_CODE
      FROM EMPLOYEE
      WHERE EMP_NAME = '노옹철'
      );

-- 2. 전 직원의 평균 급여보다 더 많은 급여를 받고 있는 직원들의 사번, 직원명, 직급 코드, 급여를 조회
-- 2-1) 전 직원의 평균 급여를 조회 (3047662.60869565217391304347826086956522)
SELECT AVG(SALARY)
FROM EMPLOYEE;

-- 2-2) 급여가 2-1)의 조회 결과 이상인 직원들의 사번, 직원명, 직급 코드, 급여를 조회
SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE SALARY >= 3047662.60869565217391304347826086956522;

-- 위의 2단계를 하나의 쿼리로 작성
SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE SALARY >= (
      SELECT AVG(SALARY)
      FROM EMPLOYEE
      );
  • 서브 쿼리의 분류에 따라 서브 쿼리 앞에 붙는 연산자가 달라진다.
  • 다중열은 모두 일반 연산자 가능 !
  • 다중행은 (NOT)IN, ANY, ALL !

단일행 서브 쿼리

  • 단일행 서브 쿼리는 서브 쿼리의 결과가 한 개의 행, 한 개의 열인 서브 쿼리이다. (결과 값이 한 개인 서브 쿼리)
  • 일반 연산자 사용이 가능하다.
-- 1-1) 전 직원의 평균 급여보다 급여를 적게 받는 직원들의 사번, 직원명, 직급 코드, 급여를 조회
-- 전 직원의 평균 급여 조회
SELECT AVG(NVL(SALARY, 0))
FROM EMPLOYEE;

-- 위 쿼리를 서브 쿼리로 사용하는 메인 쿼리 작성
SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE SALARY < (
      SELECT AVG(NVL(SALARY, 0))
      FROM EMPLOYEE
      );

-- 1-2) 최저 급여를 받는 직원의 사번, 직원명, 직급 코드, 급여, 입사일 조회
-- 최저 급여를 조회
SELECT MIN(SALARY)
FROM EMPLOYEE;

-- 위 쿼리를 서브 쿼리로 사용하는 메인 쿼리 작성
SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY, HIRE_DATE
FROM EMPLOYEE
WHERE SALARY = (
      SELECT MIN(SALARY)
      FROM EMPLOYEE
      );

-- 1-3) 노옹철 사원의 급여보다 더 많이 받는 사원의 사번, 직원명, 부서명, 직급 코드, 급여를 조회
-- 노옹철 사원의 급여
SELECT SALARY
FROM EMPLOYEE
WHERE EMP_NAME = '노옹철';

-- 위 쿼리를 서브 쿼리로 사용하는 메인 쿼리 작성
SELECT E.EMP_ID,
       E.EMP_NAME,
       D.DEPT_TITLE,
       J.JOB_CODE,
       E.SALARY
FROM EMPLOYEE E
INNER JOIN DEPARTMENT D ON (E.DEPT_CODE = D.DEPT_ID)
INNER JOIN JOB J ON (E.JOB_CODE = J.JOB_CODE)
WHERE E.SALARY > (
      SELECT SALARY
      FROM EMPLOYEE
      WHERE EMP_NAME = '노옹철'
      );

-- 1-4) 부서별 급여의 합이 가장 큰 부서의 부서 코드, 급여의 합을 조회
-- 각 부서별 급여의 합 중 가장 큰 값
-- 단순하게 DEPT_CODE를 SELECT문에 추가하지 못한다. 
-- MAX(SUM(SALARY))행의 개수는 1개라서 DEPT_CODE와 행의 개수가 맞지않다 
SELECT MAX(SUM(SALARY))
FROM EMPLOYEE
GROUP BY DEPT_CODE;

-- 위 쿼리를 서브 쿼리로 사용하는 메인 쿼리 작성
SELECT DEPT_CODE, SUM(SALARY)
FROM EMPLOYEE 
GROUP BY DEPT_CODE
HAVING SUM(SALARY) = (
       SELECT MAX(SUM(SALARY))
       FROM EMPLOYEE 
       GROUP BY DEPT_CODE
       );

다중행 서브 쿼리

  • 다중행 서브 쿼리는 서브 쿼리의 결과가 여러 개의 행, 한 개의 열인 서브 쿼리이다.

IN / NOT IN

  • 여러 개의 결과값 중에서 한 개라도 일치하는 값이 있다면 혹은 없다면 TRUE 리턴한다.

ANY

  • 여러 개의 값들 중에서 한 개라도 일치하면 TRUE, IN과 다른 점은 비교 연산자를 함께 사용한다는 점이다.
  • SALASY = ANY (...) : IN과 같은 결과 SALASY != ANY (...) : NOT IN과 같은 결과 SALASY > ANY (...) : 최소값 보다 크면 TRUE 리턴 SALASY < ANY (...) : 최대값 보다 작으면 TRUE 리턴

ALL

  • 여러 개의 값들 모두와 일치하면 TRUE, IN과 다른 점은 비교 연산자를 함께 사용한다는 점이다.
  • SALARY > ALL (...) : 최대값 보다 크면 TRUE 리턴 SALARY < ALL (...) : 최소값 보다 작으면 TRUE 리턴 SALARY = ALL (...) [ERROR] SALARY != ALL (...) [ERROR]
  • 일반적인 연산자를 사용할 수 없다. (=, >, ㅡ, <=, >=)
-- 2-1) 각 부서별 최고 급여를 받는 직원의 직원명, 직급 코드, 부서 코드, 급여를 조회
-- 부서별 최고 급여 조회 (2890000, 3660000, 8000000, 3760000, 3900000, 2490000, 2550000)
SELECT MAX(SALARY)
FROM EMPLOYEE
GROUP BY DEPT_CODE;

-- 위의 급여를 받는 사원들을 조회
SELECT EMP_NAME, DEPT_CODE, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE SALARY IN (2890000, 3660000, 8000000, 3760000, 3900000, 2490000, 2550000)
ORDER BY DEPT_CODE;

-- 위의 쿼리문을 합쳐서 하나의 쿼리문으로 작성
SELECT EMP_NAME, DEPT_CODE, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE SALARY IN (
      SELECT MAX(SALARY)
      FROM EMPLOYEE
      GROUP BY DEPT_CODE
      )
ORDER BY DEPT_CODE;

-- 2-2) 사원들의 사번, 이름, 부서 코드, 구분(사수/사원) 조회
-- 사수에 해당하는 사번을 조회 (201, 204, 100, 200, 211, 207, 214)
SELECT DISTINCT MANAGER_ID -- 중복 제거
FROM EMPLOYEE
WHERE MANAGER_ID IS NOT NULL;

-- 사번이 위와 같은 직원들의 사번, 이름, 부서 코드, 구분(사수) 조회
SELECT EMP_ID, EMP_NAME, DEPT_CODE, '사수' AS "구분"
FROM EMPLOYEE
WHERE EMP_ID IN (201, 204, 100, 200, 211, 207, 214);

-- 위의 쿼리문을 합쳐서 하나의 쿼리문으로 작성
SELECT EMP_ID, EMP_NAME, DEPT_CODE, '사수' AS "구분"
FROM EMPLOYEE
WHERE EMP_ID IN (
      SELECT DISTINCT MANAGER_ID
      FROM EMPLOYEE
      WHERE MANAGER_ID IS NOT NULL
      );

-- 일반 사원에 해당하는 정보를 조회
SELECT EMP_ID, EMP_NAME, DEPT_CODE, '사원' AS "구분"
FROM EMPLOYEE
WHERE EMP_ID NOT IN (
      SELECT DISTINCT MANAGER_ID
      FROM EMPLOYEE
      WHERE MANAGER_ID IS NOT NULL
      );
     
-- 위의 결과들을 하나의 결과로 확인 (UNION) → SELECT절을 두개 써서 비효율적 !
SELECT EMP_ID, EMP_NAME, DEPT_CODE, '사원' AS "구분"
FROM EMPLOYEE
WHERE EMP_ID NOT IN (
      SELECT DISTINCT MANAGER_ID
      FROM EMPLOYEE
      WHERE MANAGER_ID IS NOT NULL
      )
      
UNION
      
SELECT EMP_ID, EMP_NAME, DEPT_CODE, '사수' AS "구분"
FROM EMPLOYEE
WHERE EMP_ID IN (
      SELECT DISTINCT MANAGER_ID
      FROM EMPLOYEE
      WHERE MANAGER_ID IS NOT NULL
      )
ORDER BY EMP_ID;

-- SELECT절에 서브 쿼리를 사용해서 위와 동일한 결과를 얻는 방법
SELECT EMP_ID, 
       EMP_NAME, 
       DEPT_CODE,
       CASE WHEN EMP_ID IN (
            SELECT DISTINCT MANAGER_ID
            FROM EMPLOYEE
            WHERE MANAGER_ID IS NOT NULL
            ) 
            THEN '사수'
            ELSE '사원'
       END AS "구분"
FROM EMPLOYEE;

-- 2-3) 대리 직급임에도 과장 직급들의 최소 급여보다 많이 받는 직원의 사번, 이름, 직급 코드, 급여를 조회
--      (사원 → 대리 → 과장 → 차장 → 부장)
-- 과장 직급들의 급여 조회 (2200000, 2500000, 3760000)
SELECT SALARY
FROM EMPLOYEE
WHERE JOB_CODE = 'J5';

-- 직급이 대리인 직원들 중에서 위의 값 중에 하나라도 큰 경우
SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE JOB_CODE = 'J6' AND SALARY > ANY (2200000, 2500000, 3760000);
-- SALARY > 220만 OR SALARY > 250만 OR SALARY > 376만

-- 한 개의 쿼리문으로 합쳐보기
SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE JOB_CODE = 'J6' AND SALARY > ANY (
      SELECT SALARY
      FROM EMPLOYEE
      WHERE JOB_CODE = 'J5'
      );

-- 2-4) 과장 직급임에도 차장 직급의 최대 급여보다 더 많이 받는 직원들의 사번, 이름, 직급 코드, 급여 조회
-- 차장 직급들의 급여 조회 (2800000, 1550000, 2490000, 2480000)
SELECT E.SALARY
FROM EMPLOYEE E
INNER JOIN JOB J ON (E.JOB_CODE = J.JOB_CODE)
WHERE J.JOB_NAME = '차장';

-- 과장 직급임에도 차장 직급의 최대 급여보다 더 많이 받는 직원
SELECT E.EMP_ID, E.EMP_NAME, J.JOB_NAME, E.SALARY
FROM EMPLOYEE E
INNER JOIN JOB J ON (E.JOB_CODE = J.JOB_CODE)
WHERE J.JOB_NAME = '과장' AND SALARY > ALL (2800000, 1550000, 2490000, 2480000);
-- SALARY > 280만 AND SALARY > 155만 AND SALARY > 249만 AND SALARY > 248만

-- 한 개의 쿼리문으로 합쳐보기
SELECT E.EMP_ID, E.EMP_NAME, J.JOB_NAME, E.SALARY
FROM EMPLOYEE E
INNER JOIN JOB J ON (E.JOB_CODE = J.JOB_CODE)
WHERE J.JOB_NAME = '과장' AND SALARY > ALL (
      SELECT E.SALARY
      FROM EMPLOYEE E
      INNER JOIN JOB J ON (E.JOB_CODE = J.JOB_CODE)
      WHERE J.JOB_NAME = '차장'
      );

다중열 서브 쿼리

  • 다중열 서브 쿼리는 서브 쿼리의 결과가 한 개의 행, 여러 개의 열인 서브 쿼리이다.
-- 3-1) 하이유 사원과 같은 부서 코드, 같은 직급 코드에 해당하는 사원들을 조회
-- 하이유 사원의 부서 코드와 직급 코드 조회
SELECT DEPT_CODE, JOB_CODE
FROM EMPLOYEE
WHERE EMP_NAME = '하이유';

-- 부서 코드가 D5이면서 직급 코드가 J5인 사원들 조회
SELECT EMP_NAME, DEPT_CODE, JOB_CODE
FROM EMPLOYEE
WHERE DEPT_CODE = 'D5' AND JOB_CODE ='J5';

-- 각각 단일행 서브 쿼리로 작성
SELECT EMP_NAME, DEPT_CODE, JOB_CODE
FROM EMPLOYEE
WHERE DEPT_CODE = (
      -- 단일행 서브 쿼리
      SELECT DEPT_CODE
      FROM EMPLOYEE
      WHERE EMP_NAME = '하이유'
      ) AND 
      JOB_CODE =(
      -- 단일행 서브 쿼리
      SELECT JOB_CODE
      FROM EMPLOYEE
      WHERE EMP_NAME = '하이유'
      );

-- 다중열 서브 쿼리를 사용해서 조회
SELECT EMP_NAME, DEPT_CODE, JOB_CODE
FROM EMPLOYEE
WHERE (DEPT_CODE, JOB_CODE) =(('D5', 'J5'));

SELECT EMP_NAME, DEPT_CODE, JOB_CODE
FROM EMPLOYEE
WHERE (DEPT_CODE, JOB_CODE) =(
      SELECT DEPT_CODE, JOB_CODE
      FROM EMPLOYEE 
      WHERE EMP_NAME = '하이유'
      );

-- 3-2) 박나라 사원과 직급 코드가 일치하면서 같은 사수를 가지고 있는 사원의 사번, 이름, 직급 코드, 사수 사번을 조회
-- 박나라 사원의 직급 코드와 사수의 사번을 조회 (J7, 207)
SELECT JOB_CODE, MANAGER_ID
FROM EMPLOYEE
WHERE EMP_NAME = '박나라';

-- 박나라 사원과 같은 직급 코드, 같은 사수를 가지고 있는 사원의 사번, 이름, 직급 코드, 사수 사번을 조회
SELECT EMP_ID, EMP_NAME, JOB_CODE, MANAGER_ID
FROM EMPLOYEE
WHERE (JOB_CODE, MANAGER_ID) = (
      SELECT JOB_CODE, MANAGER_ID
      FROM EMPLOYEE
      WHERE EMP_NAME = '박나라'
      );

SELECT EMP_ID, EMP_NAME, JOB_CODE, MANAGER_ID
FROM EMPLOYEE
WHERE (JOB_CODE, MANAGER_ID) IN (('J7', '207'));

다중행 다중열 서브 쿼리

  • 다중행 다중열 서브 쿼리는 서브 쿼리의 결과가 여러 개의 행, 여러 개의 열인 서브 쿼리이다.
-- 4-1) 각 직급별로 최소 급여를 받는 사원들의 사번, 이름, 직급 코드, 급여를 조회
-- 각 직급별 최소 급여 조회 
-- ((J2, 3700000), (J7, 1380000), (J3,	3400000), ...)
SELECT JOB_CODE, MIN(SALARY)
FROM EMPLOYEE
GROUP BY JOB_CODE;

SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE JOB_CODE = 'J2' AND SALARY = 3700000 
   OR JOB_CODE = 'J7' AND SALARY = 1380000
   OR JOB_CODE = 'J3' AND SALARY = 3400000;

SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE (JOB_CODE, SALARY) IN (('J2', '3700000'), ('J7', '1380000'), ('J3', '3400000'));

-- 다중행 다중열 서브 쿼리를 사용해서 조회
SELECT EMP_ID, EMP_NAME, JOB_CODE, SALARY
FROM EMPLOYEE
WHERE (JOB_CODE, SALARY) IN (
      SELECT JOB_CODE, MIN(SALARY)
      FROM EMPLOYEE
      GROUP BY JOB_CODE
      )
ORDER BY JOB_CODE;

-- 4-2) 각 부서별 최소 급여를 받는 사원들의 사번, 이름, 부서 코드, 급여를 조회
-- 각 부서별 최소 급여 조회
SELECT NVL(DEPT_CODE, '부서없음'), MIN(SALARY)
FROM EMPLOYEE
GROUP BY DEPT_CODE;

-- 다중행 다중열 서브 쿼리를 사용해서 조회
SELECT EMP_ID, EMP_NAME, NVL(DEPT_CODE, '부서없음'), SALARY
FROM EMPLOYEE
WHERE (NVL(DEPT_CODE, '부서없음'), SALARY) IN (
      SELECT NVL(DEPT_CODE, '부서없음'), MIN(SALARY)
      FROM EMPLOYEE
      GROUP BY DEPT_CODE)
ORDER BY DEPT_CODE;

인라인 뷰(INLINE-VIEW)

  • 메인 쿼리의 FROM절에 서브 쿼리를 사용하는 것을 인라인 뷰라 한다.
  • 서브 쿼리를 수행한 결과를 테이블 대신에 사용한다.
-- 1. 인라인 뷰를 활용한 TOP-N 분석
-- 전 직원 중에 급여가 가장 높은 상위 5명의 순위, 이름, 급여 조회
-- * ROWNUM : 오라클에서 제공하는 컬럼, 조회된 순서대로 1부터 순번을 부여하는 컬럼
SELECT ROWNUM, EMP_NAME, SALARY
FROM EMPLOYEE
ORDER BY SALARY DESC;

-- 이미 순번이 정해진 다음에 정렬이 되었다. 
-- FROM → SELECT (순번이 정해진다.) → ORDER BY 
-- SELECT ROWNUM A* : 별칭의 모든 값을 가져온다.
-- 모든 컬럼을 본다고 해도 서브 쿼리의 결과가 테이블이므로 
SELECT ROWNUM, A.EMP_NAME, A.SALARY  -- 별칭으로 값을 가져온다. 
FROM (
      SELECT EMP_NAME, SALARY
      FROM EMPLOYEE
      ORDER BY SALARY DESC
) A -- 컬럼명을 지정할 수 있다. 
WHERE ROWNUM <= 5;

-- FROM절 외부에서 별칭을 지정한 경우, 별칭을 생략해도 문제가 없다. 
SELECT ROWNUM, EMP_NAME, SALARY   
FROM (
      SELECT *
      FROM EMPLOYEE
      ORDER BY SALARY DESC
) A -- 별칭 지정 
WHERE ROWNUM <= 5;

-- FROM절 내부에서 별칭을 지정한 경우, 컬럼명으로 불러오면 오류가 난다. [ERROR]
SELECT ROWNUM, EMP_NAME, SALARY   
FROM (
      SELECT EMP_NAME "이름",
             SALARY "급여"
      FROM EMPLOYEE
      ORDER BY SALARY DESC
) 
WHERE ROWNUM <= 5;

-- FROM절 내부에서 별칭을 지정한 경우, 별칭으로 불러오면 오류가 나지 않는다.
SELECT ROWNUM, "이름", "급여"   
FROM (
      SELECT EMP_NAME "이름",
             SALARY "급여"
      FROM EMPLOYEE
      ORDER BY SALARY DESC
) 
WHERE ROWNUM <= 5;

-- 2-1) 부서별 평균 급여가 높은 3개의 부서의 부서 코드, 평균 급여 조회
-- 컬럼명을 꼭 설정해줘야 한다 
-- 별칭을 사용하지 않으면, 컬럼명이 아니라 함수 실행이라고 인식을 한다.
-- 함수를 사용하는 구문이나 연산식이 컬럼에 들어가면 반드시 별칭을 붙여야 에러가 나지 않는다.
SELECT ROWNUM AS "순위", "부서 코드", ROUND("평균 급여")
FROM (
      SELECT NVL(DEPT_CODE, '부서 없음') AS "부서 코드", 
             AVG(NVL(SALARY, 0)) AS "평균 급여"
      FROM EMPLOYEE
      GROUP BY DEPT_CODE
      ORDER BY "평균 급여" DESC
      )
WHERE ROWNUM <= 3;

-- 별칭을 붙이지 않은 식은 컬럼명으로 가져올 수 있다.
SELECT ROWNUM AS "순위", DEPT_CODE, ROUND("평균 급여")
FROM (
      SELECT DEPT_CODE, 
             AVG(NVL(SALARY, 0)) AS "평균 급여"
      FROM EMPLOYEE
      GROUP BY DEPT_CODE
      ORDER BY "평균 급여" DESC
      )
WHERE ROWNUM <= 3;

WITH

  • 서브 쿼리에 이름을 붙여주고 인라인 뷰 대신 사용하는 것을 WITH라 한다.
  • 동일한 서브 쿼리가 여러 번 사용될 경우 중복 작성을 피할 수 있고 실행 속도도 빨라진다는 장점이 있다.
WITH TOPN_SAL AS ( -- TOPN_SAL : 인라인뷰 이름
     SELECT DEPT_CODE AS "부서 코드", 
            AVG(NVL(SALARY, 0)) AS "평균 급여"
     FROM EMPLOYEE
     GROUP BY DEPT_CODE
     ORDER BY "평균 급여" DESC
)
SELECT "부서 코드", "평균 급여"
FROM TOPN_SAL
WHERE ROWNUM<= 3;

RANK 함수

  • 데이터의 순위를 알아내는 함수이다.
  • RANK( ) OVER 함수는 동일한 순위가 있는 경우 다음 등수를 건너뛰고 순위를 계산한다.
  • DENSE_RANK( ) OVER 함수는 동일한 순위가 있어도 다음 등수를 건너뛰지 않고 순위를 계산한다.
-- 사원별 급여가 높은 순서대로 순위 매겨서 순위, 직원명, 급여 조회
-- 공동 19위 2명 뒤에 순위는 21
SELECT RANK() OVER(ORDER BY SALARY DESC) AS "RANK",
       EMP_NAME, 
       SALARY
FROM EMPLOYEE;

-- 공동 19위 2명 뒤에 순위는 20
SELECT DENSE_RANK() OVER(ORDER BY SALARY DESC) AS "RANK",
       EMP_NAME, 
       SALARY
FROM EMPLOYEE;

-- 상위 5명
SELECT DENSE_RANK() OVER(ORDER BY SALARY DESC) AS "RANK",
       EMP_NAME, 
       SALARY
FROM EMPLOYEE
WHERE RANK() OVER(ORDER BY SALARY DESC) <= 5; -- RANK 함수는 WHERE 사용할 수 없다. 

SELECT *
FROM (
      SELECT DENSE_RANK() OVER(ORDER BY SALARY DESC) AS "RANK",
             EMP_NAME, 
             SALARY
      FROM EMPLOYEE
      )
WHERE RANK <= 5;

'DB > SQL' 카테고리의 다른 글

DML(Data Manipulation Language)  (0) 2022.09.04
DDL(Data Definition Language)  (0) 2022.09.03
INNER JOIN & OUTER JOIN  (0) 2022.09.01
GROUP BY & HAVING  (0) 2022.08.30
함수(FUNCTION)  (0) 2022.08.29