FROM UNPIVOT
FROM UNPIVOT s AS v AT k iterates over the fields of a struct: it binds v to each field’s value and k
to its name. It turns a struct whose field names are data — ticker symbols, feature flag names, metric
names — into rows that the rest of the query can filter, sort and aggregate.
PIVOT does the opposite.
For the formal semantics, see "Ranging over Attribute-Value Pairs" in the FROM chapter of the
PartiQL specification.
Iterating over a struct’s fields
{
'closing_prices': { 'AMZN': 186.40, 'MSFT': 415.10, 'NVDA': 121.75 }
}
SELECT symbol, price
FROM UNPIVOT closing_prices AS price AT symbol
ORDER BY price DESC
[
{ 'symbol': 'MSFT', 'price': 415.10 },
{ 'symbol': 'AMZN', 'price': 186.40 },
{ 'symbol': 'NVDA', 'price': 121.75 }
]
Because of the ORDER BY, the result is an array ([ … ]) rather than a bag; see
ORDER BY.
Field names can be filtered like any other value:
{
'flags': { 'dark_mode': true, 'new_checkout': false, 'beta_search': true }
}
SELECT VALUE name
FROM UNPIVOT flags AS enabled AT name
WHERE enabled = true
<<
'beta_search',
'dark_mode'
>>
AT is optional. Without it, FROM UNPIVOT binds only the values.
Unpivoting a struct inside each row
Like any FROM item, UNPIVOT can range over a struct inside an earlier item’s element, so each row is
paired with each of its own fields:
{
'sensors': <<
{ 'room': 'Lab A', 'readings': { 'temp_c': 22.5, 'co2_ppm': 980 } },
{ 'room': 'Lab B', 'readings': { 'temp_c': 27.0, 'co2_ppm': 1430 } }
>>
}
SELECT s.room, metric, v AS reading
FROM sensors AS s, UNPIVOT s.readings AS v AT metric
WHERE v > 100
<<
{ 'room': 'Lab A', 'metric': 'co2_ppm', 'reading': 980 },
{ 'room': 'Lab B', 'metric': 'co2_ppm', 'reading': 1430 }
>>
When the value isn’t a struct
What FROM UNPIVOT does with a value that isn’t a struct depends on the mode the query runs in. The
specification defines both:
-
In permissive mode, a value that isn’t a struct is treated as the struct
{ '_1': value }, andMISSINGas the empty struct{}. -
In strict mode (also called type-checking mode), it is an error and the query fails.
{
'theme': 'dark'
}
SELECT name, v
FROM UNPIVOT theme AS v AT name
<<
{ 'name': '_1', 'v': 'dark' }
>>
A MAP value isn’t a struct either, but it isn’t treated as one value: FROM UNPIVOT iterates over its
entries, binding AT to each entry’s key, which need not be a string. See the
MAP type RFC.