Unnesting¶
Unnesting expands an array-valued field into a stream of rows, one per array element. The unnesting features in FDB Record Layer are aligned with the PartiQL notation, in which an array-typed reference appears as a table source in the FROM clause.
Important
FDB Record Layer does not support the standard SQL UNNEST() table function.
Basic Syntax¶
To unnest an array field, reference it as a table source after the row source that contains it:
SELECT table.*, element FROM table, table.array_column AS element
For each row of table, the array is expanded and element is bound, in turn, to each element. The result is one row per input-row/element pair. If the array is NULL or empty in a row, that row contributes no output rows. In other words, the semantics of unnesting are comparable to an inner join.
Important
FDB Record Layer does not support a LEFT OUTER JOIN variant for unnesting with outer join semantics.
As with other table sources, the AS keyword is optional:
SELECT table.*, element FROM table, table.array_column element
If the array element type is a STRUCT, element refers to the struct and its fields can be accessed via qualified names (element.field) or by star expansion (element.*). If the element type is a scalar, element refers directly to the scalar value and writing element.* is not allowed.
The query above shows the PartiQL notation for unnesting. An equivalent but more verbose way to express this unnesting operation is with a lateral subquery in the FROM clause:
SELECT table.*, elements.element FROM table, (SELECT element FROM table.array_column) AS elements
The planner produces the same plan as with the PartiQL form.
Generating Ordinals with the AT Clause¶
In addition to the array elements themselves, unnesting can generate the 1-based position of each element in its original array. To do so, add an AT clause after the element alias:
SELECT table.*, element, ordinal FROM table, table.array_column AS element AT ordinal
ordinal is an INTEGER bound to the position of the element. The position is 1-based.
The element alias is optional when AT is used. In the rare case where only the ordinals are needed, you can leave it out:
SELECT table.*, ordinal FROM table, table.array_column AT ordinal
The AT clause is only valid on an array-typed source. Applying it to a base table, view, or CTE raises a WRONG_OBJECT_TYPE error (SQLSTATE 42809).
Note
Sorting on the AT ordinal is not supported.
Examples¶
Setup¶
For these examples, assume we have the following type and table:
CREATE TYPE AS STRUCT line_item (sku STRING, qty INTEGER)
CREATE TABLE orders (
order_id BIGINT,
tags STRING ARRAY,
items line_item ARRAY,
prices DOUBLE ARRAY,
PRIMARY KEY (order_id)
)
INSERT INTO orders VALUES
(1, ['priority', 'gift'], [('A1', 2), ('A2', 1)], [9.99, 19.99]),
(2, ['backorder'], [('B1', 3)], [4.50]),
(3, NULL, [('C1', 1), ('C2', 2), ('C3', 1)], [29.99, 14.50, 5.00]),
(4, [], [], [])
The prices array is positionally aligned with the items array: prices[i] is the unit price of items[i].
Unnesting a Scalar Array¶
The following query unnests the tags array so that each tag becomes its own row:
SELECT order_id, tag FROM orders, orders.tags AS tag
|
|
|---|---|
|
|
|
|
|
|
Orders 3 and 4 contribute no rows because their tags value is NULL and [], respectively.
Unnesting a STRUCT Array¶
The following query unnests items and accesses the fields of the line_item struct via qualified names:
SELECT order_id, item.sku, item.qty FROM orders, orders.items AS item
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Star Expansion on a STRUCT Element¶
Because each items element is a STRUCT, item.* expands to one field per struct field:
SELECT order_id, item.* FROM orders, orders.items AS item
This produces the same result as the previous example.
Important
The alias.* notation is rejected when the array element type is a scalar (such as STRING or INTEGER) or a VECTOR, because there are no fields to expand. For instance, SELECT tag.* FROM orders, orders.tags AS tag is invalid.
In the unusual case where you need a one-field record wrapping a scalar element, you can build one explicitly with a subquery:
SELECT order_id, sq.*
FROM orders, (SELECT (tag) AS wrapped FROM orders.tags AS tag) AS sq
Unnesting in a Lateral Subquery¶
The struct-array example above can equivalently be written with a lateral subquery:
SELECT order_id, item.sku, item.qty FROM orders, (SELECT sku, qty FROM orders.items) AS item
The result is identical and the planner produces the same plan.
Element Ordinals with AT¶
Use AT to query the 1-based ordinal of each element alongside its value:
SELECT order_id, tag, at FROM orders, orders.tags AS tag AT at
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
If you only care about the positions, omit the element alias:
SELECT order_id, at FROM orders, orders.items AT at
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
This does not bind any alias to the elements themselves; only the at ordinal is in scope.
Ordinals and Filtering¶
When you combine AT with a WHERE predicate to filter the elements, the elements keep the ordinals they had in the original array.
SELECT order_id, item.sku, item.qty, at
FROM orders, orders.items AS item AT at
WHERE item.qty >= 2
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
The row of Order 3 (C1, 1) is filtered out, but (C2, 2) keeps its original ordinal 2, not 1.
Unnesting Two Arrays in Parallel using AT as a Subscript¶
When two arrays in the same row are positionally aligned, you can use the AT ordinal as a subscript for the second array to produce one row per element pair.
SELECT order_id, item.sku, item.qty, orders.prices[at] AS price
FROM orders, orders.items AS item AT at
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Computing the Cross Product of Two Unnested Arrays¶
Two array sources in FROM produce a cross product within each row. The AT ordinals on each side are independent.
SELECT order_id, tag, item.sku, at_t, at_i
FROM orders,
orders.tags AS tag AT at_t,
orders.items AS item AT at_i
For each order/tag pair, at_i restarts at 1 in the result:
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Orders 3 and 4 produce no rows because at least one of their arrays is [] or NULL.
See Also¶
Subqueries - Subqueries and correlated subqueries
Joins - Joining multiple tables
Indexes - Indexing nested fields and unnested arrays
SELECT -
SELECTstatement syntax