Database - Oracle DB/1주차(Database - Oracle DB)

[SQL] Advanced SQL - 조인, 서브쿼리 활용

김야키 2019. 1. 4. 16:48

 

 

Advanced SQL - 조인, 서브쿼리 활용

 

 


JOIN시 참고 사항

첫 번째 : 테이블이 모델링 될때 대부분 1 : N의 관계가 됨

두 번째 : 두 테이블이 JOIN될 때 다른 테이블에 중복이 되는 값이 있는 경우가 있음

-> 회원 테이블에는 1명이 유일 하지만

-> 주문 정보 테이블에는 해당 회원이 여러번 나올 수 있음

세 번째 : 중복되는 값이 있는 테이블을 FROM절에 올려 두고 JOIN을 시도하면 해당 경우의 수 만큼의 행이 만들어 짐

-> M : N의 관계가 만들어져 M * N의 행이 만들어 짐

네 번째 : JOIN을 시도 할 때 조건절에 오는 조건은 무조건 두 테이블 관의 관계를 작성

다섯 번째 : JOIN문장이 실행 될 때 Subquery를 적절히 이용해서 테이블의 수를 먼저 GROUP BY를 시키면 성능을 올리고 오류를 줄여 줌

-> JOIN을 시도하는 행의 수는 필요에 따라 최대한 중복이 없는 행으로 만들어서 하는것이 성능면에서 높은 효율을 보임

-> 그냥 상황별로 잘 활용 하면 될듯... 문제는 경험인...

여섯 번째 : JOIN을 시도 할 때에는 두 테이블 간에 관계가 있어야 적절한 JOIN이 이루어 짐

-> 그렇지 않는다면 불필요한 JOIN문이 필요 하므로 성능 면에서 저하 됨

일곱 번째 : JOIN을 하고서 조건을 검사 하는지, 혹은 조건을 먼저 검사를 하고 JOIN을 할 것인지 생각 해야 함


 

 

1. dept, emp 테이블을 사용하여 sales 부서에 근무하고, 1981년 상반기(1월 ~ 6월)에 입사한 사원 정보를 검색 하시오. 이때 커미션을 합한 급여 (sal + comm)을 함께 검색하고 입사 일자 순으로 정렬 합니다.

 

해답 SQL문

 

SELECT

empno, ename, NVL(comm,0)+sal INCONE

FROM emp

WHERE

    deptno IN (SELECT deptno FROM dept WHERE dname = 'SALES')

    -- 만약, SALES라는 부서의 이름이 중복 되는 값이 있다면, '='보다는 'IN'을 사용 한다.

AND

    hiredate BETWEEN

        TO_DATE('19810101','YYYYMMDD')

    AND

        TO_DATE('19810701','YYYYMMDD')- 1/86400

        -- 1981y/06h/30d 23h:59m:59s

ORDER BY hiredate;

 

-- 실행 결과 --

 

3 rows selected.


 

2. dept, emp 테이블을 사용하여 job이 'MANAGER'인 사원들의 부서 정보 및 사원 정보를 검색 하시오.

 

해답 SQL

 

SELECT 

    D.deptno, D.dname, 

    E.ename, E.sal

FROM 

    dept D JOIN emp E

ON 

    D.deptno = E.deptno

AND 

    E.job = 'MANAGER';

 

-- 실행 결과 --

 

3  rows selected.


 

3. dept, emp 테이블을 사용하여, 소속 부서의 평균 급여보다 많은 급여를 받는 'MANAGER' 들의 부서 번호, 사원 번호, 사원 이름, 급여를 검색 하시오.

 

해답 SQL문

 

SELECT 

    D.deptno, D.dname, 

    E.empno, E.ename, E.sal

FROM 

    dept D 

JOIN

emp E

ON 

    D.deptno = E.deptno

WHERE

    E.job = 'MANAGER'

AND

    E.sal > (SELECT AVG(sal) FROM emp WHERE deptno = E.deptno);

 

-- 평균 급여까지 확인하고 싶을 때는 Subquery를 JOIN에 이용한다.

SELECT 

    D.deptno, D.dname,

    E.empno, E.ename, E.sal, A.AVG_SAL

FROM emp E

JOIN (SELECT deptno, AVG(sal) AVG_SAL FROM emp GROUP BY deptno) A

ON 

    E.deptno = A.deptno

AND

    E.sal > A.AVG_SAL

AND

    E.job = 'MANAGER'

JOIN dept D

ON

    D.deptno = E.deptno;

 

-- 실행 결과 --

 

2 rows selected.

 


 

4. dept. emp 테이블을 사용하여 각 부서의 소속 사원 유무를 확인하는 검색 결과를 만드시오.

emp 컬럼은 소속 사원이 존재할 때 'YES', 아니면 'NO'를 검색합니다.

 

해답 SQL문

 

-- 검색을 하는 테이블은 emp도 하지만, 

-- 정확하게 테이블 상에 올려서 보여 주는 내용은 dept테이블의 정보 뿐임

-- 따라서 FROM절에 올려 줄 테이블은 dept 뿐

SELECT 

    D.*, 

    NVL((   SELECT 'YES' 

            FROM DUAL 

            WHERE EXISTS (  SELECT * 

                            FROM emp 

                            WHERE deptno = D.deptno)),'NO') EMP

FROM dept D;

 

-- 실행 결과 --

 

4 rows selected.

 


 

5. countries, employees 테이블을 이용하여 'Canada'에서 근무 중인 사원 정보를 다음과 같이 검색하시오. 만약 추가적으로 필요한 테이블이 더 있다면 함께 사용합니다.

 

해결 SQL문

 

-- countries 테이블과 employees 테이블은 설계상 연결되는 부분이 없기 때문에

-- 불필요한 JOIN작업이 이루어 짐 (설계의 중요성)

-- 모델링이 잘못됨을 보여주는 문제

 

SELECT * FROM countries;   

-- country_name = 'Canada'

SELECT * FROM departments;  

-- location_id = E.location_id

SELECT * FROM employees;    

-- first_name, last_name, salary, job_id

-- E.department_id = D.department_id

SELECT * FROM locations;

-- L.location_id = D.location_id

 

SELECT 

    E.first_name, E.last_name,

    E.salary, E.job_id, 

    C.country_name

FROM employees E

JOIN departments D

ON

    E.department_id = D.department_id

JOIN locations L

ON

    D.location_id = L.location_id

JOIN countries C

ON

    L.country_id = C.country_id

    AND C.country_name = 'Canada';

 

-- 실행 결과 --

 

2 rows selected.

 


 

6. custmoers, orders, wishlist 테이블을 이용하여, wishlist(관심 상품)에 저장된 상품이있는 고객의 주문 합계(SUM(orders.order_total))를 검색 하시오.

참고, wishlist.deleted 컬럼이 'N'인 행이 현재 관심상품을 의미한다.

 

해답 SQL문

 

-- 참고 테이블

SELECT * FROM customers;       -- 고객 정보

SELECT * FROM orders;            -- 주문 내역

SELECT * FROM wishlist;           -- 관심 상품

 

-- 검색된 내용은 customers와 orders에 저장된 내용만 있음

-- 따라서 FROM절에 올라올 테이블은 customers와 orders뿐이고, 

-- wishlist테이블은 Subquery로서 사용된다.

SELECT

    C.cust_id, C.cust_fname, C.cust_lname,

    SUM(O.order_total)

FROM customers C

JOIN orders O

ON C.cust_id = O.cust_id

AND EXISTS (

        SELECT cust_id 

        FROM wishlist

        WHERE deleted = 'N' 

        AND cust_id = C.cust_id )

GROUP BY C.cust_id, C.cust_fname, C.cust_lname

ORDER BY 1;

 

-- 실행 결과 --

 

42 rows selected.

 


 

7. customers, orders, wishlist 테이블을 이용하여 고객별 주문 금액의 합계(SUM(order_total))와 관심상품 목록의 합계(SUM(unit_price*quantity))를 검색하시오.

참고, wishlist.deleted 컬럼이 'N'인 행이 현재 관심상품을 의미한다.

 

해답 SQL문

 

SELECT 

    C.cust_id, C.cust_fname, C.cust_lname, 

    O.ORD_TOT, 

    W.WISH_TOT

FROM customers C

JOIN (

        SELECT 

            cust_id, SUM(order_total) ORD_TOT

        FROM orders 

        GROUP BY cust_id    ) O 

-- JOIN을 시도하는 행의 개수를 중복이 없는 cust_id의 개수 만큼으로 줄임

ON

    C.cust_id = O.cust_id

JOIN (

        SELECT 

            cust_id, SUM(unit_price * quantity) WISH_TOT

        FROM wishlist 

        WHERE deleted = 'N' 

        GROUP BY cust_id    ) W 

-- JOIN을 시도하는 행의 개수를 중복이 없는 cust_id의 개수 만큼으로 줄임

ON 

    C.cust_id = W.cust_id

ORDER BY 1;

 

 

-- 실행 결과 --

 

42 rows selected.

 

 


 

8. prods, sales 테이블을 이용하여 제품별 판매 수량(quantity_sold)의 합계를 검색하시오.

단, 판매되지 않은 제품이 존재한다면 해당 제품도 함께 표시

 

해답 SQL문

 

-- prods테이블에는 prod_id = 138이 존재 함

-- sales테이블에는 prod_id = 138이 없음

SELECT P.prod_id, P.prod_name, NVL(S.SOLD_SUM,0)

FROM prods P LEFT OUTER JOIN (

                    SELECT prod_id, SUM(quantity_sold) AS SOLD_SUM

                    FROM sales

                    GROUP BY prod_id) S

ON P.prod_id = S.prod_id;

 

-- 실행 결과 --

 

  72 rows selected.

 

 


 

9. emp 테이블에서 1981년도에 입사한 사원들을 입사 월별로 인원수를 검색하시오.

단, 사원이 없는 월도 함께 출력

 

해답 SQL문

 

SELECT b.hire, NVL(a.cnt,0) CNT

FROM (

        SELECT 

            TO_CHAR(hiredate,'YYYY/MM') hire, count(*) cnt

        FROM emp

        WHERE 

            hiredate 

                BETWEEN 

                    TO_DATE('81/01/01','RR/MM/DD')

                AND 

                    TO_DATE('81/12/31','RR/MM/DD')

        GROUP BY TO_CHAR(hiredate,'YYYY/MM') 

    ) a,

    (

        SELECT 

            '1981/'||LPAD(LEVEL,2,0) hire

        FROM dual

        CONNECT BY LEVEL <= 12

    ) b -- 더미 데이터

WHERE 

    a.hire (+) = b.hire

ORDER BY 1 ;

 

-- 실행 결과 --

 

12 rows selected.