[문제] 부서별로 인원수를 출력.

10 20 ... 110 부서가 없는 사원
1 2 ... 2 1
SELECT
    count(decode(department_id, 10, 'x')) "10",
    -- count는 개수를 세는용도이니 리턴값을 'x'문자나 숫자1 등으로 활용
    count(decode(department_id, 20, 'x')) "20",
    count(decode(department_id, 30, 'x')) "30",
    count(decode(department_id, 40, 'x')) "40",
    count(decode(department_id, 50, 'x')) "50",
    count(decode(department_id, 60, 'x')) "60",
    count(decode(department_id, 70, 'x')) "70",
    count(decode(department_id, 80, 'x')) "80",
    count(decode(department_id, 90, 'x')) "90",
    count(decode(department_id, 100, 'x')) "100",
    
    count(CASE WHEN department_id = 110 THEN 'x' END) "110",
    count(CASE WHEN department_id IS NULL THEN 'x' END) "부서가 없는 사람"
FROM hr.employees;

>> rows 개수만큼 decode 를 각 반복 체크 > 107행 * decode 12개 반복 > 속도 저하 > 악성코드

SELECT department_id, count(*)
FROM hr.employees
GROUP BY department_id;
>> 부서별 카운트
↓
SELECT
    max(decode(department_id, 10, cnt)) "10",
    max(decode(department_id, 20, cnt)) "20",
    max(decode(department_id, 30, cnt)) "30",
    max(decode(department_id, 40, cnt)) "40",
    max(decode(department_id, 50, cnt)) "50",
    max(decode(department_id, 60, cnt)) "60",
    max(decode(department_id, 70, cnt)) "70",
    max(decode(department_id, 80, cnt)) "80",
    max(decode(department_id, 90, cnt)) "90",
    max(decode(department_id, 100, cnt)) "100",
    max(decode(department_id, 110, cnt)) "110",
    max(decode(department_id, NULL, cnt)) "부서가 없는 사원"
FROM (SELECT department_id, count(*) cnt
        FROM hr.employees
        GROUP BY department_id);

>> 부서별카운트 12행 x decode 12개 반복

■ PIVOT
- 행(세로) 데이터를 열(가로)로 변경하는 함수
    ★ PIVOT(그룹함수 FOR 적용칼럼 IN (InlineView에 해당하는 값을 칼럼명으로 사용)
                 └ 함수사용 X                   ㄴ'문자' as "칼럼명"

SELECT department_id, count(*)
FROM hr.employees
GROUP BY department_id;
>> 부서별 카운트
↓ 
SELECT *
FROM (SELECT department_id
        FROM hr.employees)
PIVOT(count(*) FOR department_id IN (10,20,30,40,50,60,70,80,90,100,
                                        110,null as "부서가 없는 사원"));
||
SELECT *
FROM (SELECT department_id, count(*) cnt
        FROM hr.employees
        GROUP BY department_id)
PIVOT(max(cnt) FOR department_id IN (10,20,30,40,50,60,70,80,90,100,
                                        110,null as "부서가 없는 사원"));

>> 결과집합 cnt가 이미 사용되어 max,min,sum 등을 사용하여 그룹함수를 적용

 

SELECT department_id, sum(salary)
FROM hr.employees
GROUP BY department_id
↓
SELECT *
FROM (SELECT department_id, salary
        FROM hr.employees)
PIVOT(sum(salary) FOR department_id IN (10,20,30,40,50,60,70,80,90,100,
                                        110,null as "부서가 없는 사원"));
||
SELECT *
FROM (SELECT department_id, sum(salary) sum_sal
        FROM hr.employees
        GROUP BY department_id)
PIVOT(max(sum_sal) FOR department_id IN (10,20,30,40,50,60,70,80,90,100,
                                        110,null as "부서가 없는 사원"));

 

[문제] 년도별 입사 인원수를 가로방향으로 출력.

SELECT *
FROM (SELECT to_char(hire_date,'yyyy') year, count(*) cnt
        FROM hr.employees
        GROUP BY to_char(hire_date,'yyyy'))
PIVOT (max(cnt) FOR year IN ('2001' "2001",'2002' "2002",'2003' "2003",
                                '2004' as "2004",'2005' as "2005",
                                '2006' "2006",'2007' "2007",'2008' "2008")),
        (SELECT count(*) 총인원수 FROM hr.employees),
        (SELECT to_char(sum(salary),'9,999,999') 총급여 FROM hr.employees);
||
SELECT *
FROM (SELECT to_char(hire_date,'yyyy') year
        FROM hr.employees)
PIVOT (count(*) FOR year IN ('2001' "2001",'2002' "2002",'2003' "2003",
                                '2004' as "2004",'2005' as "2005",
                                '2006' "2006",'2007' "2007",'2008' "2008")),
        (SELECT count(*) 총인원수 FROM hr.employees),
        (SELECT to_char(sum(salary),'9,999,999') 총급여 FROM hr.employees);

 

[문제] 요일별 입사 인원수를 가로방향으로 출력.

SELECT *
FROM (SELECT to_char(hire_date,'dy') day, count(*) cnt
        FROM hr.employees
        GROUP BY to_char(hire_date,'dy'))
PIVOT (max(cnt) FOR day IN ('월' "월요일", '화' "화요일", '수' "수요일",
                                '목' "목요일", '금' "금요일", '토' "토요일",
                                '일' "일요일"));
||
SELECT *
FROM (SELECT to_char(hire_date,'dy') day
        FROM hr.employees)
PIVOT (count(*) FOR day IN ('월' "월요일", '화' "화요일", '수' "수요일",
                                '목' "목요일", '금' "금요일", '토' "토요일",
                                '일' "일요일"));

 


 

UNPIVOT
- 열(가로)을 행(세로)으로 변환하는 함수

SELECT *
FROM (SELECT *
        FROM (SELECT to_char(hire_date,'dy') day
                FROM hr.employees)
        PIVOT (count(*) FOR day IN ('월' "월요일", '화' "화요일", '수' "수요일",
                                    '목' "목요일", '금' "금요일", '토' "토요일",
                                    '일' "일요일")))
UNPIVOT (인원수 FOR 요일 in(월요일,화요일,수요일,목요일,금요일,토요일,일요일));
                            ㄴ데이터명
||
SELECT 요일||'요일' as 요일, 인원수
FROM (SELECT *
        FROM (SELECT to_char(hire_date,'dy') day
                FROM hr.employees)
        PIVOT (count(*) FOR day IN ('월' "월", '화' "화", '수' "수",
                                    '목' "목", '금' "금", '토' "토",
                                    '일' "일")))
UNPIVOT (인원수 FOR 요일 in(월,화,수,목,금,토,일));

 

[문제] 년도, 분기별 급여의 총액을 구하세요.

SELECT to_char(hire_date,'yyyy') 년도, to_char(hire_date, 'q') 분기,
        sum(salary) 총액
FROM hr.employees
GROUP BY to_char(hire_date,'yyyy'), to_char(hire_date, 'q');
↓
SELECT *
FROM (SELECT to_char(hire_date,'yyyy') 년도, to_char(hire_date, 'q') 분기,
                sum(salary) 총액
        FROM hr.employees
        GROUP BY to_char(hire_date,'yyyy'), to_char(hire_date, 'q'))
PIVOT (max(총액) FOR 분기 IN (1 "1분기", 2 "2분기", 3 "3분기", 4 "4분기"))
ORDER BY 1;

 



■ 다중열 서브쿼리

- 비쌍비교

SELECT *
FROM hr.employees
WHERE manager_id IN (SELECT manager_id
                        FROM hr.employees
                        WHERE first_name = 'John')
AND department_id IN (SELECT department_id
                        FROM hr.employees
                        WHERE first_name = 'John');
예시)
manager_id  department_id       서브쿼리에서 추출한 값
----------  --------------      ----------------------
100                 10 (O)        100 10
200                 20 (O)        100 20
300                 30 (O)        100 30
300                 10 (O)        200 10
...                 ...           200 20
                                  200 30
                                  300 10
                                  300 20
                                  300 30
                                  ...

 

- 쌍비교

SELECT *
FROM hr.employees
WHERE (manager_id, department_id) IN (SELECT manager_id, department_id
                                        FROM hr.employees
                                        WHERE first_name = 'John');
예시)
manager_id  department_id       서브쿼리에서 추출한 값
----------  -------------       ---------------------
100                 10 (O)        100 10
200                 20 (O)        200 20
300                 30 (O)        300 30
300                 10            ...
...                 ...

 

[문제] commission_pct가 null이 아닌 사원들의 department_id, salary와 일치하는
	사원들의 정보를 출력해주세요.

1) 비쌍비교
SELECT *
FROM hr.employees
WHERE department_id IN (SELECT department_id
                        FROM hr.employees
                        WHERE commission_pct IS NOT NULL)
--     └ department_id에 null은 비교대상 제외된다.
AND salary IN (SELECT salary
                FROM hr.employees
                WHERE commission_pct IS NOT NULL);
↓
SELECT *
FROM hr.employees
WHERE nvl(department_id,0) IN (SELECT nvl(department_id,0)
                        FROM hr.employees
                        WHERE commission_pct IS NOT NULL)
--     └ department_id에 있는 null을 0으로 대체하여 출력
AND salary IN (SELECT salary
                FROM hr.employees
                WHERE commission_pct IS NOT NULL);

2) 쌍비교
SELECT *
FROM hr.employees
WHERE (nvl(department_id,0), salary) IN (SELECT nvl(department_id,0), salary
                                            FROM hr.employees
                                            WHERE commission_pct IS NOT NULL);

★ NULL값의 비교대상여부, 표시여부 체크 

 


 

SELECT e.employee_id, e.last_name, e.department_id, 
        d.department_id, d.department_name 
FROM hr.employees e, hr.departments d
WHERE e.department_id = d.department_id
ORDER BY 3, 4;

>> e.department_id 107행 X d.department_id 27행
>> JOIN의 비효율성



■ SCALAR SUBQUERY
- SELECT절에 SUBQUERY를 SCALAR SUBQUERY라고 한다.
- 한행에 정확히 하나의 열값만 반환하는 서브쿼리를 의미한다.
- 동일한 입력값이 들어오면 수행 횟주를 최소화 할 수 있는 기능이 되어 있다.
- query execution cache 기능이 수행된다.
- 키값이 없는 데이터가 입력되면 null값으로 리턴한다.(OUTER JOIN 기법처럼 출력된다.)

SELECT employee_id, last_name, department_id,
        (SELECT department_name                     -- 한개값만 반환
            FROM hr.departments
            WHERE department_id = e.department_id)  -- cash 기능
FROM hr.employees e
ORDER BY 3;


SELECT employee_id, last_name, department_id,
        (SELECT department_name, manager_id         -- 오류
            FROM hr.departments
            WHERE department_id = e.department_id)
FROM hr.employees e
ORDER BY 3;
>> 여러 열값으로 출력 할 수 없다.

SELECT employee_id, last_name, department_id,
        (SELECT department_name||', '||manager_id   -- 가능
            FROM hr.departments
            WHERE department_id = e.department_id)
FROM hr.employees e
ORDER BY 3;
>> 여러 열값을 하나의 열로 수정해서 수행할 수 있다.

 

[문제] 부서이름별 급여의 총액, 평균을 출력해주세요.

1) 일반적형식(JOIN)
SELECT d.department_name, sum(e.salary), round(avg(e.salary))
FROM hr.employees e, hr.departments d
WHERE e.department_id = d.department_id
GROUP BY d.department_name;

## INLINE VIEW를 이용해서 조인의 일량을 줄이는 쿼리를 사용
SELECT department_id, sum(salary), round(avg(salary))
FROM hr.employees
GROUP BY department_id;
>> 부서ID로 검색결과 추출
↓
SELECT d.department_name, sum_sal, avg_sal
FROM (SELECT department_id, sum(salary) sum_sal, round(avg(salary)) avg_sal
        FROM hr.employees
        GROUP BY department_id) e, hr.departments d
WHERE e.department_id = d.department_id(+);
||
SELECT d.department_name, sum_sal, avg_sal
FROM (SELECT department_id, sum(salary) sum_sal, round(avg(salary)) avg_sal
        FROM hr.employees
        GROUP BY department_id) e LEFT OUTER JOIN hr.departments d
ON e.department_id = d.department_id;


2) SCALAR SUBQUERY
SELECT department_name,
        (SELECT sum(salary)
            FROM hr.employees
            WHERE department_id = d.department_id) sumsal,
        (SELECT round(avg(salary))
            FROM hr.employees
            WHERE department_id = d.department_id) avgsal
FROM hr.departments d;
>> 단일행, 단일값 출력을 위해 각각 만들었는데 hr.employees 테이블을 반복작업
↓
SELECT department_name,
        (SELECT sum(salary)||round(avg(salary))
            FROM hr.employees
            WHERE department_id = d.department_id) agg_sal
FROM hr.departments d;
>> scalar subquery로 한번에 계산하지만 sum과 avg 구분불가
↓
SELECT department_name, substr(agg_sal,1,10) sumsal,
        substr(agg_sal,11) avgsal
FROM (SELECT department_name,
            (SELECT lpad(sum(salary),10)||lpad(round(avg(salary)),10)
                FROM hr.employees
                WHERE department_id = d.department_id) agg_sal
        FROM hr.departments d);
>> lpad, substr 활용하여 분리

 

'Oracle SQL' 카테고리의 다른 글

09 WITH  (0) 2023.10.18
08 집합연산자, 그룹화, 계층검색  (0) 2023.10.17
06 SUBQUERY  (0) 2023.10.13
05 JOIN  (0) 2023.10.12
04 Null, Case, Decode, 그룹함수, GROUP BY, HAVING  (0) 2023.10.11