초록꼬마의 devlog
article thumbnail

2021.11.5(금)

🌿 SUBQUERY

  • 하나의 주된 SQL문 안에 포함된 또 하나의 SELECT문
  • main SQL문의 보조 역할을 하는 쿼리문
  • 서브쿼리를 수행한 결과값이 몇 행 몇 열이냐에 따라 구분 + 서브쿼리를 수행한 결과가 몇 행 몇 열이냐에 따라 사용 가능한 연산자도 달라짐

🌱 단일행 (단일열) 서브쿼리

  • 서브쿼리를 수행한 결과값이 오로지 1개일 때
  • 일반 연산자(=, !=, >=, < 등) 사용 가능
-- 문제 = 부서별 급여 합이 가장 큰 부서 하나의 부서코드, 부서명, 급여 합 조회
SELECT DEPT_CODE, DEPT_TITLE, SUM(SALARY)
FROM EMPLOYEE
LEFT JOIN DEPARTMENT ON (DEPT_CODE = DEPT_ID)
GROUP BY DEPT_CODE, DEPT_TITLE
HAVING SUM(SALARY) = (SELECT MAX(SUM(SALARY)) -- HAVING절에도 서브쿼리 사용 가능
                      FROM EMPLOYEE
                      GROUP BY DEPT_CODE); -- 서브쿼리의 결과값 = 단일행; 17,700,000

SELECT DEPT_CODE, DEPT_TITLE, SUM(SALARY)
FROM EMPLOYEE, DEPARTMENT
WHERE DEPT_CODE = DEPT_ID(+)
GROUP BY DEPT_CODE, DEPT_TITLE
HAVING SUM(SALARY) = (SELECT MAX(SUM(SALARY)) FROM EMPLOYEE GROUP BY DEPT_CODE); -- 서브쿼리의 결과값 = 17,700,000 = 단일행

🌱 다중행 (단일열) 서브쿼리

  • 서브쿼리의 조회 결과값이 여러 행일 때
  • 사용 가능한 연산자
    • IN: 여러 개의 결과값 중에서 1개라도 일치하는 값이 있으면
      NOT IN: 아예 없으면
    • > ANY: 여러 개의 결과값 중에서 '하나라도' 클 경우 = 여러 개의 결과값의 최소값보다 큰 경우
      < ANY: 여러 개의 결과값 중에서 '하나라도' 작을 경우 = 여러 개의 결과값의 최대값보다 작은 경우
      (ANY는 OR와 비슷함)
-- 문제 = 대리 직급임에도 불구하고 과장 직급의 급여보다 많이 받은 직원들의 사번, 이름, 직급명, 급여 조회
SELECT EMP_NO, EMP_NAME, JOB_NAME, SALARY
FROM EMPLOYEE
LEFT JOIN JOB USING (JOB_CODE)
WHERE SALARY > ANY(SELECT SALARY FROM EMPLOYEE LEFT JOIN JOB USING (JOB_CODE) WHERE JOB_NAME = '과장') -- 서브쿼리의 결과값 = 다중행 단일열
 AND JOB_NAME = '대리';
  • > ALL: 여러 개의 결과값 모두보다 큰 경우 = 여러 개의 결과값의 최대값보다 큰 경우
    < ALL: 여러 개의 결과값 모두보다 작은 경우 = 여러 개의 결과값의 최소값보다 작은 경우
    (ALL은 AND와 비슷함)
-- 문제 = 과장 직급임에도 불구하고 모든(ALL) 차장 직급의 급여보다 많이 받은 직원들의 사번, 이름, 직급명, 급여 조회
SELECT EMP_ID, EMP_NAME, JOB_NAME, SALARY
FROM EMPLOYEE
LEFT JOIN JOB USING (JOB_CODE)
WHERE SALARY > ALL(SELECT NVL(SALARY, 0)
                   FROM EMPLOYEE
                   LEFT JOIN JOB USING (JOB_CODE)
                   WHERE JOB_NAME = '차장') -- JOB_NAME이 '차장'인 사람들의 SALARY 조회하는 서브쿼리; 서브쿼리의 결과값 = 다중행 단일열; 2800000, 1550000, 2490000, 2480000
 AND JOB_NAME = '과장'; -- 대북혼

🌱 (단일행) 다중열 서브쿼리

  • 조회 결과 값은 1개 행이지만, 나열된 컬럼 수가 여러 개일 때
  • 사용 가능한 연산자
    • (컬럼1, 컬럼2, ..) = (컬럼1 값, 컬럼2 값, ..)
    • (컬럼1, 컬럼2, ..) IN (컬럼1 값, 컬럼2 값, ..)
-- 문제 = 박나라 사원과 같은 직급코드 및 같은 사수사번을 가진 사원들의 사번, 이름, 직급코드, 사수사번, 부서명 조회
SELECT EMP_ID, EMP_NAME, JOB_CODE, MANAGER_ID, DEPT_TITLE
FROM EMPLOYEE E, DEPARTMENT D
-- 단일행 다중열 result set이 나와야 함 = 박나라 사원의 JOB_CODE, MANAGER_ID
WHERE (JOB_CODE, MANAGER_ID) = (SELECT JOB_CODE, MANAGER_ID
                                FROM EMPLOYEE
                                WHERE EMP_NAME = '박나라') -- 서브쿼리의 결과값 = 단일행 다중열; J7, 207
 AND E.DEPT_CODE = D.DEPT_ID(+);

🌱 다중행 다중열 서브쿼리

  • 서브쿼리 조회 결과값이 여러 행 + 여러 컬럼일 경우
  • 사용 가능한 연산자: (컬럼1, 컬럼2, ..) IN (컬럼1 값, 컬럼2 값, ..)
-- 문제 = 각 직급별 최소 급여를 받는 사원들의 사번, 이름, 직급코드, 급여 조회
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); -- 서브쿼리 결과값 = 다중행 다중열

📗 소감

  • subquery 토픽 학습은 어려웠다 @.@
  • 연습을 많이 해 봐야겠다!

📗 homework:

-- 1번 문제: 부서별로 그룹하여 부서번호, 인원수, 급여의 평균, 급여의 합을 조회
SELECT NVL(DEPT_CODE, 'N/A') "부서번호", COUNT(*) "인원수", ROUND(AVG(SALARY)) "급여의 평균", SUM(SALARY) "급여의 합"
FROM EMPLOYEE
GROUP BY DEPT_CODE;

-- 2번 문제: 자신이 속한 직급의 평균 급여보다 많이 받는 사원의 이름, (직급코드,) 직급명, 급여 정보 조회
-- GOOGLING해서 HINT 얻음(출처: https://github.com/ikhcho/Study/blob/master/src/study/db/sql/SQL_LAB8(%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C).sql)
-- 단계1) 각 직급의 평균 급여 구하기
SELECT JOB_CODE, AVG(SALARY) "직급별 평균 급여"
FROM EMPLOYEE
GROUP BY JOB_CODE; -- J2, 4850000
                   -- J7, 2017500
                   -- J3, 3600000
                   -- J6, 2624373.33333333333333333333333333333333
                   -- J5, 2820000
                   -- J1, 8000000
                   -- J4, 2330000

-- 단계2) 자신이 속한 직급의 평균 급여보다 많이 받는 사원의 이름, (직급코드,) 직급명, 급여 정보 조회
SELECT EMP_NAME, E.JOB_CODE, JOB_NAME, SALARY, S."직급별 평균 급여"
FROM EMPLOYEE E, JOB J, (SELECT JOB_CODE, ROUND(AVG(SALARY)) "직급별 평균 급여"
                         FROM EMPLOYEE
                         GROUP BY JOB_CODE) S
WHERE E.JOB_CODE = J.JOB_CODE(+)
 AND E.JOB_CODE = S.JOB_CODE(+)
 AND E.SALARY > S."직급별 평균 급여"
ORDER BY E.JOB_CODE;

-- 3번 문제: 회사에서 휴가철을 맞아 전 사원에게 해외여행을 보내주려고 합니다. 
-- 사원들의 급여에 맞춰 보내주려고 하여 S1, S2인 사원들은 미국, S3, S4인 사원들은 중국, S5, S6인 사원들은 일본에 보내려고 해요.
-- 단, 현재 근무하고 있는 지부의 국가가 해외여행을 보내주려는 국가와 같을 경우, 국내여행을 보내주며,
-- 대표는 휴가때도 계속해서 근무를 하기로 합니다.
-- EMPLOYEE 테이블에서 이와 같은 조건에 맞는 사번, 사원명, 부서명, 지부국가명(NATIONAL_NAME), 급여등급, 휴가여행지를 사번 오름차순으로 조회하세요.
SELECT EMP_ID "사번"
    , EMP_NAME "사원명"
    , DEPT_TITLE "부서명"
    , NATIONAL_NAME "지부국가명"
    , SAL_LEVEL "급여등급"
    , CASE WHEN J.JOB_NAME = '대표' THEN '근무'
           WHEN (E.SAL_LEVEL IN ('S1', 'S2') AND NVL(N.NATIONAL_NAME, 0) != '미국') THEN '미국'
           WHEN (E.SAL_LEVEL IN ('S1', 'S2') AND NVL(N.NATIONAL_NAME, 0) = '미국') THEN '한국'
           WHEN (E.SAL_LEVEL IN ('S3', 'S4') AND NVL(N.NATIONAL_NAME, 0) != '중국') THEN '중국'
           WHEN (E.SAL_LEVEL IN ('S3', 'S4') AND NVL(N.NATIONAL_NAME, 0) = '중국') THEN '한국'
           WHEN (E.SAL_LEVEL IN ('S5', 'S6') AND NVL(N.NATIONAL_NAME, 0) != '일본') THEN '일본'
           WHEN (E.SAL_LEVEL IN ('S5', 'S6') AND NVL(N.NATIONAL_NAME, 0) = '일본') THEN '한국'
           END "휴가지"
FROM EMPLOYEE E
LEFT JOIN DEPARTMENT D ON (E.DEPT_CODE = D.DEPT_ID)
LEFT JOIN JOB J ON (E.JOB_CODE = J.JOB_CODE)
LEFT JOIN LOCATION L ON (D.LOCATION_ID = L.LOCAL_CODE)
LEFT JOIN NATIONAL N ON (L.NATIONAL_CODE = N.NATIONAL_CODE)
ORDER BY EMP_ID;

-- 4번 문제: 모든 사원들 급여의 총합을 '₩00,000,000'형식으로 구하세요
SELECT TO_CHAR(SUM(SALARY), 'L999,999,999')
FROM EMPLOYEE;

-- 5번 문제: 부서 코드가 D9인 사원들의 급여 중 최저 급여보다 높은 급여를 받는 사원의 이름과 급여를 출력
SELECT EMP_NAME, SALARY
FROM EMPLOYEE
WHERE SALARY > (SELECT MIN(SALARY)
                FROM EMPLOYEE
                GROUP BY DEPT_CODE
                HAVING DEPT_CODE = 'D9'); -- 서브쿼리의 결과값 = 3700000

-- 6번 문제: 'D6'번 부서의 사원 중에서 급여를 가장 많이 받는 사원보다 더 많은 급여를 받는 사원의 이름과 급여를 출력
SELECT EMP_NAME, SALARY
FROM EMPLOYEE
WHERE SALARY > (SELECT MAX(SALARY)
                FROM EMPLOYEE
                GROUP BY DEPT_CODE
                HAVING DEPT_CODE = 'D6'); -- 서브쿼리의 결과값 = 3900000

-- 7번 문제: 회사의 재정난으로 인하여 구조조정이 불가피해졌습니다.
-- 우선 현재 사원을 파악하기 위해 (EMPLOYEE 테이블의) 모든 직원의 사번, 사원명, 직급, 부서명, 근무지역을 직급코드와 사번의 내림차순으로 조회하세요.
SELECT EMP_ID 사번, EMP_NAME 사원명, JOB_NAME 직급, DEPT_TITLE 부서명, NATIONAL_NAME 근무지역
FROM EMPLOYEE E, JOB J, DEPARTMENT D, LOCATION L, NATIONAL N
WHERE E.JOB_CODE = J.JOB_CODE(+)
 AND E.DEPT_CODE = D.DEPT_ID(+)
 AND D.LOCATION_ID = L.LOCAL_CODE(+)
 AND L.NATIONAL_CODE = N.NATIONAL_CODE(+)
ORDER BY E.JOB_CODE DESC, EMP_ID DESC;

-- 8번 문제: 사기 충전의 의미로 한달에 한번 사내 이벤트가 진행됩니다. 랜덤 추첨을 통한 이벤트 당첨자 발표를 위해 다음을 조회하세요.
-- (사원 정보는 최소한만 노출되게 하기 위해 다음 조건을 만족해야 합니다: 당첨자의 사번, 사원명(가운데 글자는 '*'으로 표시), 아이디(이메일에서 아이디만 추출), 연락처(가운데 4자리는 '****'으로 표시)
-- 이번 달 당첨자는 주민번호 마지막 자리가 '1'인 사원들입니다.
SELECT EMP_ID "사번"
    , SUBSTR(EMP_NAME, 1, 1) || '*' || SUBSTR(EMP_NAME, 3, 1) "사원명"
    , SUBSTR(EMAIL, 1, INSTR(EMAIL, '@') - 1) "ID"
    , CASE WHEN PHONE IS NOT NULL THEN SUBSTR(PHONE, 1, 3) || '****' || SUBSTR(PHONE, 8, 4)
           WHEN PHONE IS NULL THEN 'N/A'
      END "연락처"
FROM EMPLOYEE
WHERE EMP_NO LIKE ('%1');

-- 9번 문제: 국가명, 부서명, 인원수, 급여합계, 급여평균을 조회하되,
-- 국가명과 부서명 별로 묶어서 조회하고,
-- 정렬은 국가명 내림차순, 부서명 내림차순 순서로 지정하세요.
SELECT NATIONAL_NAME 국가명, DEPT_TITLE 부서명, COUNT(*) 인원수, SUM(SALARY) 급여합계, ROUND(AVG(SALARY)) 급여평균
FROM EMPLOYEE E
LEFT JOIN DEPARTMENT D ON (E.DEPT_CODE = D.DEPT_ID)
LEFT JOIN LOCATION L ON (D.LOCATION_ID = L.LOCAL_CODE)
LEFT JOIN NATIONAL N ON (L.NATIONAL_CODE = N.NATIONAL_CODE)
GROUP BY NATIONAL_NAME, DEPT_TITLE
ORDER BY NATIONAL_NAME DESC NULLS LAST, DEPT_TITLE DESC NULLS LAST;

-- 10번 문제: 대표 '선동일'은 최근 사내에서 지연으로 의심되는 상황을 엿듣게 되었다. 누군지 찾아내기 위해 믿을만한 김기사에게 비밀리에 사원들의 정보를 정리해오라고 명령했다.
-- 사번, 사원명, 부서명, 직급명, 급여, 보너스, 성별, 출생지를 조회하고, 정렬은 출생지 오름차순, 급여 내림차순 순서로 정렬한다.
-- 출생지는 주민번호 뒷자리 2번째부터 2자리를 기준으로 다음과 같다.
--서울 00~08 / 부산 09~12 / 인천 13~15 / 경기 16~25 / 강원 26~34 / 충북 35~39 / 대전 40 /
--충남 41~47 / 전북 48~54 / 전남 55~64 / 광주 65~66 / 대구 67~69, 76 /
--경북 70~75, 77~81 / 경남 82~84, 86~92 / 울산 85 / 제주 93~95 / 그 외에는 '확인요망'으로 표시
SELECT EMP_ID 사번, EMP_NAME 사원명, DEPT_TITLE 부서명, JOB_NAME 직급명, SALARY 급여, BONUS 보너스
    , DECODE(SUBSTR(EMP_NO, 8, 1), '1', '남성', '2', '여성') 성별
    , CASE WHEN '00' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '08' THEN '서울'
           WHEN '09' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '12' THEN '부산'
           WHEN '13' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '15' THEN '인천'
           WHEN '16' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '25' THEN '경기'
           WHEN '26' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '34' THEN '강원'
           WHEN '35' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '39' THEN '충북'
           WHEN SUBSTR(EMP_NO, -2, 2) = '40' THEN '대전'
           WHEN '41' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '47' THEN '충남'
           WHEN '48' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '54' THEN '전북'
           WHEN '55' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '64' THEN '전남'
           WHEN SUBSTR(EMP_NO, -2, 2) IN ('65', '66') THEN '광주'
           WHEN SUBSTR(EMP_NO, -2, 2) IN ('67', '68', '69', '76') THEN '대구'
           WHEN '70' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '75' OR '77' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '81' THEN '경북'
           WHEN SUBSTR(EMP_NO, -2, 2) IN ('82', '83', '84') OR '86' <= SUBSTR(EMP_NO, -2, 2) AND SUBSTR(EMP_NO, -2, 2) <= '92'  THEN '경남'
           WHEN SUBSTR(EMP_NO, -2, 2) <= '85' THEN '울산'
           WHEN SUBSTR(EMP_NO, -2, 2) IN ('93', '94', '95') THEN '제주'
           ELSE '확인 요망'
      END 출생지
FROM EMPLOYEE E
LEFT JOIN DEPARTMENT D ON (E.DEPT_CODE = D.DEPT_ID)
LEFT JOIN JOB J ON (E.JOB_CODE = J.JOB_CODE)
ORDER BY 출생지, SALARY DESC NULLS LAST;