[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 小时
- 30 天内合并 PR
- 152
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 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
-
难度 2/5 1-3 小时 新手友好度 78/100
-
难度 2/5 1-3 小时 新手友好度 78/100
相似的 Issue
-
documentation
难度 2/5 1-3 小时 新手友好度 65/100
inu-appcenter/memorIN-backend#288 ·
-
难度 2/5 1-3 小时 新手友好度 65/100
-
frontend maui-pilot pilot-ask question
难度 2/5 1-3 小时 新手友好度 75/100
-
难度 2/5 1-3 小时 新手友好度 75/100
-
area/plugin
难度 2/5 1-3 小时 新手友好度 75/100
kestra-io/plugin-kestra#190 ·