Pivot 내부 구현

Pivot 내부 구현 (Pivot Internals)

PIVOT

[피보팅]({% link docs/current/sql/statements/pivot.md %})은 SQL 쿼리 재작성과 더 높은 성능을 위한 전용 PhysicalPivot 연산자의 조합으로 구현돼요. 각 PIVOT은 리스트로의 일련의 집계로 구현되고, 전용 PhysicalPivot 연산자가 그 리스트들을 컬럼 이름과 값으로 변환해요. 피보팅 시 생성할 열이 동적으로 감지되는 경우(IN 절을 사용하지 않을 때) 추가 전처리 단계가 필요해요.

출처: 문서

본문

DuckDB는 대부분의 SQL 엔진처럼 쿼리 시작 시 모든 컬럼 이름과 타입을 알아야 해요. PIVOT 구문의 결과로 생성해야 할 열을 자동으로 감지하려면, 이를 여러 쿼리로 변환해야 해요. [ENUM 타입]({% link docs/current/sql/data_types/enum.md %})이 열이 되어야 할 고유 값들을 찾는 데 사용돼요. 그런 다음 각 ENUMPIVOT 구문의 IN 절 중 하나에 주입돼요.

IN 절이 ENUM으로 채워진 후, 쿼리는 리스트로의 일련의 집계로 다시 재작성돼요.

예를 들어:

PIVOT cities
ON year
USING sum(population);

는 처음에 다음과 같이 변환돼요:

CREATE TEMPORARY TYPE __pivot_enum_0_0 AS ENUM (
    SELECT DISTINCT
        year::VARCHAR
    FROM cities
    ORDER BY
        year
    );
PIVOT cities
ON year IN __pivot_enum_0_0
USING sum(population);

그리고 마지막으로 다음과 같이 변환돼요:

SELECT country, name, list(year), list(population_sum)
FROM (
    SELECT country, name, year, sum(population) AS population_sum
    FROM cities
    GROUP BY ALL
)
GROUP BY ALL;

이는 다음과 같은 결과를 만들어요:

country name list("year") list(population_sum)
NL Amsterdam [2000, 2010, 2020] [1005, 1065, 1158]
US Seattle [2000, 2010, 2020] [564, 608, 738]
US New York City [2000, 2010, 2020] [8015, 8175, 8772]

PhysicalPivot 연산자는 그 리스트들을 컬럼 이름과 값으로 변환해 다음 결과를 반환해요:

country name 2000 2010 2020
NL Amsterdam 1005 1065 1158
US Seattle 564 608 738
US New York City 8015 8175 8772

UNPIVOT

내부 구현 (Internals)

언피보팅은 전적으로 SQL 쿼리로의 재작성으로 구현돼요. 각 UNPIVOT은 컬럼 이름 리스트와 컬럼 값 리스트에 대해 동작하는 일련의 unnest 함수로 구현돼요. 동적 언피보팅 시 COLUMNS 표현식이 먼저 평가되어 컬럼 리스트를 계산해요.

예를 들어:

UNPIVOT monthly_sales
ON jan, feb, mar, apr, may, jun
INTO
    NAME month
    VALUE sales;

는 다음과 같이 변환돼요:

SELECT
    empid,
    dept,
    unnest(['jan', 'feb', 'mar', 'apr', 'may', 'jun']) AS month,
    unnest(["jan", "feb", "mar", "apr", "may", "jun"]) AS sales
FROM monthly_sales;

month를 채우기 위한 텍스트 문자열 리스트를 만드는 데는 작은따옴표를, sales에 사용할 컬럼 값을 가져오는 데는 큰따옴표를 사용한다는 점을 주목해요. 이는 초기 예시와 동일한 결과를 만들어요:

empid dept month sales
1 electronics jan 1
1 electronics feb 2
1 electronics mar 3
1 electronics apr 4
1 electronics may 5
1 electronics jun 6
2 clothes jan 10
2 clothes feb 20
2 clothes mar 30
2 clothes apr 40
2 clothes may 50
2 clothes jun 60
3 cars jan 100
3 cars feb 200
3 cars mar 300
3 cars apr 400
3 cars may 500
3 cars jun 600

더 알아보기 (Learn more)

PIVOT 구문의 사용 방법은 [PIVOT Statement]({% link docs/current/sql/statements/pivot.md %}) 문서를 참고해요.