본문 바로가기
Developer/DB

[Oracle] NTILE, RATIO_TO_REPORT, WIDTH_BUCKET 함수

by 순수한소년 2015. 1. 15.
728x90
반응형

1. NTILE() 함수

: NTILE() 함수는 계산 대상 데이타(ROW) 들을 특정한 기준으로 분할하여 그 결과값을 반환하는 분석함수.


구문형식

 NTILE(EXPR) OVER(PARTITION BY EXPR1 ORDER BY EXPR2 [ASC|DESC])


예1) NTILE(5) OVER(ORDER BY SUM(ORDER_DETAIL) DESC) 라면

RESULTSET 을,또는 RESULTSET 의 갯수를 5등분 하고 SUM(ORDER_DETAIL) DESC 순으로 정렬한다는 것. 5등분할 때 각 레코드들에 1부터 5까지 번호를 순차적으로 붙인다.


예2) 아래 예는 데이타가 없음. 그냥 쿼리만 볼 것.

다음은 99년도 주문에서 상위 20%의 고객이 누구인지 알아보는 쿼리

--전체 RESULTSET 중 20%만 보고 싶다면 전체 RESULTSET 을 5등분 한 후 아래 RANK 알리아스에 1이 들어가 있는 것들만 뽑아내면 된다.


SELECT CUSTOMER_ID,

--PARTITION BY 절이 없으니 아래는 모든 RESULTSET 을 5등분 할 것.

  NTILE(5) OVER(PARTITION BY SUM(ORDER_TOTAL) DESC) RANK,

SUM(ORDER_TOTAL)

FROM ORDERS

WHERE TO_CHAR(ORDER_DATE,'YYYY') = 1999'

GROUP BY CUSTOMER_ID;



예3) SCOTT/TIGER 로 로그인한다.


 

SELECT T.*, 

       NTILE(3) OVER(ORDER BY HIREDATE) RANK 

    FROM EMP T;


위를 실행하면 전체 14개의 RESULTSET 을 3개로 나눠서 RANK 라는 필드에 ORDER BY HIREDATE 으로 정렬된 상태에서 위에서부터 1,2,3 을 붙인다. 아래처럼 1이 5개, 2가 5개, 3이 4개가 나옴.



* NTILE 에서 나머지가 생기는 경우가 있다. 이 때는 번호가 아래와 같이 배분된다.

예를 들어 NTILE(4) 를 사용하면, 각 ROW를 순서대로 25%씩 나누어 1부터 4의 숫자를 리턴해 준다.

그런데 전체 RESULTSET 이 103개인경우라면 어떻게 될까?

정확히 25%로 나누면 나머지가 생기게 되는데, 103/4 로 나누고 나서 남은 나머지는 3이고 , 이건 맨 뒤에 5라는 숫자를 붙이는게 아니라,  최초 값부터 1개씩 추가로 분배하게 된다. 그러니까 전체 RESULTSET 이 103개 인데, NTILE(4) 를 쓰면 상위 26개의 ROW 가 1, 그 다음 26개가 2, 그 다음 26개가 4, 그 나머지 25개의 로우는 4를 반환하게 된다. 



아래를 실행해 보면

 

SELECT T.*, 

       NTILE(4) OVER(ORDER BY HIREDATE) RANK 

    FROM EMP T;

 


NTILE(4) 이므로 일단, 4보다 큰 숫자는 배분될 수 없다. 전체 14개  RESULTSET 을 4로 나누면 2가 남으니 각 하나씩 최상위 1부터 2에 각각 하나씩 배분되고 아래와 같은 모양이 된다.



결국 1이 4개, 2가 4개, 3이 3개, 4가 3개가 된다. 





2. RATIO_TO_REPORT 함수


구문형식

 RATIO_TO_REPORT (EXPR) OVER (PARTITION BY 절)



RATIO_TO_REPORT 함수는 계산 대상 값 전체에 대한 현재 ROW 의 상대적인 비율값을 반환하는 함수임.


RATIO_TO_REPORT 함수는 계산 대상 데이터(ROW들)들을 PARTITION BY 로 구분하여 EXPR 에 명시된값의 전체 합계에 대한 각각의  ROW 의 상대적인 값을 반환한다. 만약 EXPR 값으로 NULL 이 명시되면 반환값 역시 NULL 이다.


간단하게 말하면 A 라는 필드가 있을 때 'RATIO_TO_REPORT(A)' 로 하기만 하면, SUM(A) 한 것을 기준으로  A 가 SUM(A)에서 몇 % 나 차지하는지 A/SUM(A) 로 각각의 ROW 마다 계산하여 리턴해 주는 함수임.


A가 SUM(SUM(필드명)) 이라면 RATIO_TO_REPORT 함수는 

SUM(SUM(필드명))/SUM(SUM(SUM(필드명))) 의 값을 리턴해 주는 것.

A 부분은 얼마든지 더 복잡해 질 수 있음.


* 아래는 RATIO_TO_REPORT 함수 예제.


-- DANIEL 로 로그인 하여 아래 쿼리 날려본다. 5월 한 달간 각 아이템별 판매액이 날짜별로 나온다.

 

SELECT *

    FROM SALE_HIST

    ORDER BY SALE_DATE , SALE_ITEM


결과는 아래와 같다.


 



 

이 테이블에서 각 아이템의 날짜별 판매비율을 구해야 한다고 가정하면.


우선 아래와 같은 쿼리로 각 아이템의 날짜별 판매액 합계를 구한다.

 

SELECT SALE_DATE, SUM(SALE_AMT) , SALE_ITEM

    FROM SALE_HIST

    GROUP BY SALE_DATE , SALE_ITEM

    ORDER BY SALE_DATE , SALE_ITEM  


위 쿼리의 결과는 아래와 같다.




이제 아래 쿼리로 각 아이템의 날짜별 판매비율을 구하면 된다.


 

 

SELECT SALE_DATE, SUM(SALE_AMT) , SALE_ITEM ,

        --아래 분석 함수는 괄호의 위치가 조금 틀리거나 괄호 갯수가 하나만 틀려도

        -- ORA-30483: 윈도우 함수를 여기에 사용할 수 없습니다 라는 오류 메시지 뜬다.

        ROUND(SUM(SALE_AMT)/SUM(SUM(SALE_AMT)) OVER(PARTITION BY SALE_ITEM),2)  RATIO

    FROM SALE_HIST

    GROUP BY SALE_DATE , SALE_ITEM

    ORDER BY SALE_ITEM , SALE_DATE  



** 위 쿼리를 RATIO_TO_REPORT 함수를 써서 고치면 아래와 같이 된다.


 

 

SELECT SALE_DATE, SUM(SALE_AMT) , SALE_ITEM ,

        RATIO_TO_REPORT (SUM(SALE_AMT)) OVER (PARTITION BY SALE_ITEM ) RATIO

    FROM SALE_HIST

    GROUP BY SALE_DATE , SALE_ITEM

    ORDER BY SALE_ITEM , SALE_DATE

 


위 SUM(SALE_AMT)/SUM(SUM(SALE_AMT))  부분이 --> RATIO_TO_REPORT (SUM(SALE_AMT)) 로 변경되었음. 


위의 쿼리를 돌려보면 아래와 같이 나옴. 

비교를 위해 RATIO_TO_REPORT 함수 쓴 쿼리는 ROUND  함수를 쓰지 않았음.





결국, RATIO_TO_REPORT 함수는 PARTITION BY 절에 명시된 그룹의 

SUM(SALE_AMT) 값의 전체 합계(SUM(SUM(SALE_AMT)))에서의 비율을 리턴해 주는 함수임.



* 위와 동일한 테이블에서 다른 예제.

- 특정 월(01년 05월)에 어떤 판매처(SALE_SITE 필드)에서 어떤 아이템(SALE_ITEM) 이 어느정도 팔렸는지 알고 싶다면?


먼저, 판매처별 , 아이템별 판매 현황을 보기위해 아래 쿼리를 실행시킨다.


 

SELECT *

    FROM SALE_HIST

    WHERE SUBSTR(TO_CHAR(SALE_DATE,'YYYYMMDD'),1,6) = '200105' 

    ORDER BY SALE_SITE , SALE_ITEM    


결과는 아래와 같다.




 

--지우개가 어느 판매점에서 몇 % 팔렸는지등을 알아내는게 목표니까 

-- 분석함수 쓰기 전에 먼저 아이템과 판매점으로 group by 해서

-- 아이템 별로 어떤 판매점에서 총 얼마나 판매 되었는지 확인한다.



SELECT  SALE_ITEM ,SALE_SITE, SUM(SALE_AMT) 

    FROM SALE_HIST  

    WHERE SUBSTR(TO_CHAR(SALE_DATE,'YYYYMMDD'),1,6) = '200105' 

    GROUP BY SALE_ITEM, SALE_SITE

    ORDER BY SALE_ITEM, SALE_SITE


위 쿼리의 RESULTSET 은 아래와 같다.



 


이제 마지막으로 분석함수를 집어 넣는다. 

 

RATIO_TO_REPORT(A) 의 형식에 집어 넣으면 됨. A 는 위에서 SUM(SALE_AMT) 임.

아래처럼 쿼리를 만들면 됨.


 

--각 판매점에서 각 아이템별로 어느 정도 많이  판매 되었는지를 

-- 확인해야 하므로 아이템별로 그룹핑 시켜야 한다 PARTITION BY 는 아이템으로 해야 한다는 말.

-- 그리고 각 ROW 를  돌면서 해당 ROW 의 SUM(SALE_AMT) 가 그 아이템의 전체 판매량 에서

-- 어느 정도의 비중을 차지하는지 RATIO_TO_REPORT 에게 구하라고 한다. 

-- 아래 쿼리와 같이 구한다.


SELECT  SALE_ITEM ,SALE_SITE, SUM(SALE_AMT) ,

        ROUND(RATIO_TO_REPORT(SUM(SALE_AMT)) OVER(PARTITION BY SALE_ITEM),2) RATIO

    FROM SALE_HIST  

    WHERE SUBSTR(TO_CHAR(SALE_DATE,'YYYYMMDD'),1,6) = '200105' 

    GROUP BY SALE_ITEM, SALE_SITE

    ORDER BY SALE_ITEM, SALE_SITE


위 쿼리를 실행하면 다음과 같다.



지우개의 경우 1번 판매점에서 5월 판매량의 35% 판매했다는 얘기.......등등.

 



3. WIDTH_BUCKET 함수

: WIDTH_BUCKET 함수 역시 특정 기준에 의해 RESULTSET 에 번호를 부여하는 함수다. NTILE 과 분할하여 번호 붙이는 기준만 다를 뿐임.


구문형식

 WIDTH_BUCKET (EXPR, MIN_VALUE, MAX_VALUE, NUM_BUCKETS)


WIDTH_BUCKET 함수는 EXPR 에 명시된 값들을 대상으로,

MIN_VALUE 와 MAX_VALUE 사이를 NUM_BUCKETS 만큼 분할하여 각각의 ROW 에 1번부터 번호를 붙인다. 




테스트를 위해 아래 스크립트로 테이블을 만들고 데이타를 넣는다.

 

CREATE TABLE STAT_TEST (

         T_VALUE NUMBER );



INSERT INTO STAT_TEST VALUES(10);

INSERT INTO STAT_TEST VALUES(35);

INSERT INTO STAT_TEST VALUES(26);

INSERT INTO STAT_TEST VALUES(55);

INSERT INTO STAT_TEST VALUES(49);

INSERT INTO STAT_TEST VALUES(75);

INSERT INTO STAT_TEST VALUES(9);

INSERT INTO STAT_TEST VALUES(29);

INSERT INTO STAT_TEST VALUES(44);

INSERT INTO STAT_TEST VALUES(99);



 

예를 들어보면,

 

WIDTH_BUCKET(T_VALUE, 1 ,100 ,4 )  는 T_VALUE 들의 ROW 들에 대해서 각각 번호를 부여하되,

어떤 번호를 어디에 부여하냐면, 1 부터 100 까지 4등분을 한 후 T_VALUE 에 있는 번호를 보고 적당한 번호를 붙여라..라는 얘기.

그러니까 1부터 100을 4등분하면 1~25, 26~50, 51~75, 76~100 이니까

T_VALUE 가  1~25 면 1을 부여하고,

T_VALUE 가  26~50  면 2를 부여하고,

T_VALUE 가  51~75  면 3을 부여하고,

T_VALUE 가  76~100 면 4를 부여하라는 것.

 


테스트로 아래처럼 쿼리를 날려본다.


 

SELECT T_VALUE,

       WIDTH_BUCKET(T_VALUE, 1 ,100 ,4 ) WIDTH

       FROM STAT_TEST

       ORDER BY WIDTH


결과는 아래와 같다.



위에 나오듯이, 9는 1~25 사이니까 1을 부여, 10도 1~25사이니까 1을 부여, ..... 75 는 51~75 사이니까 3을 부여.... 와 같은 식으로 되는 것임.


 

만약 WIDTH_BUCKET(T_VALUE, 1 ,100 ,4 )  와 같은 식에서 T_VALUE 의 값이 최소값인 1보다 작으면 무조건 0  을 리턴하고 최대값인 100보다 크면 맨 마지막 매변의 숫자 + 1 한 값을 리턴한다.

WIDTH_BUCKET(T_VALUE, 1 ,100 ,4 )  의 경우 T_VALUE 값이 130 이라면 마지막 매변인 4+1 을 해서 5를 리턴하게 되는 것.


* 아래 쿼리로 NTILE 과 WIDTH_BUCKET 함수를 비교해 볼 것.


 

SELECT T_VALUE,

       WIDTH_BUCKET(T_VALUE,10, 90 ,4) WIDTH,

       NTILE(4) OVER(ORDER BY T_VALUE) TILE

       FROM STAT_TEST

       ORDER BY T_VALUE


결과는 아래와 같다. 


728x90
반응형