A spreadsheet is easy to read when every month gets a column. It is harder to analyze when your SQL has to change each time another month arrives. Turning months into values gives you one column to filter, group, and compare.
We will move six spreadsheet cells into rows, preserve the unknown one, and turn the result back into a report. Watch what happens to February before copying a query.
Explore the idea
Follow the blank cell through the reshape
North’s February value is unknown. South’s March value is a measured zero.
6
region–month cells
Two regions, three month columns
The spreadsheet is easy to scan, but month is encoded in the column names.
5
rows after dropping NULL
The unknown February cell disappears
The default unpivot output contains only the five non-NULL values. Zero is retained.
6
rows, including the unknown
Every source cell gets a row
The long-form table preserves North February as NULL, making missing-measurement checks possible.
2
rows in the regional report
Choose the report shape deliberately
The explicit month list fixes column order. SUM combines values at the region–month grain; it does not make unknown values zero.
Read the cells, including the blank
monthly_sales2 rows
A familiar spreadsheet shape
North February is unknown. South March is a real zero. Each region has one source row.
| id | region | jan | feb | mar |
|---|---|---|---|---|
| 1 | North | 100 | NULL | 140 |
| 2 | South | 50 | 70 | 0 |
North has no recorded February amount. South has a recorded March amount of zero. Those facts are different: one is unknown; the other says nothing was sold.
Each region has exactly one source row, and each month column contains the same kind of measurement. Mixing dates, prices, and counts into one unpivoted value column would need a different model.
Stack the month columns
DuckDB’s simplified UNPIVOT names the source columns and the two destination columns:
The simplified form omits the unknown February value
UNPIVOT monthly_sales ON jan, feb, mar
INTO NAME month VALUE amount
ORDER BY region, month;
| id | region | month | amount |
|---|---|---|---|
| 1 | North | jan | 100 |
| 1 | North | mar | 140 |
| 2 | South | feb | 70 |
| 2 | South | jan | 50 |
| 2 | South | mar | 0 |
Scroll for more columns →
month contains the source column name, and amount contains its value. The unchanged id and region columns accompany each resulting row.
Count the result: five rows, not six. The default unpivot behavior omits NULL values, so North’s unknown February cell disappears. That may be appropriate for a sparse list of known measurements. It is wrong if you need to audit missing measurements.
The alphabetical output order is deliberate for this inspection: feb, jan, mar. Month names are text, not dates. Store an actual month date or a numeric month index when chronological calculations matter.
Preserve unknown values explicitly
Use the SQL-standard form with INCLUDE NULLS:
Keep all six cells, including the unknown one
SELECT region, month, amount FROM monthly_sales
UNPIVOT INCLUDE NULLS (amount FOR month IN (jan, feb, mar))
ORDER BY region, month;
| region | month | amount |
|---|---|---|
| North | feb | NULL |
| North | jan | 100 |
| North | mar | 140 |
| South | feb | 70 |
| South | jan | 50 |
| South | mar | 0 |
All six cells now have rows. North February remains NULL, while South March remains zero. The setup saves this result as long_sales, ready for ordinary SQL filters and aggregates.
This format makes questions such as “which region-month measurements are missing?” straightforward. It also lets a new month arrive as more rows rather than a new query expression.
For a changing wide input, DuckDB also supports selecting unpivot columns with a COLUMNS expression. Use that when the naming convention is dependable; blindly selecting every non-key column can sweep unrelated metadata into your measurements.
Build a report with a stable shape
Choose a stable column order for the report
PIVOT long_sales ON month IN ('jan', 'feb', 'mar')
USING sum(amount) GROUP BY region ORDER BY region;
| region | jan | feb | mar |
|---|---|---|---|
| North | 100 | NULL | 140 |
| South | 50 | 70 | 0 |
Scroll for more columns →
PIVOT turns the month values into columns. The explicit IN list fixes their order as January, February, March. GROUP BY region fixes the row grain, while sum(amount) decides how multiple contributing rows would combine.
The report still has NULL for North February and zero for South March. Because this fixture has one measurement per region-month, the sums reproduce the known values. With multiple source rows, pivoting aggregates them; it is not a lossless undo button.
Your turn · check your answer
Find the missing measurement
Return region and month for the unknown measurement in long_sales. Do not include a recorded zero.
See the expected result
| region | month |
|---|---|
| North | feb |
Preparing the editor…
Your result
Need a hint?
NULL and zero express different facts. Test the original amount with IS NULL.
Show a solution
SELECT region, month FROM long_sales WHERE amount IS NULL ORDER BY region, month;
Keep analysis and presentation separate
Long-form rows are convenient for filtering and joining. A wide report is convenient for scanning. You can use both without forcing every downstream calculation to work with a growing list of month columns.
Before adding a “total” column, decide what to do when one month is unknown. sum ignores NULL, so a partial sum can look like a complete quarter. Pair it with an observed-month count, or withhold the quarterly result until every required measurement is present.
The moving-average tutorial carries this distinction into a daily time series, where absent rows and unknown values can change the denominator.