💬 8일차 후기: 진도가 후루룩 나가서 조인문과 서브쿼리까지 배웠다. 한 번 배우고 나서 다시 배우는 것인데도 Outer 조인, 복잡한 서브쿼리 작성은 계속 헷갈린다😵 이론으로 외우는 것보다도 문제를 여러 개 풀어보면서 정리하는 게 좋을 것 같다! 오늘도 간단하게 실습 내용 정리 후 배울 점이 있었던 문제 풀이 소개 시작🙏
⛔ 오늘 문제 스압 주의 ⛔
: 행 집합 연산을 수행하여 그룹별로 하나의 결과를 산출하는 함수
COUNT
: 속성 값의 개수COUNT(*)
: NULL 값도 포함해서 모든 튜플 개수 반환, 전체 튜플 수 파악에 용이COUNT(특정 속성)
: 특정 속성 안에 NULL 값 있으면 제외하고 개수 반환COUNT(DISTICT 특정 속성)
: 특정 속성 안 중복 제외 후 개수 반환MAX
: 속성 값의 최댓값MIN
: 속성 값의 최솟값SUM
: 속성 값의 합계 / 숫자 데이터에만 사용 가능AVG
: 속성 값의 평균 / 숫자 데이터에만 사용 가능 NULL 값 포함 ❌✅ AVG
함수는 NULL 값 포함하지 않고 평균 연산
✅ NULL 값을 포함해서 연산하고 싶다면,
SUM(특정 속성) / COUNT(*)
혹은 AVG(NVL(특정 속성, 0))
GROUP BY
키워드로 그룹을 나누는 기준이 되는 속성 지정, 그룹 함수랑 함께 쓰임
그룹에 대한 조건은 , WHERE
HAVING
절에 표현
가독성을 위해 기준이 되는 속성(GROUP BY 대상)을 SELECT문에도 작성하는 것 권장 (SELECT문에 없어도 에러 아님)
그룹에 대한 조건은 HAVING절에 작성
그룹에 대한 조건은 WHERE절에 작성할 수 없지만, 그룹 외 추가 조건은 WHERE절에 우선 작성하여 연산 복잡도를 낮출 수 있음
그룹 함수(집계 함수) 외에 (SELECT절에 있는)분류 기준이 되는 속성은 모두 GROUP BY절에도 있어야 함
중복 값이 없는 GROUP BY는 의미 없음 (묶인 것이 없는 경우를 말함)
두 개 이상 값을 기준으로 그룹핑 할 수 있음, 우선순위대로 나열
특정 속성을 기준으로 묶어놓은 GROUP BY절에서 그룹에 대한 조건을 명시하는 절
= GROUP BY
전용 조건절
WHERE
절을 대체하는 것이 아님, 용도가 다름
WHERE
절은 그룹이 아닌 기존 속성에 대한 조건을 명시
HAVING
절은 그룹에 대한 조건을 명시
SELECT deparment_id, SUM(salary)
FROM employees
WHERE hiredate < TO_DATE('2000/01/01','YYYY/MM/DD') -- 기존 속성에 대한 조건
GROUP BY department_id
HAVING SUM(salary) > 50000 -- 그룹별로 나누어 연산한 SUM값에 대한 조건
: GROUP BY나 서브쿼리를 사용하지 않고 그룹 함수(SUM,AVG,... & GROUP BY) 사용과 정렬(ORDER BY)을 가능하게 하는 함수
ORDER BY, GROUP BY 서브쿼리를 개선하기 위해 나온 함수
그룹 함수() OVER(PARTITION BY 컬럼 / ORDER BY 컬럼 / 세부 분할 기준)
PARTITION BY 컬럼
: 어느 컬럼을 기준으로 나눌지 명시, GROUP BY와 동일한 기능
ORDER BY 컬럼
: 정렬 시 기준을 설정, ORDER BY와 동일한 기능
세부 분할 기준(windowing_clause)
: PARTITION BY, ORDER BY로 충분히 분할하지 못했을 경우 사용, ORDER BY를 사용한 상태에서만 적용 가능
[참고] over() 함수 사용 예시 - 행복한 수지아빠님 블로그
1) *(INNER) JOIN : 두 개의 테이블에서 컬럼값 비교 후 JOIN 조건에 맞는 행만 반환(기본 조인)
2) *OUTER JOIN(LEFT/RIGHT) : 두 개의 테이블에서 JOIN 조건에 LEFT, RIGHT 각각 매치되는 값이 없더라도 NULL값을 넣어 선택된 쪽의 결과를 반환
3) FULL OUTER JOIN : 매치되지 않은 LEFT, RIGHT 값 모두 반환
4) CROSS JOIN : 한 테이블의 모든 행을 다른 테이블의 모든 행과 조인(그닥 유용하지 않음) => Cartesian Product
5) **SELF JOIN : 테이블 하나를 두 개처럼 인식하고 자기 자신과 조인
(+)
가 붙지 않은 쪽 OUTER JOIN임SELECT *
-- 기본 형식
SELECT ___, ___, ___
FROM ___ a, ___ b
WHERE a._____ = b._____;
-- 조건 추가(AND만 가능)
SELECT ___, ___, ___
FROM ___ a, ___ b
WHERE a._____ = b._____
AND ________;
-- 여러 테이블 JOIN
SELECT ___, ___, ___
FROM ___ a, ___ b, ___ c
WHERE a._____ = b._____ AND b._____ = c._____;
-- OUTER JOIN(LEFT, RIGHT)
SELECT ___, ___, ___
FROM ___ a, ___ b
WHERE a._____ = b._____ (+); -- LEFT OUTER JOIN
SELECT ___, ___, ___
FROM ___ a, ___ b
WHERE a._____ (+) = b._____; -- RIGHT OUTER JOIN
-- CROSS JOIN(카티션 프로덕트)
SELECT *
FROM ___ a, ___ b;
테이블 1 JOIN 테이블2 ON
으로 명시JOIN-ON
형식 유지-- 기본 형식
SELECT ___, ___, ___
FROM ___ a
JOIN ___ b ON a._____ = b._____;
-- 조건 추가(AND, WHERE 가능)
SELECT ___, ___, ___
FROM ___ a
JOIN ___ b ON a._____ = b._____
AND ________; 혹은 WHERE ________;
-- 여러 테이블 JOIN(JOIN-ON 형식 유지)
SELECT ___, ___, ___
FROM ___ a
JOIN ___ b ON a._____ = b._____
JOIN ___ c ON a._____ = c._____
-- OUTER JOIN(LEFT, RIGHT, FULL)
SELECT ___, ___, ___
FROM ___ a
LEFT JOIN ___ b ON a._____ = b._____; -- LEFT OUTER JOIN
SELECT ___, ___, ___
FROM ___ a
RIGHT JOIN ___ b ON a._____ = b._____; -- RIGHT OUTER JOIN
SELECT ___, ___, ___
FROM ___ a
FULL JOIN ___ b ON a._____ = b._____; -- FULL OUTER JOIN
-- CROSS JOIN(카티션 프로덕트)
SELECT *
FROM ___ a
CROSS JOIN ___ b;
ON
: 조인 조건을 명시USING
: 두 테이블 내 조인 기준 컬럼 이름이 같을 때,해당 컬럼을 기준으로 조인함을 명시 (USING 쓸 때는 alias 사용 ❌)SELECT employee_id, salary, department_id
FROM employees e
JOIN departments d USING(department_id);
문제 4-9) 부서번호, 부서번호별 토탈월급을 가로로 출력하시오 (부서번호를 모두 안다고 가정)
10 20 30
------ ------ ------
8750 10875 9400
SELECT
SUM(DECODE(deptno, 10, sal)) AS "10",
SUM(DECODE(deptno, 20, sal)) AS "20",
SUM(DECODE(deptno, 30, sal)) AS "30"
FROM emp;
문제 4-17) 직무, 부서번호, 직무별 부서번호별 토탈월급을 출력하시오
SELECT job,
SUM(DECODE(deptno, 10, sal)) AS "10",
SUM(DECODE(deptno, 20, sal)) AS "20",
SUM(DECODE(deptno, 30, sal)) AS "30"
FROM emp
GROUP BY job;
문제 4-19) 입사한 년도(4자리), 입사한 년도별 토탈월급을 가로로 출력하시오
1980 1981 1982 1983
------ ------ ------ ------
800 22825 4300 1100
SELECT
SUM(DECODE(TO_CHAR(hiredate,'YYYY'), 1980, sal)) AS "1980",
SUM(DECODE(TO_CHAR(hiredate,'YYYY'), 1981, sal)) AS "1981",
SUM(DECODE(TO_CHAR(hiredate,'YYYY'), 1982, sal)) AS "1982",
SUM(DECODE(TO_CHAR(hiredate,'YYYY'), 1983, sal)) AS "1983"
FROM emp;
Q8. EMP 테이블에서 DEPTNO, JOB 컬럼으로 Grouping 된 급여의 합계를 다음과 같이 검색하시오
-- 배운 내용으로 푸는 방법
SELECT deptno,
SUM(DECODE(job, 'ANALYST', sal))
,SUM(DECODE(job, 'CLERK', sal))
,SUM(DECODE(job, 'PRESIDENT', sal))
,SUM(DECODE(job, 'SALESMAN', sal))
FROM emp
GROUP BY deptno;
-- PIVOT을 활용해 간결하게 푸는 방법
SELECT *
FROM (SELECT deptno, job, sal FROM emp)
PIVOT(SUM(sal) FOR job IN ('ANALYST' AS analyst,
'CLERK' AS cleark,
'PRESIDENT' AS president,
'SALESMAN' AS salesman))
ORDER BY deptno;
-- ROLLUP을 활용해 총계를 구하는 방법
SELECT deptno,
SUM(DECODE(job, 'ANALYST', sal))
,SUM(DECODE(job, 'CLERK', sal))
,SUM(DECODE(job, 'PRESIDENT', sal))
,SUM(DECODE(job, 'SALESMAN', sal))
FROM emp
GROUP BY ROLLUP(deptno);
🔍 PIVOT 함수
: 그룹함수와 함께 사용되어 행으로 표현되던 값을 열로 변환
PIVOT(그룹합수(집계컬럼) FOR 피벗대상컬럼 IN ([피벗컬럼값] ... ) AS pivot_result
🔍 ROLLUP과 CUBE
-ROLLUP()
: GROUP BY절과 함께 사용되어 소계, 총계를 구해줌
-CUBE()
: GROUP BY 항목들 간 모든 경우의 수로 그룹을 생성하게 집계, ROLLUP()보다 더 세세하게 나올 수 있는 모든 소계와 총계를 구해서 반환
Q9. EMP 테이블에서, 'JONES' (ENAME)보다 더 많은 급여(SAL)를 받는 사원을 검색하시오 (단, JONES의 급여도 함께 검색합니다.) ⭐서브쿼리 활용
SELECT e.empno, e.ename, e.sal, s.sal AS "Jones's salary"
FROM emp e, (SELECT sal
FROM emp
WHERE ename = 'JONES') s
WHERE e.sal > s.sal;
Q10. DEPT, EMP 테이블을 사용하여 부서별 급여의 합계를 다음과 같이 검색하시오. 근무하는 사원이 없는 부서도 함께 표시합니다.
-- 조인 풀이
SELECT d.deptno, d.dname, SUM(e.sal)
FROM dept d, emp e
WHERE d.deptno = e.deptno(+)
GROUP BY d.deptno, d.dname;
Q11. DEPT, EMP 테이블을 사용하여 각 부서의 소속 사원 유무를 확인하는 검색 결과를 만드시오. EMP 컬럼은 소속 사원이 존재할 때 'YES', 아니면 'NO'를 검색합니다.
SELECT d.deptno, d.dname, d.loc,
CASE WHEN COUNT(e.empno) > 0 THEN 'YES'
ELSE 'NO' END AS EMP
FROM dept d, emp e
WHERE d.deptno = e.deptno(+)
GROUP BY d.deptno, d.dname, d.loc;
Q12. COUNTRIES, EMPLOYEES 테이블을 이용하여, 'Canada'에서 근무 중인 사원 정보를 다음과 같이 검색하시오. 만약 추가적으로 필요한 테이블이 더 있다면 함께 사용합니다.
SELECT e.first_name, e.last_name, e.salary, e.job_id, c.country_name
FROM employees e, departments d, locations l, countries c
WHERE c.country_id = l.country_id AND c.country_name = 'Canada'
AND l.location_id = d.location_id
AND d.department_id = e.department_id;
Q13. EMP 테이블에서 1981년도에 입사한 사원들을 입사 월별로 인원수를 검색하시오. 단, 사원이 없는 월도 함께 출력 ⭐⭐⭐
SELECT a.hire, NVL(b.cnt, 0) AS cnt
FROM (SELECT '1981/' || lpad(level,2,0) AS hire
FROM dual CONNECT BY level <= 12) a,
(SELECT TO_CHAR(hiredate, 'YYYY/MM') AS hire, COUNT(*) AS cnt
FROM emp
WHERE TO_CHAR(hiredate, 'YYYY') = '1981'
GROUP BY TO_CHAR(hiredate, 'YYYY/MM')) b
WHERE a.hire = b.hire(+)
ORDER BY a.hire;
🔍 CONNECT BY LEVEL
: 연속된 숫자를 조회할 때 사용
SELECT level FROM dual CONNECT BY level <= n;
의 형태로 사용
연속된 날짜 형식을 출력한다거나, 특정 구간의 날짜를 출력할 때 활용 가능
CONNECT BY LEVEL
을 활용lpad(기존 값, 총 자릿수, 빈 부분을 채워줄 값)
함수 사용NVL()
함수로 0으로 변환문제 5-7) 부서위치, 이름, 월급, 순위를 출력하는데 순위가 월급이 높은 순서대로 순위를 출 력하시오
SELECT d.loc, e.ename, e.sal, RANK() OVER (ORDER BY e.sal DESC)
FROM emp e, dept d
WHERE d.deptno = e.deptno;
🔍 일련 번호, 순위를 구하는 함수
ROW_NUMBER() OVER(ORDER BY 정렬 필드)
: 같은 값은 무시, (1,2,3,4,5)RANK() OVER(ORDER BY 정렬 필드)
: 같은 값은 동등한 값으로 값을 반환, (1,2,2,4,5)DENSE_RANK() OVER(ORDER BY 정렬 필드)
: 같은 값을 동등한 값으로 반환, 다음 순위는 순차적으로 적용, (1,2,2,2,3,4)
문제 5-9) 부서위치, 해당 부서위치에 근무하는 사원들의 이름을 가로로 출력하시오
SELECT d.dname,
LISTAGG(e.ename,',') WITHIN GROUP (ORDER BY ename ASC)
FROM emp e, dept d
WHERE e.deptno=d.deptno
GROUP BY d.dname;
🔍 LISTAGG() 함수
: 여러 행을 하나의 컬럼으로 보고 싶을 때 사용하는 함수
LISTAGG([합칠 컬럼명], [구분자]) WITHIN GROUP(ORDER BY [정렬 컬럼명])
LISTAGG( ) 함수는 그룹 함수이기 때문에 GROUP BY 또는 PARTITION BY 절과 함께 사용해야함
문제 5-27) 이름과 부서위치를 출력하는데 naturual 조인으로 수행하시오
SELECT ename, loc
FROM emp NATURAL JOIN dept;
🔍 NATURAL JOIN(자연 조인)
: 자연 조인은 등가 조인하는 방법 중 하나로 동일한 타입과 이름을 가진 컬럼을 조인 조건으로 이용함
- 자연조인을 하기 위해서는 반드시 두 테이블 간 동일한 타입, 동일한 이름을 가진 조인 조건 컬럼이 있어야 함
- 조인 조건을 명시해주지 않아도 알아서 조인
- SELECT절에 두 테이블 간 이름이 겹치는 컬럼이 있더라도 별칭(alias) 사용 ❌