📝

9. Window Function

9.1. 윈도우 함수

9.1.1. 윈도우 함수 개요

윈도우 함수는 테이블 내의 특정 범위인 윈도우가 포함한 행들을 대상으로 연산을 수행합니다. 윈도우가 포함하고 있는 테이블의 행에 대한 집계, 순위, 비율 등의 연산을 수행할 수 있습니다. 윈도우 함수는 GROUP BY와 유사한 방식으로 데이터를 그룹화하고 집계합니다. GROUP BY는 데이터를 그룹별로 하나의 행을 반환합니다.
하지만, 윈도우함수는 결과를 단일 값으로 축소하지 않고 윈도우 범위에 따라 각 행에 대한 새로운 값을 반환하여 원본 데이터의 행 수를 유지한다는 점에서 GROUP BY와 차이가 있습니다.
 

9.1.2. 윈도우 함수 특징

아래 4가지의 특징인 ‘계산의 다양성’, ‘성능 향상’, ‘코드의 간결성’, ‘분석의 편의성’을 위해 윈도우 함수를 사용합니다. 데이터 분석 및 집계 작업을 더 유연하게 수행할 수 있으며, 복잡한 데이터 분석 작업을 단순화할 수 있습니다.
  1. 계산의 다양성 : 평균(AVG), 합계(SUM), 카운트(COUNT) 등 계산을 각 행에 수행할 수 있습니다.
  1. 쿼리 성능 향상 : 윈도우 함수는 중간 결과를 캐싱하므로 여러 번의 계산이 필요 없습니다.
  1. 코드의 간결성 : 복잡한 쿼리를 더 간결하게 작성할 수 있어 코드의 유지보수가 용이합니다.
  1. 분석의 편의성 : 특정 범위의 데이터 분석이 용이하여 시계열, 순위/비율 계산 등을 수행합니다.
 
SQL 데이터베이스 시스템(PostgreSQL, MySQL, SQL Server, Oracle)에서 지원되며, 데이터베이스 시스템에 따라 문법과 지원하는 윈도우 함수가 다를 수 있습니다.
💡
윈도우 함수는 결과 집합의 특정 윈도우에 걸쳐 계산을 수행하는 데 설계되었습니다. 이러한 연산의 특성상 다른 윈도우 함수 내에서 중첩하여 윈도우 함수를 호출하는 것은 SQL 표준에서 허용되지 않습니다.
 

9.1.3. 윈도우 함수 구성요소

윈도우 함수는 특정 범위의 윈도우에 연산을 적용하게 해주는 기능입니다. 윈도우 함수는 특정 행의 구문 내에서 연산을 수행하면서 그룹화 없이 각 행을 개별적으로 반환합니다.
SELECT WINDOW_FUNCTION(ARGUMENTS) OVER(PARTITION BY column [1|2|,,] ORDER BY [1|2|,,] WINDOW_frame) FROM Table1;
  • WINDOW_FUNCTION
WINDOW_FUNCTION은 결과 집합의 행에 대한 연산을 수행하며, 행 범위를 지정하여 어떤 행들에 대한 계산을 할지 정의합니다.
대표적으로 순위함수(RANK, DENSE_RANK, ROW_NUMBER), 집계함수(SUM, AVG, MIN, MAX), 순서함수(FIRST_VALUE, LAST_VALUE, LAG, LEAD), 비율함수(PERCENT_RANK, RATIO_TO_REPORT,
CUME_DIST, NTILE)가 있습니다.
 
순위함수는 기준에 따라 오름차순 또는 내림차순을 한 결과 집합의 행에 대해 순위를 매깁니다.
집계함수는 결과 집합 내에서 각 행별로 윈도우에 대한 계산을 수행합니다.
순서함수는 데이터 집합 내에서 특정 항목의 위치나 순서를 반환합니다.
비율함수는 결과 집합의 특정 항목이 전체 항목에 대해 차지하는 비율을 계산합니다.
 
9.3.절에서 순위함수, 집계함수, 순서함수, 비율함수에 대해 예시와 함께 자세하게 다루도록 하겠습니다.
💡
SQL Server는 DBMS의 설계 및 구현 결정, 최적화 전략, 호환성 유지 또는 기능 우선순위 특징을 가지고 있으므로 아래 두가지 기능을 지원하지 않습니다. 1. OVER 절 내 ORDER BY 절 행 순서 함수(RANK(), DENSE_RANK(), ROW_NUMBER()) 미지원 2. NTILE을 제외한 비율 함수 미지원
 
  • OVER
OVER절은 윈도우 함수에서 필수 구성 요소로 함수가 작동할 윈도우를 정의하고 결과 집합의 각 행에 대한 계산 범위나 순서를 정의하는 데 사용됩니다. 함수가 적용될 특정 범위인 윈도우를 지정하여 결과 집합 전체나 지정된 파티션 내의 행들에 대한 연산을 수행합니다.
OVER절 내에서 PARTITION BYORDER BY절을 사용하여 데이터의 분할과 정렬을 지정합니다.
 
  • PARTITION BY
PARTITION BY절은 OVER절의 구성요소로 데이터셋을 파티션으로 나누고, 윈도우 함수는 각 파티션에 적용됩니다. PARTITION BY절을 사용하여 결과 집합을 여러 그룹으로 나누어 각 그룹에 윈도우 함수를 별도로 적용할 수 있습니다. 각 그룹은 PARTITION BY 절에 지정된 열의 값을 기준으로 구분됩니다. 전체 집합을 기준에 의해 소그룹으로 나눕니다.
 
  • ORDER BY
ORDER BY절은 OVER절 내에서 사용되며, 지정된 파티션 내의 데이터를 순서대로 정렬하는 역할을 합니다. ORDER BY절은 선택적으로 사용될 수 있지만, 행의 순서나 순위를 결정하는 윈도우 함수에서는 필수적입니다. 대표적으로 순위함수인 ROW_NUMBER(), RANK(), DENSE_RANK() 순서함수인 FIRST_VALUE(), LAST_VALUE(), LAG(), LEAD(), 비율함수 중 NTILE(n)이 있습니다.
 
  • WINDOW_frame
WINDOW_frame 절은 OVER절 내에 있는 ORDER BY절의 구성요소로 파티션 내의 일련의 행에서 계산을 수행하기 위한 부분집합입니다. 필요에 따라 선택적으로 사용할 수 있으며 현재 행 위치를 기준으로 상대적인 윈도우 범위를 정하여 이동 평균, 누적 합계, 백분율 등을 계산 등을 계산하기 위해 사용됩니다.
WINDOW_frame절 기본구문은 ‘ROWS BETWEEN A AND B’입니다. A와 B사이 현재의 행을 의미하는 CURRENT ROW, n 행 앞 n PRECEDING, n 행 뒤 n FOLLOWING, 이전 행 전체 범위 UNBOUNDED PRECEDING, 이후 행 전체 범위 UNBOUNDED FOLLOWING 키워드를 지정하여 범위를 정의할 수 있습니다. ROWS/RANGE를 사용하여 행 기준의 범위를 이전 행, 현재 행, 다음 행으로 정할 수 있습니다.
notion imagenotion image
출처 : Weniv
 

9.2. 구성요소

9.2.1. 윈도우 함수

윈도우 함수는 여러 행을 그룹으로 묶어 특정 행마다 결과를 계산하여 반환합니다. OVER 절을 통해 반환할 결과에 대한 행의 범위를 정의합니다. 윈도우 함수는 평균, 순위, 누적 합 계산 등 분석 작업을 수행할 수 있어 ‘분석 함수’라고도 불립니다.
WINDOW_FUNCTION() OVER( [PARTITION BY 절] [ORDER BY 절] [WINDOW_frame 절] )
형식
  • WINDOW_FUNCTION() : 연산을 수행하는 함수로 OVER절 앞에 선언합니다. 예를 들어, 여기서 RANK()라는 순위 함수를 사용할 수 있습니다.
  • OVER : 반환할 결과에 대한 행의 속성을 지정합니다.
  • PARTITION BY: 함수 적용 범위를 지정합니다.
  • ORDER BY: 함수의 데이터 정렬을 지정합니다. DISTINCT가 있는 경우에는 허용되지 않습니다.
  • WINDOW_frame : 함수 결과의 행(윈도우 프레임)을 지정합니다. DISTINCT가 있는 경우에는 허용되지 않습니다.
 
예시 1
SUM() 함수를 “salary” 컬럼에 적용하여 전체 직원의 급여 합을 출력한 윈도우 함수 예시입니다.
SELECT employee_id , salary , SUM(salary) OVER() AS total_salary FROM `HR.employees`;
notion imagenotion image
 
예시 2
부서별로 데이터를 분할하여 각 부서 내에서의 평균 급여를 “department_id” 기준 내림차순으로 정렬하여 계산하는 쿼리입니다.
SELECT employee_id , salary , department_id , ROUND(AVG(salary) OVER(PARTITION BY department_id ORDER BY department_id DESC)) AS avg_salary_per_department FROM `HR.employees` ORDER BY salary DESC;
notion imagenotion image
 
  • 행 분할 : PARTITION BY
    • PARTITION BY는 데이터를 어떻게 그룹화할지 정의하는데 사용됩니다. PARTITION BY를 통해 데이터를 여러 그룹으로 나누어 각 그룹에 독립적으로 윈도우 함수를 계산할 수 있습니다. 예를 들어, PARTITION BY를 사용하여 고객별로 판매량을 계산하거나 날짜별로 주문을 분석할 수 있습니다.
       
      예시
      PARTITION BY를 사용하여 부서로 평균 급여를 계산합니다.
      SELECT DISTINCT department_id , ROUND(AVG(salary) OVER(PARTITION BY department_id)) AS avg_salary_by_depart FROM `HR.employees` ORDER BY department_id;
      notion imagenotion image
💡
GROUP BYPARTITION BY의 차이점은 무엇인가요? GROUP BY 절에서 지정한 컬럼을 기준으로 집계 함수를 이용하여 집약시키기 때문에 행의 수가 줄어듭니다. 반면, 윈도우 함수는 PARTITION BY에 지정한 컬럼을 기준으로 파티션을 나누어 각 행에 대한 개별적 결과를 반환하므로 원본 데이터의 행의 수가 변경되지 않습니다.
 
  • 행 정렬 : ORDER BY
    • ORDER BY는 윈도우 함수가 데이터를 어떤 순서로 처리할지 지정하는 역할을 합니다. ORDER BY를 사용하여 데이터를 정렬하고 원하는 순서대로 윈도우 함수가 작동하게 할 수 있습니다.
       
      예시
      각 부서 별 급여를 내림차순으로 정렬하고 RANK()함수를 이용해 순위를 매겨 출력합니다.
      SELECT department_id , employee_id , salary , RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS rank_by_salary_in_department FROM `HR.employees` ORDER BY department_id;
      notion imagenotion image
 
  • 행 지정 : ROWS, RANGE (frame절)
    • ROWS 절은 윈도우 함수에서 행의 수를 기준으로 윈도우의 범위를 정의하는 데 사용됩니다. 이 범위는 윈도우 함수가 현재 행 주변의 행을 어떻게 포함할지를 지정하여 윈도우 프레임의 범위가 결정됩니다.
      RANGE는 논리적인 값에 의한 범위로 논리적 주소에 의해 행 집합을 지정합니다.
      💡
      ROW대신 RANGE도 사용될 수 있지만, 동작 방식에 차이가 있습니다. ROW는 특정 행 수를 기준으로 하며, RANGE는 값의 범위를 기준으로 합니다.
       
      예시
      부서별로 최대 급여를 반환하고, RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 구문을 사용하여 프레임을 전체 파티션에 대해 설정합니다. 결과는 각 부서별 최대 급여입니다.
      SELECT DISTINCT department_id , MAX(salary) OVER(PARTITION BY department_id RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS max_salary FROM `HR.employees` ORDER BY department_id;
      notion imagenotion image
 

9.2.2. WINDOW_frame

WINDOW_frame 절은 윈도우 frame 내에서 현재 행을 중심으로 함수가 작동하는 행의 범위를 정의하는 데 사용합니다. 범위 지정 기준에 따라 ROWS 또는 RANGE중 하나를 선택하여 사용합니다.
{ [ROWS| RANGE] BETWEEN frame_start AND frame_end }
형식
  • ROWS : 행(row) 수준에서 범위를 지정합니다. 물리적인 행의 순서를 기준으로 프레임 범위를 정의합니다.
  • RANGE : 값(value)의 범위를 지정합니다. 값의 순서에 따라 범위를 정의해 동일한 값이 여러 행에 걸쳐 있을 때 동일한 값을 가진 모든 행이 같은 범위에 속합니다.
  • BETWEEN : 윈도우 프레임의 시작과 끝을 지정하는 데 사용됩니다. 윈도우 프레임의 시작과 끝을 연결해 주는 연산자인 AND와 함께 사용됩니다.
frame_start, frame_end
n preceding
current_row
n following
unbounded_following
unbounded_preceding, n preceding
O
O
O
O
current_row
O
O
O
n following
O
O
  • frame_start : 윈도우 함수가 적용되는 시작 범위를 정의합니다.
    • unbounded_preceding : 윈도우함수 적용 범위가 가장 앞 행 부터 시작합니다.
    • n preceding : 현재 행 기준 윈도우의 시작 위치를 n만큼 이전 행으로 설정합니다.
    • current_row : 윈도우의 시작을 현재 위치하고 있는 행으로 설정합니다.
    • n following : 현재 행을 기준으로 현재 행에서 n만큼 이후 행을 시작 위치로 설정합니다.
  • frame_end : 윈도우함수가 적용되는 마지막 범위를 정의합니다.
    • n preceding : 현재 행 기준 윈도우의 종료 위치를 n만큼 이전 행으로 설정합니다.
    • current_row: 윈도우 프레임의 종료 위치를 현재 행으로 설정합니다.
    • n following : 윈도우 프레임의 종료 위치를 현재 행에서 n만큼 이후 행으로 설정합니다.
    • unbounded_following : 윈도우 프레임 종료 위치를 현재 행 이후 모든 행으로 설정합니다.
💡
ROW_NUMBER, DENSE_RANK, RANK, PERCENT_RANK, CUME_DIST 와 같이 결과 행에 대해 번호, 순위를 매기는 역할을 하는 함수들은 윈도우 프레임절에서 범위 지정이 불필요하여 윈도우프레임 절에서 사용 시 에러가 발생합니다.
 
예시 1
각 부서 별로 급여를 기준으로 정렬하고, 각 직원의 급여와 해당 부서 내에서 최고 급여를 계산합니다. PARTITION BY로 부서를 기준으로 데이터를 분할하고, ORDER BY로 급여를 정렬합니다. ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING 를 설정하여 현재 직원을 중심으로 이전 2명과 이후 2명까지의 범위를 정의합니다.
SELECT employee_id , department_id , salary , LAST_VALUE(salary) OVER(employee_window) AS top_salary FROM `HR.employees` WINDOW employee_window AS (PARTITION BY department_id ORDER BY salary ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING);
notion imagenotion image
 
예시 2
현재 행을 중심으로 현재 행 이전의 모든 행을 포함합니다. 부서별 시작 행부터 현재 행까지의 모든 행에대한 “salary”합계를 나타냅니다.
SELECT employee_id , department_id , salary , SUM(salary) OVER(PARTITION BY department_id ORDER BY department_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_salary FROM `HR.employees`;
notion imagenotion image
 
예시 3
급여에 따라 직원을 정렬시켜 이전~현재행에서 급여가 높은 직원의 이름을 출력하는 쿼리입니다.
ORDER BY 절이나 WINDOW_frame 절이 없다면 모든 행이 결과에 포함됩니다. 아래 두 쿼리는 같은 결과를 반환하는 쿼리입니다. 집계 함수의 경우 OREDER BY절이 있지만 WINDOW_frame 절이 없는 경우, 현재 행 기준 파티션 내의 첫 번째 행까지 범위를 의미하는 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 가 기본값입니다.
SELECT employee_id , first_name , salary , LAST_VALUE(first_name) OVER(ORDER BY salary) FROM `HR.employees`;
SELECT employee_id , first_name , salary , LAST_VALUE(first_name) OVER(ORDER BY salary RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS LAST_VALUE_SALARY FROM `HR.employees`;
notion imagenotion image
 

9.2.3. 사용자 정의 윈도우 함수

윈도우 함수가 어떻게 작동하고 데이터를 처리할지를 정의할 수 있습니다. 데이터 내에서 특정 범위나 부분 집합에 대한 계산을 수행하도록 지정합니다. named_window에 사용자 정의 윈도우 함수를 지정합니다.
WINDOW [ named_window ][ PARTITION BY 절 ][ ORDER BY 절 ][ window_frame 절 ]
규칙
  1. 윈도우의 스펙 확장 : 기존 윈도우 함수를 선언한 윈도우 함수에서 정의한 다른 스펙과 함께 사용할 수 있습니다. 즉, 기존 윈도우 함수의 스펙을 새로운 스펙으로 확장할 수 있습니다.
  1. 중복 정의 방지 : 기존 윈도우 함수와 선언한 윈도우 함수 내에서 중복된 정의를 가질 수 없습니다. 예를 들어, 기존 윈도우 함수에 ORDER BY 절을 정의하고 새로 정의한 윈도우에 ORDER BY 절을 정의하면 오류가 발생합니다.
  1. 절의 순서 : PARTITION BY는 항상 가장 먼저 나와야 하며, 그 뒤에 ORDER BY와 window_frame절이 따라와야 합니다. 기존 윈도우 함수를 추가할 경우, 가장 먼저 처리됩니다.
  1. PARTITION BY : 신규 선언된 윈도우 함수 내에서 PARTITION BY와 기존 윈도우 함수를 동시에 사용할 수 없습니다. PARTITION BY가 필요한 경우, 해당 부분을 기존 윈도우 함수에 추가해야 합니다.
  1. ORDER BY : ORDER BY절, 하위쿼리, 외부쿼리에서는 기존 윈도우 함수를 사용할 수 없습니다.
 
예시 1
다음은 employee_window 라는 윈도우 함수를 직원의 급여 별로 정렬하도록 정의하여 자기 자신과 앞 2명, 뒤 2명의 급여 중 최고 급여를 “top_salary” 에 반환하는 쿼리입니다.
선언 규칙 4, 5를 준수하지 않고 ORDER BY 절이 기존 WINDOW 함수인 LAST_VALUE() 와 선언한 WINDOW 함수인 “employee_window” 사이에 있으면 오류가 발생합니다.
SELECT employee_id , salary , LAST_VALUE(employee_id) OVER(employee_window ORDER BY salary) AS top_salary FROM `HR.employees` WINDOW employee_window AS (ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) ORDER BY salary;
notion imagenotion image
 
따라서 규칙 3에 따라 PARTITION BY는 항상 가장 먼저 나와야 하며, 그 뒤에 ORDER BY가 따라와야 합니다. ORDER BY 절은 두 윈도우 함수 사이에 위치할 수 없어 파티션 범위는 기존 윈도우 함수 절에서 선언해야 합니다.
SELECT employee_id , salary , LAST_VALUE(employee_id) OVER(employee_window ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS top_salary FROM `HR.employees` WINDOW employee_window AS (ORDER BY salary) ORDER BY salary;
notion imagenotion image
 
예시 2
부서별로 직원의 급여를 누적 합산한 결과를 출력하는 쿼리입니다. 각 부서별로 누적 급여를 계산하기 위해 employee_window라는 윈도우 함수를 선언합니다.
SELECT employee_id , department_id , salary , SUM(salary) OVER employee_window AS cumulative_salary FROM `HR.employees` WINDOW employee_window AS (PARTITION BY department_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) ORDER BY department_id;
notion imagenotion image
 

9.3. WINDOW FUNCTION 분류

9.3.1. 집계함수

  • SUM
SUM()은 테이블에서 선택한 항목에 대한 합계를 나타내는 함수입니다. OVER절에서 선언하는 파티션에 따라 합계, 소계, 누적 합계 계산이 가능합니다.
 
예시 1
다음은 직원 테이블에서 전체 직원 급여의 합계를 구하는 쿼리입니다.
SELECT DISTINCT SUM(salary) OVER() AS total_salary FROM `HR.employees`;
notion imagenotion image
 
예시 2
다음은 PARTITION BY를 이용하여 부서를 기준으로 파티션을 분리해 부서 별 급여를 소계합니다.
SELECT DISTINCT department_id , SUM(salary) OVER(PARTITION BY department_id) AS subtotal_salary FROM `HR.employees` ORDER BY department_id;
notion imagenotion image
 
예시 3
"employees" 테이블에서 부서별로 누적 급여를 계산하는 쿼리입니다.
SELECT employee_id , department_id , salary , SUM(salary) OVER(PARTITION BY department_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_salary FROM `HR.employees` ORDER BY department_id;
notion imagenotion image
 
  • AVG
AVG()는 주어진 컬럼 값들의 평균을 계산하는 데 사용되는 집계 함수입니다. 데이터의 분포를 요약하고 평균값을 반환하여 데이터의 대푯값을 찾는 데 유용합니다.
 
예시
다음은 부서별 평균 급여를 구하는 쿼리입니다.
SELECT DISTINCT department_id , AVG(salary) OVER(PARTITION BY department_id ORDER BY department_id) AS avg_department_salary FROM `HR.employees`
notion imagenotion image
 
  • MIN
MIN()은 주어진 컬럼에서 최솟값을 반환합니다. 즉, 해당 열에서 가장 작은 값을 찾아줍니다.
 
예시
다음은 부서별 최저 급여를 출력하는 쿼리입니다.
SELECT DISTINCT department_id , MIN(salary) OVER(PARTITION BY department_id ORDER BY department_id) AS min_salary FROM `HR.employees`;
notion imagenotion image
 
  • MAX
MAX()는 주어진 컬럼에서 최댓값을 반환합니다. 즉, 해당 열에서 가장 큰 값을 찾아줍니다.
 
예시
다음은 부서별 최고 급여를 출력하는 쿼리입니다.
SELECT DISTINCT department_id , MAX(salary) OVER(PARTITION BY department_id) AS max_salary FROM `HR.employees` ORDER BY department_id;
notion imagenotion image
 
  • COUNT
COUNT()는 주어진 컬럼에서 레코드의 수를 세어 반환합니다. 해당 열에 얼마나 많은 값이 있는지를 세어줍니다.
 
예시
다음은 COUNT 함수를 이용하여 부서별 직원 수를 출력하는 쿼리입니다.
SELECT DISTINCT department_id , COUNT(employee_id) OVER(PARTITION BY department_id) AS count_employees FROM `HR.employees` ORDER BY department_id;
notion imagenotion image
 

9.3.2. 순위함수

순위 함수는 SQL 내에서 행에 순위나 순서를 부여할 때 사용하는 윈도우 함수의 일부입니다.
윈도우 함수로 사용할 수 있는 순위 함수는 대표적으로 RANK(), DENSE_RANK(), ROW_NUMBER()가 있습니다. 순위 함수는 OVER 절과 함께 사용되며, PARTITION BYORDER BY를 통해 데이터를 그룹화하고 정렬할 수 있습니다. 예를 들어, 부서별로 점수를 기준으로 학생들의 순위를 부여하려면 ‘PARTITION BY department’와 같은 형식으로 쿼리를 작성할 수 있습니다. 순위 함수는 데이터의 순서나 그룹 내 상대적인 위치를 정의하는 데 매우 유용합니다.
 
  • RANK
RANK()함수는 결과 셋 내의 행에 순위를 부여합니다. 만약 두 행이 같은 값을 가진다면, 동일한 순위를 부여합니다. 앞에 동일한 순위가 부여 되었다면, 해당 순번만큼 건너뛰고 그다음 순번을 부여합니다. 예를 들어 5명이 동일한 점수로 1위를 차지한 경우, 앞의 5명 모두에게 1위가 부여되고, 다음 순위는 2위가 아닌 6위가 됩니다.
SELECT column1 , column2 , RANK() OVER (PARTITION BY column [1|2] ORDER BY column [1|2] [ASC|DESC],... ) AS alias FROM Table1 as t WHERE condition1;
 
💡
잘못된 정렬 기준을 사용하면 예상치 못한 결과를 얻을 수 있습니다. 따라서 RANK() 함수 사용 시 어떤 열을 기준으로 순위를 매길 것인지, 어떤 열을 기준으로 정렬할 것인지 기준을 명확히 정의해야 합니다. 기본값은 오름차순으로 순위를 부여하며 동일한 값에 대해서는 같은 순위를 부여합니다.
 
예시
“employees” 테이블에서 “department_id”별 “salary”를 높은 연봉부터 낮은 연봉까지를 순위를 매겨 “department_salary_rank”라는 별칭으로 조회하고 해당 순위를 내림차순으로 정렬합니다.
PARTITION BY department_id’를 사용하면 RANK() 함수는 부서별로 순위를 매기기 시작합니다. 최종 결과의 행 순서는 ORDER BY ”department_salary_rank”에 의해 결정됩니다.
SELECT employee_id , department_id , salary , RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS department_salary_rank FROM `HR.employees` ORDER BY department_id, department_salary_rank;
notion imagenotion image
 
  • DENSE_RANK
DENSE_RANK()함수 또한 RANK()처럼 결과 세트 내의 행에 순위를 부여합니다.
하지만, RANK()와는 달리 순위 간 간격이 없어 같은 값을 가진 행이 있더라도 다음 순위는 건너뛰지 않습니다. 동일한 순위가 있어도 순차적인 순위를 부여하고 싶다면, DENSE_RANK()를 사용합니다.
동일한 순위의 바로 그다음 순위는 같은 순위의 수와 상관없이 1을 증가한 값을 돌려줍니다.
예를 들어 1순위가 2명이라도, 중복 순위가 가능하기 때문에 다음 순위는 2순위라고 표시합니다. ORDER BY구문에서 사용한 정렬을 기반으로 순위를 부여합니다. ‘ASC’는 오름차순, ‘DESC’는 내림차순입니다.
SELECT column1 , column2 , DENSE_RANK() OVER(ORDER BY column [1|2] [ASC|DESC],... ) AS alias FROM Table1 WHERE condition1;
 
그룹별로 순위를 부여하고 싶다면 PARTITION BY를 사용하여 결과 집합을 여러 파티션으로 나누어 각 파티션 내에서 독립적으로 순위를 매길 수 있습니다.
파티션 내 ORDER BY절에서 정렬한 후, 순위를 부여합니다. PARTITION BY절에는 파티션 기준 컬럼을 명시합니다.
상세한 순위를 구하고자 한다면, 아래와 같이 ORDER BY절에 컬럼명을 리스트 형식으로 나열하여 결과에 대한 집합을 정렬할 수 있습니다.
SELECT column1 , column2 , DENSE_RANK() OVER(PARTITION BY column [1|2] ORDER BY column1 [ASC|DESC], column2 [ASC|DESC] ... ) AS alias FROM Table1 WHERE condition1;
 
예시
다음 예시에서 “employees” 테이블에서 부서별로 직원들의 급여 순위를 계산합니다. “department_id”별 “salary”를 내림차순으로 하여 순위를 매겨 “dense_rank”로 별칭 합니다.
SELECT department_id , employee_id , salary , DENSE_RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS dense_rank FROM `HR.employees` ORDER BY department_id, dense_rank;
notion imagenotion image
 
  • ROW_NUMBER
ROW_NUMBER() 함수는 결과셋 내의 모든 행에 고유한 순위를 부여합니다. 같은 값을 가진 행이 있더라도 서로 다른 순위가 부여됩니다.
ROW_NUMBER() OVER ([PARTITION BY column [1|2|,,], .. ] ORDER BY sort_expression [ASC | DESC],... )
 
예시 1
“employees” 테이블에서 부서별로 급여가 높은 직원부터 낮은 직원까지 순서대로 행 번호를 부여합니다. ROW_NUMBER() 함수는 부서별로 직원들에게 고유한 급여 순위를 부여합니다. 같은 급여를 받는 직원이 있더라도 서로 다른 순위가 부여됩니다.
SELECT department_id , employee_id , salary , ROW_NUMBER() OVER(PARTITION BY department_id ORDER BY salary DESC) AS row_number FROM `HR.employees` ORDER BY department_id DESC, row_number ASC;
notion imagenotion image
 
💡
OVER 절 내부에 PARTITION BY를 사용하면 파티션 그룹별로 순위를 부여할 수 있습니다. 파티션 내에서 순차적으로 순위를 부여하다가, 다음 파티션으로 넘어가면 순위를 1부터 다시 부여합니다.
 
예시 2
“employees”테이블에서 각 부서별 급여를 내림차순합니다. RANK(), DENSE_RANK(), ROW_NUMBER()를 사용하여 순서를 매깁니다.
SELECT department_id , employee_id , salary , RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS rank , DENSE_RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS dense_rank , ROW_NUMBER() OVER(PARTITION BY department_id ORDER BY salary DESC) AS row_number FROM `HR.employees` ORDER BY department_id, row_number;
notion imagenotion image
 

9.3.3. 행 순서 함수

데이터셋 내에서 각 행의 위치에 따라 값을 계산하거나 반환합니다. 행 순서 함수는 대표적으로 FIRST_VALUE(), LAST_VALUE(), LAG() , LEAD() 함수가 있습니다. 이러한 함수들은 주로 특정 행과 그 주변 행들 간의 관계를 분석하거나 비교할 때 사용하고 시계열 데이터 분석, 이동 평균 계산, 데이터의 변화율 분석 등에 활용할 수 있습니다.
 
  • FIRST_VALUE
FIRST_VALUE()는 각 그룹의 최솟값을 찾을 때 사용합니다. 파티션 내에서 첫 번째 행의 값을 “column1”에 반환합니다. PARTITION BY절에 결과 세트를 여러 파티션으로 나눌 컬럼을 지정하고 ORDER BY절에는 결과 세트의 행을 정렬할 열과 정렬방식을 지정합니다. WINDOW_frame 절에는 함수 결과의 행을 지정합니다.
FIRST_VALUE(column1) OVER(PARTITION BY column [1|2|,,,], ... ] ORDER BY column [1|2|,,,] [ASC | DESC], ... WINDOW_frame )
 
예시
“employees” 테이블에서 부서별로 급여가 가장 높은 직원의 급여를 “first_value” 열로 반환합니다. 부서별 급여를 내림차순 하여 “first_value”로 별칭 한 후, 최댓값을 가져와 조회합니다. “department_id” 오름차순, “salary”는 내림차순 하여 결과를 나타냅니다.
SELECT employee_id , department_id , salary , FIRST_VALUE(salary) OVER(PARTITION BY department_id ORDER BY salary DESC) AS first_value FROM `HR.employees` ORDER BY department_id ASC, salary DESC;
notion imagenotion image
 
  • LAST_VALUE
LAST_VALUE()는 각 그룹의 최댓값을 찾을 때 사용합니다. 파티션 내에서 마지막 행의 값을 “column1”에 반환합니다.
LAST_VALUE(column1) OVER ( [PARTITION BY partition_expression, ... ] ORDER BY sort_expression [ASC | DESC], ... [WINDOW_frame] )
 
예시
“employees” 테이블에서 각 부서별로 급여가 가장 낮은 직원의 급여를 “last_value” 열로 반환합니다. 윈도우 프레임 내에서 마지막 값은 각 부서의 가장 낮은 급여, 즉 최소 급여를 반환합니다.
SELECT department_id , salary , LAST_VALUE(salary) OVER(PARTITION BY department_id ORDER BY salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS last_value FROM `HR.employees` ORDER BY department_id DESC, salary DESC;
💡
DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING를 사용하는 것이 중요합니다. 만약 이 설정을 사용하지 않으면 파티션 내에서만 계산되어 파티션 별로 올바른 결과를 얻을 수 없을 수도 있습니다.
notion imagenotion image
 
  • LAG
LAG()함수는 현재 행과 이전 행 간의 차이를 계산할 때 사용합니다. 현재 행에 대해 지정된 열의 이전 행의 값을 반환합니다.
LAG(column [1|2|,,,], offset, default_value) OVER( [PARTITION BY column [1|2|,,], ... ] ORDER BY column [1|2|,,] [ASC | DESC], ... )
형식
column [1|2] : 이전 행의 값을 반환할 열을 지정합니다.
offset : 현재 행으로부터 몇 행 떨어진 행의 값을 반환할지 정합니다. 기본값은 1입니다.
default_value : 이전 행이 없을 경우 반환할 기본 값을 지정합니다.
PARTITION BY (option) : 결과 세트를 여러 파티션으로 나눌 열을 지정합니다.
ORDER BY : 결과 세트의 행을 정렬할 열과 정렬 방식을 지정합니다.
 
예시
각 부서별로 현재 직원의 급여와 4명 전 직원의 급여(”prev_salary”)를 비교합니다.
SELECT first_name , department_id , salary , LAG(salary, 4) OVER(PARTITION BY department_id ORDER BY salary DESC) AS prev_salary FROM `HR.employees` ORDER BY department_id, salary DESC;
notion imagenotion image
 
  • LEAD
LEAD()함수는 LAG()함수와 유사한 함수입니다. 현재 행과 다음 행 간의 차이를 계산할 때 사용합니다. 현재 행에 대해 지정된 열의 다음 행의 값을 반환합니다.
LEAD(column1, offset, default_num) OVER([PARTITION BY column [1|2], ... ] ORDER BY column [1|2] [ASC | DESC], ... )
형식
column1 : 다음 행의 값을 반환할 열을 지정합니다.
offset : 현재 행으로부터 몇 행 떨어진 행의 값을 반환할지 정합니다. 기본값은 1입니다.
default_num : 이전 행이 없을 경우 반환할 기본 값을 지정합니다.
PARTITION BY (option) : 결과 세트를 여러 파티션으로 나눌 열을 지정합니다.
ORDER BY : 결과 세트의 행을 정렬할 열과 정렬 방식을 지정합니다.
 
예시
CONCAT을 이용하여 ”full_name”이라는 별칭으로 컬럼명을 생성하여 새로운 값을 넣어줍니다.
각 부서 별로 LEAD()함수를 통해 동일한 부서 내에서 다음으로 높은 급여를 검색하도록 설정합니다.
현재 직원의 급여와 다음 직원의 급여 ”next_salary”를 비교합니다.
SELECT CONCAT(first_name, ' ', last_name) AS full_name , department_id , salary , LEAD(salary, 2) OVER(PARTITION BY department_id ORDER BY salary DESC) AS next_salary FROM `HR.employees` ORDER BY department_id, salary DESC;
notion imagenotion image
 

9.3.4. 비율 함수

  • PERCENT_RANK
각 행의 백분위수 순위(0~1)를 가져옵니다. 특정 행이 전체 집합에서 어느 위치에 있는지 백분위 순위를 계산하는 데 사용됩니다.
 
예시
“employees” 테이블에서 각 직원의 정보와 급여를 기준으로 한 백분위 순위 “percent_rank” 를 선택하여 결과를 반환하는 쿼리입니다.
SELECT employee_id , first_name || ' ' || last_name AS full_name , salary , PERCENT_RANK() OVER(ORDER BY salary) AS percent_rank FROM `HR.employees` ORDER BY salary;
notion imagenotion image
 
  • CUME_DIST
CUME_DIST()는 각 행의 누적 분포(상대 위치(0, 1))를 가져옵니다.
 
예시 1
SELECT department_id , employee_id , salary , CUME_DIST() OVER(PARTITION BY department_id ORDER BY salary) AS salary_percent_rank FROM `HR.employees`;
notion imagenotion image
 
예시 2
CUME_DIST() 함수를 사용하여 “hire_date” 컬럼 기준으로 직원들의 입사일 누적 분포를 계산합니다.
SELECT employee_id , first_name , last_name , hire_date , CUME_DIST() OVER(ORDER BY hire_date) AS cumulative_distribution FROM `HR.employees`;
notion imagenotion image
 
  • PERCENTILE_DISC
이산형 값에 대해 지정된 백분위수를 계산합니다.RESPECT NULLS 가 없으면 NULL값을 무시합니다.
 
예시 1
PERCENTILE_DISC() 함수를 사용하여 각 백분위수에 해당하는 직원들의 수수료를 출력합니다.
SELECT PERCENTILE_DISC(commission_pct,0) OVER() AS min_com , PERCENTILE_DISC(commission_pct,0.5) OVER() AS median_com , PERCENTILE_DISC(commission_pct,1) OVER() AS max_com FROM `HR.employees` LIMIT 1;
notion imagenotion image
 
예시 2
위 쿼리에서 RESPECT NULLS 를 추가하여 NULL 값을 포함한 값을 출력합니다.
SELECT PERCENTILE_DISC(commission_pct,0 RESPECT NULLS) OVER() AS min_com , PERCENTILE_DISC(commission_pct,0.5 RESPECT NULLS) OVER() AS median_com , PERCENTILE_DISC(commission_pct,1 RESPECT NULLS) OVER() AS max_com FROM `HR.employees` LIMIT 1;
notion imagenotion image
 
  • NTILE
NTILE() 함수는 주어진 그룹 또는 파티션을 몇 개의 동일한 크기의 그룹으로 분할하는 데 사용됩니다.
 
예시 1
직원들의 급여를 기준으로 급여를 4개의 동일한 크기의 그룹으로 나눕니다.
SELECT department_id , employee_id , salary , NTILE(4) OVER(ORDER BY salary) AS salary_quartile FROM `HR.employees`;
notion imagenotion image
 
예시 2
NTILE(5)를 사용하여 5개의 경력 그룹으로 분할하고, “hire_date” 열을 기준으로 정렬합니다.
SELECT employee_id , first_name , last_name , hire_date , NTILE(5) OVER(ORDER BY hire_date) AS experience_group FROM `HR.employees`;
notion imagenotion image