본문 바로가기
dev/DB

[DB/ORACLE] 분석함수

by dev_Step 2022. 11. 25.
-- 분석함수 
-- 집계 함수는 행 그룹으로 값을 집계하고 // 행 그룹별로 단일 행을 반환 하기 때문에 데이터 집합이 변경됨
-- 분석 함수는 파티션과 윈도우로 값을 집계한다. //  데이터 집합을 변경하지 않고 값을 집계하므로, 원본값과 집계 값을 함께 분석할수 있다.

SELECT A.EMPNO, A.SAL, B.SAL AS C1, A.SAL / B.SAL AS C2
FROM 
    EMP A
    , (SELECT DEPTNO, SUM(SAL) AS SAL FROM EMP GROUP BY DEPTNO ) B
WHERE
    A.DEPTNO = 10
    AND B.DEPTNO = A.DEPTNO
ORDER BY 
    1;
    
-- 분석 함수를 사용하면 EMP 테이블을 1번만 조회하면 된다.
-- SUM(), RATIO_TO_REPORT() 함수이다.
-- RATIO_TO_REPORT() -> 전체 합계 대비 비율 또는 백분율 구하는 함수
SELECT 
    EMPNO
    , SAL
    , SUM(SAL) OVER() AS C1
    , RATIO_TO_REPORT(SAL) OVER() AS C2
FROM 
    EMP
WHERE
    DEPTNO = 10
ORDER BY 
    1;
    
-- 분석 함수는 OVER 키워드를 사용한다.
-- 집계 함수에 OVER 키워드를 기술하면 분석함수로 동작한다. 
-- OVER 키워드에  [QUERY PARTITION 절] [ORDER BY 절 [ WINDOWING 절 ] ] 로 구성된다.

-- QUERY PARTITION 절은 GROUP BY 와 유사하게 동작한다. 파티션은 행 그룹과 유사하다.
-- 분석을 위한 정적 그룹으로 생각 할 수 있다. qUERY PARTITION절을 생략하면 전체 행이 하나의 파티션으로 동작한다.
SELECT 
    EMPNO
    , JOB
    , SAL
    , SUM(SAL) OVER (PARTITION BY JOB) AS C1
    , SUM(SAL) OVER() AS C2
FROM 
    EMP
WHERE 
    DEPTNO = 30
ORDER BY 
    2,1;
    
-- ORDER BY 절로 파티션내 정렬 순서를 지정할 수 있다, C1열은 SAL의 오름차순 정렬에 대한 누적 합계 값을 반환한다.
SELECT 
    EMPNO
    , SAL
    , SUM(SAL) OVER (ORDER BY SAL, EMPNO) AS C1
FROM
    EMP
WHERE
    DEPTNO = 30
ORDER BY 
    2,1;
    
    
    

drop table t1 purge;
create table t1(c1 number, c2 number);

insert into t1 values(1,1);
insert into t1 values(2,1);
insert into t1 values(3,2);
insert into t1 values(4,3);
insert into t1 values(5,3);
insert into t1 values(6,4);
insert into t1 values(7,5);
insert into t1 values(8,5);
insert into t1 values(9,6);

commit;

select * from t1;

--- ORDER BY 절 ROW 기준 ( ORDER BY 의 기준이 되는 C2가 동일 값일경우 다르게 처리된다. 즉 물리적 행으로 처리를 하기 때문에)
-- -- R01 에서  C1이 5일때  행의 절대적인 위치로 2개 앞 ~ 맨앞까지의 갯수이므로 3을 반환하게 된다.
select 
    c1
    , c2
    , count(*) over ( order by c2 ROWS BETWEEN UNBOUNDED PRECEDING AND 2 PRECEDING) AS R01
    , count(*) over ( order by c2 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS R02
    , count(*) over ( order by c2 ROWS BETWEEN UNBOUNDED PRECEDING AND 2 FOLLOWING) AS R03
    , count(*) over ( order by c2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS R04
    , count(*) over ( order by c2 ROWS BETWEEN  2 PRECEDING AND 1 PRECEDING) AS R05
    , count(*) over ( order by c2 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS R06
    , count(*) over ( order by c2 ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS R07
    , count(*) over ( order by c2 ROWS BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING) AS R08
    , count(*) over ( order by c2 ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS R09
    , count(*) over ( order by c2 ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS R10
    , count(*) over ( order by c2 ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) AS R11
    , count(*) over ( order by c2 ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) AS R12
    , count(*) over ( order by c2 ROWS UNBOUNDED PRECEDING) AS R13
    , count(*) over ( order by c2 ROWS 2 PRECEDING) AS R14
    , count(*) over ( order by c2 ROWS CURRENT ROW ) AS R15
FROM
    T1
ORDER BY 1;

--- ORDER BY 절 RANGE 기준( ORDER BY 의 기준이 되는 C2가 동일 값일 경우 묶음으로 처리가 되어 같은 값을 반환하게 된다.)
-- R01 에서  C1이 4일때  C2가 3인데 2 PRECEDING 이므로 3-2 을 하게되면 = 1 인데 C2가  1인경우는  2개이므로 2를 반환하게 된다.
-- RANGE 기준에서 오름차순 기준 PRECEDING  (-) , FOLLOWING (+) , 내림차순의 경우는 반대라고 생각하면된다.
select 
    c1
    , c2
    , count(*) over ( order by c2 DESC RANGE BETWEEN UNBOUNDED PRECEDING AND 2 PRECEDING) AS R01
     
    , count(*) over ( order by c2 DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS R02
    , count(*) over ( order by c2 DESC RANGE BETWEEN UNBOUNDED PRECEDING AND 2 FOLLOWING) AS R03
    , count(*) over ( order by c2 DESC RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS R04
    , count(*) over ( order by c2 DESC RANGE BETWEEN  2 PRECEDING AND 1 PRECEDING) AS R05
    , count(*) over ( order by c2 DESC RANGE BETWEEN 2 PRECEDING AND CURRENT ROW) AS R06
    , count(*) over ( order by c2 DESC RANGE BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS R07
    , count(*) over ( order by c2 DESC RANGE BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING) AS R08
    , count(*) over ( order by c2 DESC RANGE BETWEEN CURRENT ROW AND 2 FOLLOWING) AS R09
    , count(*) over ( order by c2 DESC RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS R10
    , count(*) over ( order by c2 DESC RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING) AS R11
    , count(*) over ( order by c2 DESC RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) AS R12
    , count(*) over ( order by c2 DESC RANGE UNBOUNDED PRECEDING) AS R13
    , count(*) over ( order by c2 DESC RANGE 2 PRECEDING) AS R14
    , count(*) over ( order by c2 DESC RANGE CURRENT ROW ) AS R15
FROM
    T1
ORDER BY 
    1 DESC;


SELECT 
    EMPNO
    , ENAME
    , SAL 
    , SUM(SAL) OVER (ORDER BY SAL ROWS UNBOUNDED PRECEDING) AS C1
    , SUM(SAL) OVER (ORDER BY SAL RANGE UNBOUNDED PRECEDING) AS C2 -- 1250 이 2명인 사람의 SAL을 한번에 더해서 나타내줌
    , SUM(SAL) OVER (ORDER BY SAL, EMPNO ROWS UNBOUNDED PRECEDING) AS C3 -- 정렬기준이 고유하다(중복되는 정렬 값이 없다) 따라서 ROWS/RANGE와 동일한 결과가 나옴
    , SUM(SAL) OVER (ORDER BY SAL, EMPNO RANGE UNBOUNDED PRECEDING) AS C4 -- 정렬기준이 고유하다(중복되는 정렬 값이 없다) 따라서 ROWS/RANGE와 동일한 결과가 나옴
FROM EMP
WHERE DEPTNO = 30
ORDER BY 3,1;

-- C1 = 정적그룹인 JOB 내에서 동적 그룹인 윈도우로 값이 집계된다.
SELECT
    JOB
    , HIREDATE
    , SAL
    , SUM(SAL) OVER( PARTITION BY JOB ORDER BY HIREDATE) AS C1
FROM 
    EMP
WHERE DEPTNO = 30
ORDER BY 1,2;

-- KEEP 키워드
-- KEEP 키워드를 사용하면 OVER절에 QUERY PARTITION 을 넣을수 있다.

-- JOB 파티션 별 SAL가 최소인 행을 대상으로 COMM의 최고값을 갖는다.
SELECT 
    ENAME
    , JOB
    , SAL
    , COMM
    , MAX(COMM) KEEP(DENSE_RANK FIRST ORDER BY SAL) OVER(PARTITION BY JOB) AS C1
FROM
    EMP
WHERE DEPTNO = 30
ORDER BY 2,3,4;

--주의사항
-- RANGE 방식에 VALUE_EXPR 를 지정할시 ORDER BY 절에 숫자 값이나 날짜 값을 사용해야 한다. 
    -- SELECT SUM(SAL) OVER( ORDER BY JOB RANGE 1 PRECEDING) AS C1 --> ORDER BY 기준이 DATE나 NUMBER가 되어야 한다.   ORA-00902
-- 또한 정렬 표현식을 1개만 사용할 수 있다.
    -- SELECT SUM(SAL) OVER (ORDER BY SAL, COMM RANGE 1 PRECEDING) AS C1 --> ORDER BY 기준이 1개가 되어야 한다. ORA-30486 
-- 분석함수는 SELECT 절과 ORDER BY 절에 사용할 수 있다.    
    
-- 오라클의 날짜 DATE 컬럼에서 년도를 더하고 뺄때 ADD_MONTH() 내장 함수를 사용하기도 한다.
-- 하지만 INTERVAL 을 이용하는게 직관적이고 쉽게 컨트롤 할 수 있다.
-- SYSDATE + ( INTERVAL '1' DAY[YEAR,MONTH,DAY,HOUR 등...] 
-- SYSDATE + ( INTERVAL '03:30' HOUR TO MINUTE/ MINUTE TO SECOND / 1-1 YEAR TO MONTH 등으로 사용된다.
-- 시간은 : 으로 구분 연도와 개월의 경우 - 으로 구분한다.
select
    HIREDATE
    , SAL
    , SUM(SAL) OVER( ORDER BY HIREDATE RANGE 90 PRECEDING) AS C1
    , SUM(SAL) OVER ( ORDER BY HIREDATE RANGE INTERVAL '3' MONTH PRECEDING) AS C2
FROM EMP
WHERE DEPTNO = 30
ORDER BY 1;



-- FROM 절의 인라인뷰를 사용하면 WHERE절에서 분석함수의 결과값을 사용할 수 있다.
SELECT 
    DEPTNO
    ,ENAME
    ,SAL
    ,C1
FROM
    (SELECT A.*, SUM(A.SAL) OVER (PARTITION BY A.DEPTNO) AS C1 FROM EMP A)
WHERE 
    C1 >= 10000
ORDER BY 1,2;

-- C2 ,C3열에서 사용한 분석 함수는 그룹핑이 완료된 후 수행된다.
SELECT
    DEPTNO
    , SUM(SAL) AS C1
    , SUM(SUM(SAL)) OVER() AS C2
    , COUNT(*) OVER() AS C3
FROM EMP
GROUP BY DEPTNO
ORDER BY 1;

--아래와 같이 인라인 뷰를 통해 집계 쿼리와 분석함수를 분리하는 편이 가독성측면에서 좋다
SELECT DEPTNO, C1, SUM(C1) OVER() AS C2, COUNT(*) OVER() AS C3
FROM (SELECT DEPTNO, SUM(SAL) AS C1 FROM EMP GROUP BY DEPTNO)
ORDER BY 1;   

-- DETPNO별 SAL의 합계 값을 집계한다.
SELECT DISTINCT
    DEPTNO
   , SUM(SAL) OVER(PARTITION BY DEPTNO) AS C1
FROM EMP;

-- 행 그룹으로 집계할 경우에는 분석함수가 아닌 GROUP BY 절을 사용해야 한다.
SELECT
    DEPTNO
    , SUM(SAL) 
FROM 
    EMP
GROUP BY 
    DEPTNO;
    
-- 분석함수의 대부분은 집계 함수에 over 키워드를 기술하는 방식으로 사용한다.
-- 분석함수는 집곟삼수와 유사하게 구분할 수 있다.
-- 기본함수 = count, min, max, sum, avg
-- 통계함수 = STDDEV, VARIANCE
-- 순위함수 = RANK, DENSE_RANK, ROW_NUMBER, NTILE, CUME_DIST, PERCENT_RANL, RATIO_TO_REPORT
-- 분포함수 = PERCENTILE_CONT, PERCENTILE_DISC, MEDIAN
-- 순차함수 = FIRST_VALUE, LAST_VALUE, NTH_VALUE
-- 기타함수 = LAG, LEAD, LISTAGG

-- COUNT함수 : 행의 갯수나 EXPR의 갯수를 반환한다.
SELECT JOB, COUNT(*) OVER (PARTITION BY JOB) AS C1
FROM EMP
WHERE DEPTNO = 30
ORDER BY 1;
-- MIN 함수 : EXPR의 최저 값을 반환한다.
SELECT
    EMPNO
    ,SAL
    ,COMM
    ,MIN(COMM) OVER ( ORDER BY SAL, EMPNO ROWS UNBOUNDED PRECEDING) AS C1
FROM EMP
WHERE DEPTNO = 30
ORDER BY 2,1;
--MAX 함수 : EXPR의 최고값을 반환한다
SELECT
    EMPNO
    , SAL
    , COMM
    , MAX(COMM) OVER (ORDER BY SAL) AS C1
FROM
    EMP
WHERE DEPTNO = 30
ORDER BY 2,1;
--AVG 함수 : EXPR 의 평균 값을 반환한다.
    -- ROWS 기준
SELECT
    EMPNO
    , ENAME
    , SAL
    , AVG(SAL) OVER (ORDER BY SAL, EMPNO ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS C1
    , COUNT(*) OVER  ( ORDER BY SAL, EMPNO ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS C2
FROM 
    EMP
WHERE DEPTNO = 30
ORDER BY 3,1;
    -- RANGE 기준
SELECT
    EMPNO
    , ENAME
    , SAL
    , AVG(SAL) OVER (ORDER BY SAL RANGE BETWEEN 300 PRECEDING AND 300 FOLLOWING) AS C1
    , COUNT(*) OVER (ORDER BY SAL RANGE BETWEEN 300 PRECEDING AND 300 FOLLOWING) AS C2
FROM EMP
WHERE DEPTNO = 30
ORDER BY 3,1;


DROP TABLE T1 PURGE;

CREATE TABLE T1 AS
SELECT DT, ROWNUM * 100 AS VL
FROM (SELECT DATE '2050-01-01' + ROWNUM-1 AS DT FROM XMLTABLE('1 to 31'))
WHERE TO_CHAR(DT,'D') NOT IN ('1','7');

select * from t1;

SELECT 
    DT,
    VL
    , AVG(VL) OVER (ORDER BY DT ROWS 9 PRECEDING) AS C1
    , AVG(VL) OVER (ORDER BY DT RANGE 9 PRECEDING) AS C2
FROM T1
ORDER BY 1;
    
    
-- STDDEV함수 EXPR의 표준 편차를 반환한다
SELECT 
    JOB
    , SAL
    ,STDDEV(SAL) OVER (PARTITION BY JOB) AS C1
FROM EMP
WHERE DEPTNO = 30
ORDER BY 2,1;

-- VARIANCE  함수 : EXPR의 분산을 반환한다.
SELECT
    JOB
    , SAL
    , VARIANCE(SAL) OVER (PARTITION BY  JOB) AS C1
FROM EMP
WHERE DEPTNO = 30
ORDER BY 2,1;

-- 순위 함수
-- 순위 함수는 정렬 조건에 따른 순위를 반환한다. 순위 집계 함수는 가상의 행을 생성하지만 순위 분석 함수는 실제의 행으로 값을 계산한다.

--RANK : EPXR 이 동일하면 동순위를 부여하고 , 다음 순위는 동순위의 개수만큼 건너 뛴다.
SELECT
    EMPNO
    ,ENAME
    ,SAL
    ,RANK() OVER (ORDER BY SAL) AS C1
FROM EMP
WHERE DEPTNO = 30
ORDER BY 3,1;
--DENSE_RANK 동순위가 존재하더라도 다음 순위를 이어서 부여한다
SELECT 
    EMPNO
    ,ENAME
    , SAL
    , DENSE_RANK() OVER (ORDER BY SAL) AS C1
FROM EMP
WHERE DEPTNO = 30
ORDER BY 3,1;

--ROW_NUMBER함수
SELECT 
    EMPNO
    , ENAME
    , SAL
    , ROW_NUMBER() OVER (ORDER BY SAL) AS C1
    , ROW_NUMBER() OVER (ORDER BY SAL, EMPNO) AS C2
FROM EMP
WHERE DEPTNO = 30
ORDER BY 3,1;


--부서별로 가장 최근에 입사한 사원을 반환한다
SELECT * FROM (SELECT A.*, ROW_NUMBER() OVER( PARTITION BY DEPTNO ORDER BY HIREDATE DESC, EMPNO DESC) AS RN FROM EMP A) WHERE RN=1;


SELECT  * FROM (SELECT A.*, ROW_NUMBER() OVER( PARTITION BY DEPTNO ORDER BY HIREDATE DESC, EMPNO DESC) AS RN FROM EMP A) WHERE RN = 1;

SELECT 
    EMPNO
    , ENAME
    , SAL
    , ROW_NUMBER() OVER (ORDER BY SAL) AS C1
    , RANK() OVER(ORDER BY  SAL) AS C2
    , DENSE_RANKS() OVER (ORDER BY SAL) AS C3
FROM EMP
WHERE DEPTNO = 30
ORDER BY 2,1;

-- NTILE 함수 : EXPR 의 갯수만큼 버킷을 생성한 후 행에 해당하는 버킷 번호를 할당한다 (100 -> (3)) 0-34 35-68 69-100 이렇게 나뉘고 해당 범위에 맞는 버킷 번호를 부여

SELECT C1, COUNT(*) AS C2, SUM(SAL) AS C3 FROM ( SELECT SAL, NTILE(4) OVER (ORDER BY SAL) AS C1 FROM EMP WHERE DEPTNO = 30 ) GROUP BY C1 ORDER BY C1;

'dev > DB' 카테고리의 다른 글

[DB/ORACLE] TOP-N QUERY  (0) 2022.11.29
[DB/ORACLE] 분석함수 및 활용(선형 보간, 선분이력)  (0) 2022.11.28
[DB/Mysql] HAVING, GROUP BY  (0) 2022.07.05
[DB/MySQL] 기타함수(Group_concat)  (0) 2022.07.05
[DB/MySQL] 집계함수  (0) 2022.07.05