[Feature] Support PIVOT and UNPIVOT statements (DuckDB-style syntax)
@CoollZzz がすでに取り組んでいます。
2026年6月15日 から。
評価
この issue はまだ評価されていません。
説明
Motivation
Reshaping data between wide and long form is a very common need in time-series analytics and reporting:
- PIVOT (long → wide): turn distinct values of a column (e.g.
region,device_id, a status code) into separate columns, aggregating a measurement per cell. Great for building cross-tab/report-style result sets. - UNPIVOT (wide → long): stack multiple measurement columns (e.g.
temperature,humidity,pressure) into a(name, value)pair. This is especially natural for IoTDB, where the table model stores each measurement as its own column and users frequently want a normalized long format for downstream analytics or export.
Today the table model has no PIVOT / UNPIVOT, so users must hand-write verbose CASE WHEN ... END aggregations (for pivoting) or long UNION ALL chains (for unpivoting). This proposes first-class PIVOT / UNPIVOT support, using DuckDB's syntax as the reference, since DuckDB offers both the SQL-standard form and a friendly simplified form, and is the most ergonomic of the mainstream engines.
Examples below assume a table model table like
device_metrics(time, device_id, region, temperature, humidity).
Proposed Syntax (reference: DuckDB)
DuckDB implements two syntaxes for each statement: a simplified (DuckDB-specific) form and the SQL-standard form. We can adopt one or both.
PIVOT
Simplified form
PIVOT ⟨table⟩
ON ⟨columns⟩ -- distinct values become new columns
USING ⟨aggregate(s)⟩ -- value of each cell
GROUP BY ⟨rows⟩ -- remaining row keys
[ORDER BY ...] [LIMIT ...];
IoTDB example — turn region values into columns of average temperature, one row per device:
PIVOT device_metrics
ON region
USING avg(temperature)
GROUP BY device_id;
Restrict to specific values with IN, and use multiple/aliased aggregates:
PIVOT device_metrics
ON region IN ('north', 'south')
USING avg(temperature) AS avg_temp, max(temperature) AS max_temp
GROUP BY device_id;
-- => columns: device_id, north_avg_temp, north_max_temp, south_avg_temp, south_max_temp
To reshape without aggregating, DuckDB uses first() (e.g. USING first(temperature)).
SQL-standard form
SELECT * FROM device_metrics
PIVOT (
avg(temperature) AS avg_temp
FOR region IN ('north', 'south')
GROUP BY device_id
);
UNPIVOT
Simplified form
UNPIVOT ⟨table⟩
ON ⟨value-columns⟩
INTO NAME ⟨name-col⟩ VALUE ⟨value-col⟩;
IoTDB example — stack measurement columns into long (measurement, value) rows:
UNPIVOT device_metrics
ON temperature, humidity
INTO NAME measurement VALUE value;
-- => columns: time, device_id, region, measurement, value
Dynamic column selection with COLUMNS(* EXCLUDE (...)) (keeps working when new measurements are added):
UNPIVOT device_metrics
ON COLUMNS(* EXCLUDE (time, device_id, region))
INTO NAME measurement VALUE value;
SQL-standard form (with optional INCLUDE NULLS; default drops rows whose value is NULL):
FROM device_metrics
UNPIVOT [INCLUDE NULLS] (
value FOR measurement IN (temperature, humidity)
);
Capabilities to cover (from DuckDB)
- PIVOT:
ON(one or more columns),USING(one or more aggregates, optionalASalias),GROUP BYrows; optionalIN (...)list to fix the pivoted values; generated column naming like⟨value⟩_⟨agg-alias⟩. - UNPIVOT:
ONexplicit columns orCOLUMNS(* EXCLUDE (...)),INTO NAME ... VALUE ...;INCLUDE NULLS; (advanced) multiple value columns in one statement; expressions/casts insideONto reconcile differing column types. - Usable as a top-level statement and inside subqueries / CTEs.
- (DuckDB also exposes
PIVOT_WIDER/PIVOT_LONGERas aliases — optional.)
Design decisions to discuss before implementing
- Which syntax first — simplified, SQL-standard, or both. (Recommend landing one end-to-end first, then the other.)
- Static vs. dynamic columns (biggest one).
ON regionwithout anINlist requires discovering distinct values at runtime, so the output schema isn't known at plan time. Proposal: require an explicitIN (...)list in v1 (static schema), and treat auto-detection as a follow-up (it needs a pre-execution scan of the source). - Default grouping. DuckDB defaults to
GROUP BY ALL. In IoTDB, defaulting to include thetimecolumn would explode cardinality — so we should likely require an explicitGROUP BY(or define a sensible default that excludestime). - NULL handling for UNPIVOT. Default drops NULL values; support
INCLUDE NULLS. - Type unification for the UNPIVOT
valuecolumn. When source columns differ in type, do we implicit-cast or require explicit casts (DuckDB requires explicit casts)? - Generated column naming rules, and how non-identifier pivot values (e.g. values with spaces) are quoted.
- Scope/phasing of multi-column
ON, multiple aggregates, and multiple value columns.
Prior Art
| Engine | PIVOT | UNPIVOT | Auto-detect (dynamic) columns | Friendly/simplified syntax |
|---|---|---|---|---|
| DuckDB (reference) | ✅ | ✅ | ✅ | ✅ (ON/USING/INTO) |
| SQL Server | ✅ | ✅ | ❌ (must list values) | ❌ |
| Oracle | ✅ | ✅ | ❌ | ❌ |
| Snowflake | ✅ | ✅ | ✅ (ANY / subquery) |
❌ |
| Spark SQL | ✅ | ✅ | ❌ (must list values) | ❌ |
Acceptance Criteria
-
PIVOTwithON/USING/GROUP BYand an explicitIN (...)list (static output schema). -
UNPIVOTwithINTO NAME ... VALUE ...(simplified) and/orFOR ... IN (...)(SQL-standard). -
INCLUDE NULLSoption forUNPIVOT(default = drop NULLs). -
PIVOT/UNPIVOTusable inside subqueries and CTEs. - Documented column-naming and value-column type rules.
- Tests + user documentation.
- (Phase 2) dynamic column auto-detection, multiple aggregates,
COLUMNS(* EXCLUDE ...), multiple value columns.
References
- DuckDB — PIVOT statement: https://duckdb.org/docs/stable/sql/statements/pivot
- DuckDB — UNPIVOT statement: https://duckdb.org/docs/stable/sql/statements/unpivot
- DuckDB — Friendly SQL (background): https://duckdb.org/2023/08/23/even-friendlier-sql
Note: this can be split into two independently claimable tasks — PIVOT and UNPIVOT — if preferred.
Suggested labels:
enhancement, table model / SQL.
- 主要言語
- Java
- スター
- 6.4k
- フォーク
- 1.2k
- 平均マージ
- 1日 17時間
- マージ済み PR(30日)
- 152
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/iotdb のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
-
IoTDB Edge: stop-edge.sh does not stop its own process when IOTDB_HOME is set, and reports success オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
[Bug] findColumn throws NullPointerException instead of SQLException for an unknown column name オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
似ている issue
-
area-deployment area-integrations triage:bot-seen
難易度 2/5 半日 初心者へのやさしさ 86/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
apache/flink-agents#1156 ·
-
[source-shopify] FAILED bulk operation without partialDataUrl is silently treated as successful オープンarea/connectors autoteam community connectors/source/shopify needs-triage team/use type/bug
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
難易度 1/5 1時間未満 初心者へのやさしさ 85/100