728x90
반응형

1. Window 함수

Window 함수는 쿼리에서 OVER() 절과 함께 사용된다. 이 OVER() 절은 함수가 어떻게 적용될지에 대한 범위(윈도우)를 정의한다. 윈도우는 PARTITION BY, ORDER BY 등을 사용하여 특정 그룹이나 순서대로 계산할 수 있게 한다.

2. 기본 구조

<window_function> OVER (PARTITION BY <column> ORDER BY <column>)
  • <window_function>: 집계 함수나 순위 함수 등.
  • PARTITION BY <column>: 데이터를 그룹으로 나누어 계산. 이 부분을 생략하면 전체 테이블을 하나의 그룹으로 간주한다.
  • ORDER BY <column>: 각 파티션 내에서 데이터를 정렬하여 계산.

3. 자주 사용되는 함수

ROW_NUMBER(): 각 행에 대해 순차적인 번호를 매긴다.

SELECT id, ROW_NUMBER() OVER (PARTITION BY id ORDER BY created_at) AS row_num
FROM orders;

PARTITION BY 를 써서 특정 컬럼으로 조건을 정하고 rank를 분리할 수도 있다.

 

RANK(): 순위 번호를 매기고, 동점인 경우 동일 순위를 부여한다.

SELECT id, amount, RANK() OVER (ORDER BY amount DESC) AS rank
FROM sales;

 

DENSE_RANK(): RANK()와 비슷하지만 동점인 경우에도 순위를 건너뛰지 않는다.

SELECT id, amount, DENSE_RANK() OVER (ORDER BY amount DESC) AS dense_rank
FROM sales;

 

NTILE(n): 데이터를 n개의 구간으로 나누어 각 행에 구간 번호를 부여 한다. .

SELECT id, amount, NTILE(4) OVER (ORDER BY amount) AS quartile
FROM sales;

 

SUM(), AVG(), MIN(), MAX() 등 집계 함수: 특정 윈도우 내에서 값을 집계한다.

SELECT id, amount, SUM(amount) OVER (PARTITION BY region ORDER BY date) AS running_total
FROM sales;

4. 예시

판매 데이터에서 지역별로 매출의 누적 합계를 계산하는 예시이다.

SELECT region, amount, 
       SUM(amount) OVER (PARTITION BY region ORDER BY sale_date) AS running_total
FROM sales;
  • PARTITION BY region: 지역별로 데이터를 그룹화한다.
  • ORDER BY sale_date: 각 지역 내에서 날짜별로 정렬하여 누적 합계를 계산한다.

5. ROWS BETWEEN 절

ROWS BETWEEN을 사용하면 윈도우 범위를 더 세밀하게 지정할 수 있다. 예를 들어, 현재 행을 기준으로 이전 3개의 행까지의 데이터를 윈도우로 설정할 수 있다.

SELECT id, amount, 
       SUM(amount) OVER (ORDER BY sale_date ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS moving_avg
FROM sales;

ROWS BETWEEN 3 PRECEDING AND CURRENT ROW: 현재 행과 이전 3개의 행을 포함하는 윈도우 범위를 설정한다.

6. 주의사항

  • Window 함수는 집계 함수와는 다르게 GROUP BY와 함께 사용할 수 없다. 대신 PARTITION BY를 사용하여 그룹화한다.
  • 쿼리 성능에 영향을 미칠 수 있으므로 적절한 인덱스와 함께 사용하는 것이 좋다.

7. JPQL 적용

Window 함수의 JPQL 적용 시 단점은 nativeQuery로만 동작한다는 것이다.

예를들어 아래와 같이 함수를 구성할 수 있다.

    @Query(value = """
        SELECT sub.id, sub.name, sub.height
        FROM (
            SELECT m.id, m.name, m.height
                   ROW_NUMBER() OVER (PARTITION BY m.id ORDER BY m.created_at DESC) AS row_num
            FROM member m
            WHERE m.created_at BETWEEN :start AND :end
        ) sub
        WHERE sub.row_num = 1
                """, nativeQuery = true)
    List<Object[]> getMemberData(
        @Param("start") LocalDateTime start,
        @Param("end") LocalDateTime end
    );

nativeQuery=true 옵션을 주어야 하며, 반환 값은 Object[]로 사용하여야 한다.

 

        List<Object[]> memberData = memberRepository.getMemberData(
            start, end);
        for (Object[] data : memberData) {
            result.add(new Member(
                (Integer) data[0],
                (String) data[1],
                ((Double) data[2]).floatValue()
            ));
        }

 

explain (ANALYZE) 쿼리로 성능을 비교해서 더 나은 쿼리를 사용해보자.

728x90
반응형

+ Recent posts