SQL 스터디 1주차!
🖇 GROUP BY
- 데이터를 그룹화하는 문법. 같은 값을 가진 데이터끼리 모으는 것.
- 중복된 값이 제거되어 결과로 나올 수 있도록 한다 =
DISTINCT
의 역할과 같다.DISTINCT
와 다른 점: SUM, MIN 같은 집계함수를 쓸 수 있다.
- TO_CHAR, TO_DATE같은 기본 함수와 CASE문 같은 문법을 쓸 수 있다.
- 문법 :
WHERE 절
과ORDER BY 절
사이에 쓴다.SELECT 절
에는GROUP BY
에 사용할 컬럼을 똑같이 써준다.
주요 집계함수
- SUM : 수량, 금액과 같은 숫자형 데이터의 합
- COUNT : 데이터의 건수
- MIN : 데이터의 최솟값
- MAX : 데이터의 최댓값
- 집계함수는
GROUP BY
가 없어도 단독 사용할 수 있다. - 집계함수 괄호 안에서도
CASE문
을 사용할 수 있다. - 집계함수를 사용하지 않은 컬럼은
SELECT절
에서 같이 사용할 수 없다.
-- 집계함수 - 에러가 발생하는 SQL
SELECT T1.ORD_ST -- 집계함수를 사용하지 않은 컬럼
,COUNT(*) CNT
,SUM(T1.ORD_AMT) TTL_ORD_AMT
,MIN(T1.ORD_SEQ) MIN_ORD_SEQ
,MAX(T1.ORD_SEQ) MAX_ORD_SEQ
FROM T_ORD T1
WHERE T1.ORD_DT>= TO_DATE('20170101','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170201','YYYYMMDD');
-- 집계함수 - 정상적인 SQL
SELECT COUNT(*) CNT
,SUM(T1.ORD_AMT) TTL_ORD_AMT
,MIN(T1.ORD_SEQ) MIN_ORD_SEQ
,MAX(T1.ORD_SEQ) MAX_ORD_SEQ
FROM T_ORD T1
WHERE T1.ORD_DT >= TO_DATE('20170101','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170201','YYYYMMDD');
COUNT 집계 함수 사용시 주의점
NULL을 집계하는지?
- COUNT는 NULL 값을 0으로 카운트한다.
- 그러나, COUNT(*)은 로우 자체를 건수로 카운트하여 로우를 구성하는 컬럼이 모두 NULL이어도 1로 카운트한다.
중복을 제거하려면?
- COUNT(DISTINCT 컬럼명)으로 중복을 제거할 수 있다.
- 한번이라도 ~~한 데이터를 찾을 때 유용하다.
-- 주문년월별 주문고객 수(중복을 제거해서 카운트), 주문건수
SELECT TO_CHAR(T1.ORD_DT,'YYYYMM') ORD_YM
,COUNT(DISTINCT T1.CUS_ID) CUS_CNT
,COUNT(*) ORD_CNT
FROM T_ORD T1
WHERE T1.ORD_DT >= TO_DATE('20170101','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170401','YYYYMMDD')
GROUP BY TO_CHAR(T1.ORD_DT,'YYYYMM')
ORDER BY TO_CHAR(T1.ORD_DT,'YYYYMM');
-
COUNT(DISTINCT)
는 여러 컬럼을 동시에 사용할 수 없다. 2개 컬럼 사용시||
로 결합해 사용해야 한다.--USE CONCAT SELECT COUNT(DISTINCT T1.ORD_ST||'-'||T1.PAY_TP) FROM T_ORD T1;
SELECT ~ EXISTS로 대신하기
DISTINCT
가 아닌 EXISTS
로 구현할 수도 있다.
HAVING
GROUP BY
가 수행된 결과 집합에 조건을 줄 때 사용- WHERE 절과 같은 기능
- 문법 :
GROUP BY
뒤에 위치.ORDER BY
앞에 위치.AND
,OR
사용 가능 GROUP BY
에 정의한 컬럼은 그대로 사용할 수 있으나,GROUP BY
에 정의하지 않은 내용은 집계함수 처리 후에 사용해야 한다.- 인라인-뷰에 대한 WHERE절로 대신하는 방법:
GROUP BY
결과를 인라인-뷰로 처리, 인라인-뷰 바깥에서 WHERE절로 처리
📍GROUP BY 관련 기억할 것들
GROUP BY
에 사용한 컬럼만 SELECT 절에서 그대로 사용할 수 있다.GROUP BY
에 사용하지 않은 컬럼은 SELECT 절에서 집계함수를 사용해야 한다.- HAVING 사용시
GROUP BY
에 정의한 컬럼은 그대로 사용할 수 있으나,GROUP BY
에 정의하지 않은 내용은 집계함수 처리 후에 사용해야 한다.
🖇 ROLLUP
- 소계와 전체합계가 필요한 경우 사용
- 문법 :
GROUP BY
뒤에 ROLL UP이라고 적어서 사용- 예시 : GROUP BY ROLL UP(A,B,C,D)
- GROUP BY된 A+B+C+D별 데이터
- A+B+C별 소계 데이터
- A+B별 소계 데이터
- A별 소계 데이터
- 전체합계
- 예시 : GROUP BY ROLL UP(A,B,C,D)
-- GROUP BY
SELECT TO_CHAR(T1.ORD_DT,'YYYYMM') ORD_YM
,T1.CUS_ID
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
WHERE T1.CUS_ID IN ('CUS_0001','CUS_0002')
AND T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD')
GROUP BY TO_CHAR(T1.ORD_DT,'YYYYMM') ,T1.CUS_ID
ORDER BY TO_CHAR(T1.ORD_DT,'YYYYMM') ,T1.CUS_ID;
-- GROUP BY~ROLLUP
SELECT TO_CHAR(T1.ORD_DT,'YYYYMM') ORD_YM
,T1.CUS_ID
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
WHERE T1.CUS_ID IN ('CUS_0001','CUS_0002')
AND T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD')
GROUP BY
ROLLUP(TO_CHAR(T1.ORD_DT,'YYYYMM') ,T1.CUS_ID)
ORDER BY TO_CHAR(T1.ORD_DT,'YYYYMM') ,T1.CUS_ID;
ROLL UP 주의사항 : 컬럼 순서
-
컬럼 순서에 따라 다른 소계가 나온다. 컬럼 순서대로 계층적 소계를 만들기 때문.
- 예시1 : GROUP BY ROLL UP(A,B,C,D)
- GROUP BY된 A+B+C+D별 데이터
- A+B+C별 소계 데이터
- A+B별 소계 데이터
- A별 소계 데이터
- 전체합계
- 예시2 : GROUP BY ROLL UP(B,A,C,D)
- GROUP BY된 B+A+C+D별 데이터
- B+A+C별 소계 데이터
- B+A별 소계 데이터
- B별 소계 데이터
- 전체합계
- 예시1 : GROUP BY ROLL UP(A,B,C,D)
특정 부분 소계만 구하고 싶을 때
ROLLUP
의 위치를 옮기거나 소계가 필요한 대상을 괄호로 조정한다.
컬럼 묶기
- 여러 컬럼을 하나의 괄호로 묶을 수 있다.
- 전체합계와 일부 컬럼만 소계를 내야할 때 유용하다.
-- 주문년월, 지역ID, 고객등급별 주문금액 – 전체 합계만 구하기
SELECT CASE WHEN GROUPING(TO_CHAR(T2.ORD_DT,'YYYYMM'))=1 THEN 'Total'
ELSE TO_CHAR(T2.ORD_DT,'YYYYMM') END ORD_YM
,CASE WHEN GROUPING(T1.RGN_ID) = 1 THEN 'Total' ELSE T1.RGN_ID END RGN_ID
,CASE WHEN GROUPING(T1.CUS_GD) = 1 THEN 'Total' ELSE T1.CUS_GD END CUS_GD
,SUM(T2.ORD_AMT) ORD_AMT
FROM M_CUS T1
,T_ORD T2
WHERE T1.CUS_ID = T2.CUS_ID
AND T2.ORD_DT >= TO_DATE('20170201','YYYYMMDD')
AND T2.ORD_DT < TO_DATE('20170401','YYYYMMDD')
AND T1.RGN_ID IN ('A','B')
GROUP BY ROLLUP((TO_CHAR(T2.ORD_DT,'YYYYMM') ,T1.RGN_ID ,T1.CUS_GD))
ORDER BY TO_CHAR(T2.ORD_DT,'YYYYMM') ,T1.RGN_ID ,T1.CUS_GD;
전체 합계를 구할 때는 괄호가 두개다.
ROLLUP안에 정의된 모든 컬럼이 하나로 인식되어, 전체합계만 구할 수 있다.
GROUPING
- 컬럼값이 소계인지 아닌지 구분해준다. 해당 컬럼이 ROLLUP 처리되었으면 1을 반환하고, 그렇지 않으면 0 반환.
- ROLLUP으로 소계를 만든 컬럼이 NULL로 표시되어, 실제 데이터가 NULL인지, ROLLUP된 결과인지 구분하기 어려워지기 때문에 GROUPING이 필요하다.
- ROLLUP된 컬럼을 다른 값으로 치환할 때는 꼭 GROUPING을 사용해야 한다.
-- NULL이 존재하는 컬럼인 PAY_TP에 대해 ROLLUP을 수행. GROUPING함수 사용
SELECT T1.ORD_ST ,GROUPING(T1.ORD_ST) GR_ORD_ST
,T1.PAY_TP ,GROUPING(T1.PAY_TP) GR_PAY_TP
,COUNT(*) ORD_CNT
FROM T_ORD T1
GROUP BY ROLLUP(T1.ORD_ST, T1.PAY_TP);
📍ROLLUP 관련 기억할 것들
- 여러 개 칼럼이
GROUP BY
될 때 전체 합계만 필요하다면 GROUP BY ROLLUP((A, B, C, D))와 같이 사용한다. - 여러 칼럼 중 앞 3개 컬럼까지의 소계와 전체합계가 필요하다면 GROUP BY ROLLUP(A, B, C, (D, E, F))와 같이 사용한다.
ROLLUP을 대신하는 세 가지 방법
1. UNION ALL
ROLLUP
대신에 흔하기 사용하는 방법.- 성능에서 손해를 볼 수 있다.
UNION ALL
이 많아질수록SELECT절
의 컬럼 순서를 맞추는 작업이 번거롭다.
-- ROLLUP을 UNION ALL로 대신하기
SELECT TO_CHAR(T1.ORD_DT,'YYYYMM') ORD_YM ,T1.CUS_ID
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
WHERE T1.CUS_ID IN ('CUS_0001','CUS_0002')
AND T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD')
GROUP BY TO_CHAR(T1.ORD_DT,'YYYYMM') ,T1.CUS_ID
UNION ALL
SELECT TO_CHAR(T1.ORD_DT,'YYYYMM') ORD_YM ,'Total' CUS_ID
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
WHERE T1.CUS_ID IN ('CUS_0001','CUS_0002')
AND T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD')
GROUP BY TO_CHAR(T1.ORD_DT,'YYYYMM')
UNION ALL
SELECT 'Total' ORD_YM ,'Total' CUS_ID
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
WHERE T1.CUS_ID IN ('CUS_0001','CUS_0002')
AND T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD');
2. 카르테시안 조인
조인조건을 주지 않고 집합을 구성하는 모든 데이터의 조인이 발생한다. 조인 과정에서 성능 저하가 발생할 수 있다.
-- ROLLUP을 카테시안 조인으로 대신하기
SELECT CASE WHEN T2.RNO = 1 THEN TO_CHAR(T1.ORD_DT,'YYYYMM')
WHEN T2.RNO = 2 THEN TO_CHAR(T1.ORD_DT,'YYYYMM')
WHEN T2.RNO = 3 THEN 'Total' END ORD_YM
,CASE WHEN T2.RNO = 1 THEN T1.CUS_ID
WHEN T2.RNO = 2 THEN 'Total'
WHEN T2.RNO = 3 THEN 'Total' END CUS_ID
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
,(
SELECT ROWNUM RNO FROM DUAL CONNECT BY ROWNUM <= 3
) T2
WHERE T1.CUS_ID IN ('CUS_0001','CUS_0002')
AND T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD')
GROUP BY CASE WHEN T2.RNO = 1 THEN TO_CHAR(T1.ORD_DT,'YYYYMM')
WHEN T2.RNO = 2 THEN TO_CHAR(T1.ORD_DT,'YYYYMM')
WHEN T2.RNO = 3 THEN 'Total' END
,CASE WHEN T2.RNO = 1 THEN T1.CUS_ID
WHEN T2.RNO = 2 THEN 'Total'
WHEN T2.RNO = 3 THEN 'Total' END;
3. WITH 와 UNION ALL
-- ROLLUP을 WITH 절과 UNION ALL로 대체
WITH T_RES AS(
SELECT TO_CHAR(T1.ORD_DT,'YYYYMM') ORD_YM ,T1.CUS_ID
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
WHERE T1.CUS_ID IN ('CUS_0001','CUS_0002')
AND T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD')
GROUP BY TO_CHAR(T1.ORD_DT,'YYYYMM') ,T1.CUS_ID
)
SELECT T1.ORD_YM ,T1.CUS_ID ,T1.ORD_AMT
FROM T_RES T1
UNION ALL
SELECT T1.ORD_YM ,'Total' ,SUM(T1.ORD_AMT)
FROM T_RES T1
GROUP BY T1.ORD_YM
UNION ALL
SELECT 'Total' ,'Total' ,SUM(T1.ORD_AMT)
FROM T_RES T1;
CUBE
- 조합 가능한 모든 소계를 만들어 낸다.
- 문법:
ROLLUP
과 동일- 예시: GROUP BY CUBE(A, B, C)
- A+B별 소계
- A별 소계
- A+C별 소계
- B+C별 소계
- B별 소계
- C별 소계
- 예시: GROUP BY CUBE(A, B, C)
- 컬럼이 많고 처리할 데이터가 많을수록 성능이 좋지 못하다.
GROUPING SETS
ROLLUP
이나CUBE
처럼 소계를 만든다.- 문법 :
GROUP BY
뒤에ROLLUP
,CUBE
대신에GROUPING SETS
라고 표기한 ㅅ후, 그룹화할 컬럼을 괄호로 묶어 나열 - 소계가 필요한 집합을 일일이 지정할 수 있다.
-- 주문년월, 고객ID별 주문건수와 주문 금액 – GROUPING SETS 활용
SELECT TO_CHAR(T1.ORD_DT,'YYYYMM') ORD_YM
,T1.CUS_ID
,COUNT(*) ORD_CNT
,SUM(T1.ORD_AMT) ORD_AMT
FROM T_ORD T1
WHERE T1.ORD_DT >= TO_DATE('20170301','YYYYMMDD')
AND T1.ORD_DT < TO_DATE('20170501','YYYYMMDD')
AND T1.CUS_ID IN ('CUS_0061','CUS_0062')
GROUP BY GROUPING SETS(
(TO_CHAR(T1.ORD_DT,'YYYYMM'),T1.CUS_ID) --GROUP BY기본 데이터
,(TO_CHAR(T1.ORD_DT,'YYYYMM')) --주문년월별 소계
,(T1.CUS_ID) --고객ID별 소계
,() --전체합계
);
reference
SQL BOOSTER