- cNum, cName, bCode, bName, sDate, bPrice, userID, qty 출력
SELECT s.cNum, cName, b.bCode, bName, s.sDate, bPrice, userId, qty
FROM book b
JOIN dsale d ON d.bCode = b.bCode
JOIN sale s ON s.sNum = d.sNum
JOIN cus c ON c.cNum = s.cNum
LEFT OUTER JOIN member m ON m.cNum = c.cNum
WHERE userId IS NULL;
-- 회원 판매 현황
SELECT s.cNum, cName, b.bCode, bName, s.sDate, bPrice, userId, qty
FROM book b
JOIN dsale d ON d.bCode = b.bCode
JOIN sale s ON s.sNum = d.sNum
JOIN cus c ON c.cNum = s.cNum
LEFT OUTER JOIN member m ON m.cNum = c.cNum
WHERE userId IS NOT NULL;
-- 고객별 누적판매 금액 (고객번호 cNum, 고객명 cName, 판매금액합) 출력
-- 단, 고객중 책을 한권도 구매하지 않은 고객도 출력
SELECT s.cNum, cName, NVL(SUM(bPrice*qty), 0) 합
FROM book b
JOIN dsale d ON d.bCode = b.bCode
JOIN sale s ON s.sNum = d.sNum
RIGHT OUTER JOIN cus c ON c.cNum = s.cNum
GROUP BY s.cNum, cName
ORDER BY cNum;
-- 고객별 누적 판매 금액 현황
SELECT s.cNum, cName, NVL(SUM(bPrice*qty), 0) 합
FROM book b
JOIN dsale d ON d.bCode = b.bCode
JOIN sale s ON s.sNum = d.sNum
JOIN cus c ON c.cNum = s.cNum
GROUP BY s.cNum, cName
ORDER BY cNum;
-- 고객별 누적판매 금액의 비율
SELECT s.cNum, cName, NVL(SUM(bPrice*qty), 0) 합,
ROUND(RATIO_TO_REPORT(SUM(bPrice*qty)) OVER() * 100, 1) || '%' 비율
FROM book b
JOIN dsale d ON d.bCode = b.bCode
JOIN sale s ON s.sNum = d.sNum
JOIN cus c ON c.cNum = s.cNum
GROUP BY s.cNum, cName
ORDER BY cNum;
-- 형식 1
SELECT b.bCode, bName, sNum, qty
FROM book b, dsale d
WHERE b.bCode = d.bCode;
-- EQUI JOIN
SELECT b.bCode, bName, sNum, qty
FROM book b, dsale d
WHERE b.bCode = d.bCode(+);
-- LEFT OUTER JOIN
-- 형식 2
SELECT b.bCode, bName, sNum, qty
FROM book b
JOIN dsale d ON b.bCode = d.bCode;
-- EQUI JOIN
SELECT b.bCode, bName, sNum, qty
FROM book b
LEFT OUTER JOIN dsale d ON b.bCode = d.bCode;
-- LEFT OUTER JOIN
일단 EQUI JOIN을 했을 때의 결과이다.
그냥 EQUI JOIN은 book 테이블과 dsale의 테이블의 교집합만을 구하므로, 팔린 책들만 나오는 것을 볼 수 있다.
LEFT OUTER JOIN을 사용하면, book 테이블에 있는 모든 책들이 나오고 dsale 에 있는 것과 겹치는 것은 JOIN이 되어 나온다.
LEFT 와 RIGHT 는 FROM 뒤에 써져있는 테이블을 기준으로 함에 유의하자.
WHERE 절을 사용해 JOIN할 경우 모든 자료가 나오는 테이블 말고 합쳐지는 테이블에 (+) 를 넣어준다.
LEFT OUTER JOIN을 이용할 때는
FROM 모든 자료가 나올 테이블명 LEFT OUTER JOIN 그 테이블에 붙일 테이블명 으로 사용한다.
그럼 왼쪽이 다 나오고 오른쪽에 해당하는 테이블은 왼쪽과 교집합인 부분만 나온다.
다른 예를 통해서도 알아보자.
- 판매되지 않은 책만 출력하라.
위에 서 LEFT OUTER JOIN으로 판매된 책과 판매되지 않은 책을 출력했었다.
-- 판매되지 않은 책만 출력
SELECT b.bCode, bName, sNum, qty
FROM book b
LEFT OUTER JOIN dsale d ON b.bCode = d.bCode
WHERE d.bCode IS NULL;
JOIN이 있는 절까지를 테이블로 생각하면 편하다.
JOIN절 이후에 WHERE 를 통해 조건을 줘서 판매가 되지 않은 책만 출력이 가능하다.
RIGHT OUTER JOIN
SELECT b.bCode, bName, sNum, qty
FROM dsale d, book b
WHERE d.bCode(+) = b.bCode;
SELECT b.bCode, bName, sNum, qty
FROM dsale d
RIGHT OUTER JOIN book b ON d.bCode = b.bCode;
이 쿼리의 결과는 dsale은 book테이블과 겹치는 것만 나오고 book 테이블은 다 나오는 것이므로, 아까 위에서 book 테이블과 dsale을 LEFT OUTER JOIN한 것과 동일하다.
SELECT sNum, sDate, s.cNum, m.cNum, cName, userId
FROM sale s
FULL OUTER JOIN member m ON s.cNum = m.cNum
FULL OUTER JOIN cus c ON c.cNum = s.cNum;
SELECT sNum, sDate, s.cNum, m.cNum, cName, userId
FROM cus c
FULL OUTER JOIN member m ON c.cNum = m.cNum
FULL OUTER JOIN sale s ON c.cNum = s.cNum;
첫 번째 쿼리의 결과
두 번째 쿼리의 결과
두 번째 쿼리가 더 잘 짠 쿼리이다.
JOIN을 할 때 FROM 뒤에 있는 테이블이 가지고 있는 데이터 양보다 JOIN하는 테이블이 가지고 있는 데이터양이 적어은 것이 좋다.
첫 번재 쿼리는
sale 테이블에 모든 판매 정보가 있다. member 테이블과 FULL OUTER JOIN을 해서 모든 판매 정보와 회원의 판매정보를 모두 나오게 한다. (회원/비회원 판매정보) 거기에서 cus와 FULL OUTER JOIN을 통해 이름을 출력하게 한다.
cus 테이블에는 모든 고객들이 있다. 회원 테이블과 FULL OUTER JOIN을 하면 모든 고객에서 회원인 사람과 아닌 사람들을 알 수 있게된다. (회원/비회원 여부) 거기에서 판매 테이블과 FULL OUTER JOIN을 하면 회원/비회원의 판매현황을 알 수 있다.
-- 형식 1, WHERE로 JOIN했을 시
SELECT b.bCode, bName, bPrice, sDate, s.cNum, cName, qty, bPrice * qty amt
FROM BOOK b, dsale d, sale s, cus c
WHERE b.bCode = d.bCode AND d.sNum = s.sNum AND s.cNum = c.cNum AND cName = '서울서점'
ORDER BY cNum;
-- 형식 2
SELECT b.bCode, bName, bPrice, sDate,
s.cNum, cName, qty, bPrice * qty amt
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum
WHERE cName = '서울서점'
ORDER BY cNum;
cNum을 보면 숫자가 다른데, 서울서점의 이름인 곳이 중복적으로 있었음을 확인할 수 있다.
SELECT b.bCode 책코드, bName 책이름, SUM(qty) "판매수량의 합", SUM (bPrice * qty) "판매금액 합"
FROM book b
JOIN dsale d ON b.bCode = d.bCode
GROUP BY b.bCode, bName
ORDER BY b.bCode;
- 판매된 책코드(bCode), 책이름(bName), 판매수량의 합(qty) : 단 판매수량이 80개 이상인 것만 출력
SELECT * FROM (
SELECT b.bCode 책코드, bName 책이름, SUM(qty) "판매수량의 합"
FROM book b
JOIN dsale d ON b.bCode = d.bCode
GROUP BY b.bCode, bName
ORDER BY b.bCode
) WHERE "판매수량의 합" >= 80;
SELECT b.bCode 책코드, bName 책이름, SUM(qty) "판매수량의 합"
FROM book b
JOIN dsale d ON b.bCode = d.bCode
GROUP BY b.bCode, bName
HAVING SUM(qty) >= 80;
GROUP BY로 정렬한 것은 HAVING을 써서 해당하는 조건을 만족하는 것만 출력하게 할 수 있다.
- 판매된 책코드(bCode), 책이름(bName) 중복을 배제하고 출력하라.
-- 서브쿼리
SELECT bCode, bName
FROM BOOK
WHERE bCode IN (SELECT bCode FROM dsale);
-- 조인
SELECT b.bCode "판매된 책코드", bName "책이름"
FROM book b
JOIN dsale d ON b.bCode = d.bCode
GROUP BY b.bCode, bName
HAVING SUM(qty) > 0;
-- DISTINCT
SELECT DISTINCT b.bCode "판매된 책코드", bName "책이름"
FROM book b
JOIN dsale d ON b.bCode = d.bCode;
- 판매된 책 중 판매권수의 합이 가장 큰 책코드, 책이름 출력
SELECT b.bCode 책코드, bName 책이름, SUM(qty) "판매수량의 합",
RANK() OVER(ORDER BY SUM(qty) DESC) 순위
FROM book b
JOIN dsale d ON b.bCode = d.bCode
GROUP BY b.bCode, bName;
-- 이 쿼리로 팔린 것들의 순위를 매길 수 있다.
-- 서브쿼리 방법-1
SELECT 책코드, 책이름 FROM (
SELECT b.bCode 책코드, bName 책이름,
RANK() OVER(ORDER BY SUM(qty) DESC) 순위
FROM book b
JOIN dsale d ON b.bCode = d.bCode
GROUP BY b.bCode, bName
) WHERE 순위 = 1;
-- 방법-2
SELECT b.bCode 책코드, bName 책이름
FROM book b
JOIN dsale d ON b.bCode = d.bCode
GROUP BY b.bCode, bName
HAVING SUM(qty) = (
SELECT MAX( SUM(qty))
FROM book b1
JOIN dsale d1 ON b1.bCode = d1.bCode
GROUP BY b1.bCode, bName
);
SELECT b.bCode, bName, bPrice, b.pNum, pName, sDate,
s.cNum, cName, qty, bPrice * qty amt
FROM book b
JOIN pub p ON b.pNum = p.pNum
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum
WHERE TO_CHAR(sDate, 'YYYY') = TO_CHAR(SYSDATE, 'YYYY');
JOIN 이 끝나는 곳까지를 테이블이라고 생각하면 편하다. 이 후에 WHERE을 붙여 조건을 주면 그 조건에 해당하는 값들만 출력된다.
SELECT s.cNum, cName, TO_CHAR(sDate, 'YYYY') 년도, SUM(bPrice * qty) 금액
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum
GROUP BY s.cNum, cName, TO_CHAR(sDate, 'YYYY')
ORDER BY cNum, 년도;
-- 고객의 작년과 올해 판매 현황
-- 고객의 이번달 판매금액(고객코드, 고객명, 금액) / 소비자는 고객(회원)일 수 있고 비회원일 수 있음 여기서는 고객만!
-- 고객의 전달 판매금액(고객코드, 고객명, YYYYMM, 금액)
SELECT s.cNum, cName, TO_CHAR(sDate, 'YYYY') 년도, SUM(bPrice * qty) 금액
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum
WHERE TO_CHAR(SYSDATE, 'YYYY') - TO_CHAR(sDate, 'YYYY') <= 1
GROUP BY s.cNum, cName, TO_CHAR(sDate, 'YYYY')
ORDER BY cNum, 년도;
-- 고객의 이번달 판매금액(고객코드, 고객명, 금액)
SELECT s.cNum, cName, TO_CHAR(sDate, 'YYYYMM'), SUM(bPrice * qty) 금액
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum
WHERE TO_CHAR(sDate, 'YYYYMM') = TO_CHAR(SYSDATE, 'YYYYMM')
GROUP BY s.cNum, cName, TO_CHAR(sDate, 'YYYYMM')
ORDER BY cNum;
-- 고객의 전달 판매금액(고객코드, 고객명, YYYYMM, 금액)
SELECT s.cNum, cName, TO_CHAR(sDate, 'YYYYMM') 날짜, SUM(bPrice * qty) 금액
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum
WHERE TO_CHAR(ADD_MONTHS(SYSDATE, -1), 'YYYYMM') = TO_CHAR(sDate, 'YYYYMM')
GROUP BY s.cNum, cName, TO_CHAR(sDate, 'YYYYMM')
ORDER BY cNum;
SELECT s.cNum, cName, TO_CHAR(sDate, 'YYYYMM') 날짜, SUM(bPrice * qty) 금액
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum
WHERE TO_CHAR(SYSDATE - INTERVAL '1' MONTH, 'YYYYMM') = TO_CHAR(sDate, 'YYYYMM')
GROUP BY s.cNum, cName, TO_CHAR(sDate, 'YYYYMM')
ORDER BY cNum;
ADD_MONTHS('날짜', 더하고 싶은 숫자) 를 하면 알아서 1달을 빼준다. 여기서 YYYYMM을 뽑아서 바로 빼지 않는 이유는 202101에서 1달을 빼면 202100이 되기 때문에 알아서 날짜연산을 해주는 ADD_MONTHS를 이용해야한다. (혹은 INTERVAL '숫자' 단위 를 이용한다)
-- 방법 1 : WHERE 로 JOIN한다.
SELECT bCode, bName, book.pNum, pName, bPrice
FROM book, pub
WHERE book.pNum = pub.pNum;
-- 방법 2 : JOIN 을 이용한다.
SELECT bCode, bName, b.pNum, pName, bPrice
FROM book b
JOIN pub p ON b.pNum = p.pNum;
테이블 book은 b로 별명을 주었기 때문에 book테이블의 pNum은 b.pNum 으로 표현가능하다. 두 테이블에 같은 값을 가지고 있는 pNum을 이용해 EQUI JOIN을 했기 때문에 중복되는 pNum을 SELECT에서 가져오려면 컬럼명 앞에 테이블명을 주어야한다. 안쓰면 에러(ORA-00918 : 컬럼의 정의가 애매하다. )가 발생한다. 주로 이런 상황에서는 먼저있는 테이블을 쓰기 때문에 b.pNum으로 주었다. p.pNum으로 줘도 상관은 없다.
WHERE절을 이용해서도 JOIN이 가능하지만 JOIN까지를 한 테이블로 생각하고 WHERE로 조건을 주는 것이 나는 더 편하기 때문에 방법 2를 주로 사용할 예정이다. (아니면 WHERE 절에 조인도 하고 조건도 줘야되서 조금 헷갈린다. 특히 LEFT OUTER JOIN 나 RIGHT OUTER JOIN사용 시 엄청 헷갈림)
-- 방법 1
SELECT b.bCode, bName, bPrice, b.pNum, pName, sDate,
s.cNum, cName, qty, bPrice * qty amt
FROM BOOK b, pub p, dsale d, sale s, cus c
WHERE b.pNum = p.pNum AND b.bCode = d.bCode AND d.sNum = s.sNum AND s.cNum = c.cNum;
-- 방법 2
SELECT b.bCode, pName, bPrice, b.pNum, pName, sDate,
d.sNum, s.cNum, cName, qty, bPrice * qty amt
FROM book b
JOIN pub p ON b.pNum = p.pNum
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum;
-- 방법 3
SELECT bCode, pName, bPrice, pNum, pName, sDate,
cNum, cName, qty, bPrice * qty amt
FROM book b
JOIN pub USING(pNum)
JOIN dsale USING(bCode)
JOIN sale USING(sNum)
JOIN cus USING(cNum);
-- 컬럼명이 다를 수 있어서 방법2의 EQUI JOIN을 더 많이사용한다.
-- 판매된 책코드(bCode), 책이름(bName), 판매수량(qty) 를 출력하라.
SELECT b.bCode, bName, qty
FROM book b
JOIN dsale d ON b.bCode = d.bCode
ORDER BY b.bCode;
판매수량은 dsale 테이블이 가지고 있으므로 판매된 책코드와 책이름을 알아보기 위해서는 book과 dsale테이블을 JOIN해준다.
SELECT hak, ban,
DECODE(gubun, 1, '1학기중간', 2, '1학기기말', 3, '2학기중간', 4, '2학기기말') 구분,
name, com, excel, word, com+excel+word tot,
ROUND((com+excel+word) / 3, 1) ave,
RANK() OVER(PARTITION BY hak, gubun, ban ORDER BY com+excel+word DESC) 학급석차,
RANK() OVER(PARTITION BY hak, gubun ORDER BY com+excel+word DESC) 학년석차
FROM score s
LEFT OUTER JOIN injeok i ON s.hakbeon = i.hakbeon
ORDER BY hak, gubun, ban;
-- LEFT OUTER JOIN을 활용하면, 시험을 치지 않은 학생들도 같이나옴.
-- 이 자료에서는 시험을 안친 학생이 없어서 상관없음.
-- FOREIGN KEY 의 제약 조건으로 인해 injeok이 없으면 score 자료도 없게 제약조건을 둔 것
-- 하지만 시험을 치지 않은 학생들을 배제하고 나오게 하려면 EQUI JOIN을 쓴다.
SELECT hak, ban,
DECODE(gubun, 1, '1학기중간', 2, '1학기기말', 3, '2학기중간', 4, '2학기기말') 구분,
name, com, excel, word, com+excel+word tot,
ROUND((com+excel+word) / 3, 1) ave,
RANK() OVER(PARTITION BY hak, gubun, ban ORDER BY com+excel+word DESC) 학급석차,
RANK() OVER(PARTITION BY hak, gubun ORDER BY com+excel+word DESC) 학년석차
FROM score s
JOIN injeok i ON s.hakbeon = i.hakbeon
ORDER BY hak, gubun, ban;
-- 여기서 학번이 기본키이기 때문에 꼭 학번이 존재한다.
SELECT hak, ban,
DECODE(gubun, 1, '1학기중간', 2, '1학기기말', 3, '2학기중간', 4, '2학기기말') 구분,
name, com, excel, word, com+excel+word tot,
ROUND((com+excel+word) / 3, 1) ave,
RANK() OVER(PARTITION BY hak, gubun, ban ORDER BY com+excel+word DESC) 학급석차,
RANK() OVER(PARTITION BY hak, gubun ORDER BY com+excel+word DESC) 학년석차
FROM score s
RIGHT OUTER JOIN injeok i ON s.hakbeon = i.hakbeon
ORDER BY hak, gubun, ban;
문제 2
score 테이블과 injeok 테이블을 조인하여 다음의 컬럼을 출력한다.
-- 출력할 컬럼
학년, 반, 구분, 이름, 총점, 평균, 판정
-- 처리조건
구분 : gubun 1-> 1학기중간, 2 -> 1학기기말, 3 -> 2학기중간, 4 -> 2학기기말 로 표시
판정
세 과목 모두 40점 이상이고, 평균 60점 이상 : 합격
세 과목 중 한과목이라도 40점 미만이고, 평균 60점 이상이면 : 과락
그렇지 않으면 불합격으로 처리한다.
SELECT hak 학년, ban 반, DECODE(gubun, 1, '1학기중간', 2, '1학기기말', 3, '2학기중간', 4, '2학기기말') 구분,
name 이름, com+excel+word 총점,
ROUND((com+excel+word) / 3, 1) 평균,
(CASE
WHEN com >= 40 AND excel >= 40 AND word >= 40 AND (com+excel+word) / 3 >= 60 THEN '합격'
WHEN (com+excel+word)/3 >= 60 THEN '과락'
ELSE '불합격'
END) 판정
FROM score s
JOIN injeok i ON s.hakbeon = i.hakbeon;
-- 모든 데이터 사전 테이블 정보 확인
SELECT COUNT(*) FROM DICTIONARY;
SELECT * FROM DICTIONARY;
-- 현재 사용자의 모든 객체 정보
SELECT * FROM user_objects;
-- 테이블 정보 확인
SELECT * FROM user_tables;
SELECT * FROM tabs;
SELECT * FROM tab;