Use below

SELECT
  col_a, col_b, el
FROM
  `table`,
UNNEST(array) as el
Answer from Mikhail Berlyant on Stack Overflow
🌐
Count
count.co › sql-resources › bigquery-standard-sql › unnest
UNNEST | BigQuery Standard SQL - Count.co
This will create multiple rows for each element of your array but you can then filter it down. SELECT * FROM UNNEST([1, 2, 2, 5, NULL]) AS unnest_column
🌐
Medium
medium.com › data-engineers-notes › unnesting-arrays-in-bigquery-c1b48d413ece
UNNESTING ARRAYS in BigQuery
April 15, 2024 - If we want to perform operations (say find out what was the earliest enrollment date, or count the distinct activities that the members are enrolled in), we’d need to unpack these rows by UNNESTing the ARRAY where activities are stored it.
🌐
Yuichi Otsuka
yuichiotsuka.com › home › blog › bigquery unnest and working with arrays
BigQuery UNNEST and Working with Arrays - Yuichi Otsuka
June 10, 2024 - We now include the last row, with a NULL value for the column samples_individual. When we use the UNNEST function on a column in BigQuery, all the rows under that column is flattened all at once.
🌐
Learn BigQuery!
scosco.github.io › learn-bigquery › foundation › subqueries.html
Subqueries: Arrays + UNNEST() | Learn BigQuery!
You can even think of your unnested array as a temporary table. But what about columns? A real table has a couple of columns, right? Introduce STRUCTs! SELECT * FROM UNNEST( [ STRUCT(12 as id, 'Hannah' as name), (13, 'Simone'), (14, 'Ada') ] ) BigQuery is semi-structured - that means the schema needs to be consistent.
Top answer
1 of 4
6

That's because the comma is a cross join - in combination with an unnested array it is a lateral cross join. You repeat the parent row for every row in the array.

One problem with pivoting arrays is that arrays can have a variable amount of rows, but a table must have a fixed amount of columns.

So you need a way to decide for a certain row that becomes a certain column.

E.g. with

SELECT 
  id,
  name,
  groups[ordinal(1)] as firstArrayEntry,
  groups[ordinal(2)] as secondArrayEntry,
  keyword
FROM `project_id.dataset_id.table_id`
unnest(groups)
where id = 204358

If your array had a key-value pair you could decide using the key. E.g.

SELECT 
  id,
  name,
  (select value from unnest(groups) where key='key1') as key1,
  keyword
FROM `project_id.dataset_id.table_id`
unnest(groups)
where id = 204358

But that doesn't seem to be the case with your table ...

A third option could be PIVOT in combination with your cross-join solution but this one has restrictions too: and I'm not sure how computation-heavy this is.

2 of 4
2

Consider below simple solution

select * from (
  select id, name, keyword, offset
  from `project_id.dataset_id.table_id`,
  unnest(`groups`) with offset 
) pivot (max(name) name for offset + 1 in (1, 2))      

if applied to sample data in your question - output is

Note , when you apply to your real case - you just need to know how many such name_NNN columns to expect and extend respectively list - for example for offset + 1 in (1, 2, 3, 4, 5)) if you expect 5 such columns

In case if for whatever reason you want improve this - use below where everything is built dynamically for you so you don't need to know in advance how many columns it will be in the output

execute immediate (select '''
select * from (
  select id, name, keyword, offset
  from `project_id.dataset_id.table_id`,
  unnest(`groups`) with offset 
) pivot (max(name) name for offset + 1 in (''' ||  string_agg('' || pos, ', ')  || '''))
'''
from (select pos from (
  select max(array_length(`groups`)) cnt
  from `project_id.dataset_id.table_id` 
  ), unnest(generate_array(1, cnt)) pos 
))
Find elsewhere
Top answer
1 of 1
56

The best way to think about this is by looking at what happens on a row-by-row basis. Setting up some input data, we have:

CopyWITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
)
...

(I'm using a third element for the second row to make things more interesting). If we just select from it, we get output that looks like this:

CopyWITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
)
SELECT * FROM t1;
+----+---------------+
| id | numbers_array |
+----+---------------+
| 1  | [0, 1]        |
| 2  | [2, 4, 5]     |
+----+---------------+

Now let's talk about unnesting. The UNNEST function takes an array and returns a value table of the array's element type. Whereas most BigQuery tables are SQL tables defined as a collection of columns, a value table has rows of some value type. For numbers_array, UNNEST(numbers_array) returns a value table whose value type is INT64, since numbers_array is an array with an element type of INT64. This value table contains all of the elements in numbers_array for the current row from t1.

For the row with an id of 1, the contents of the value table returned by UNNEST(numbers_array) are:

Copy+-----+
| f0_ |
+-----+
| 0   |
| 1   |
+-----+

This is the same as what we get with the following query:

CopySELECT * FROM UNNEST([0, 1]);

UNNEST([0, 1]) in this case means "create a value table from the INT64 values 0 and 1".

Similarly, for the row with an id of 2, the contents of the value table returned by UNNEST(numbers_array) are:

Copy+-----+
| f0_ |
+-----+
| 2   |
| 4   |
| 5   |
+-----+

Now let's talk about how CROSS JOIN fits into the picture. In most cases, you use CROSS JOIN between two uncorrelated tables. In other words, the contents of the table on the right of the CROSS JOIN are not defined by the current contents of the table on the left.

In the case of arrays and UNNEST, however, the contents of the value table produced by UNNEST(numbers_array) change depending on the current row of t1. When we join the two tables, we get the cross product of the current row from t1 with all of the rows from UNNEST(numbers_array). For example:

CopyWITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
)
SELECT id, number
FROM t1
CROSS JOIN UNNEST(numbers_array) AS number;
+----+--------+
| id | number |
+----+--------+
| 1  | 0      |
| 1  | 1      |
| 2  | 2      |
| 2  | 4      |
| 2  | 5      |
+----+--------+

numbers_array has two elements in the first row and three elements in the second, so we get 2 + 3 = 5 rows in the result of the query.

To answer the question about how this differs from flattening the numbers_array and then performing a CROSS JOIN, let's look at the results of this query:

CopyWITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
), t2 AS (
  SELECT number
  FROM t1
  CROSS JOIN UNNEST(numbers_array) AS number
)
SELECT number
FROM t2;
+--------+
| number |
+--------+
| 0      |
| 1      |
| 2      |
| 4      |
| 5      |
+--------+

In this case, t2 is is a SQL table with a column named number with those values. If we perform a CROSS JOIN between t1 and t2, we get a true cross product of all rows:

CopyWITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
), t2 AS (
  SELECT number
  FROM t1
  CROSS JOIN UNNEST(numbers_array) AS number
)
SELECT id, numbers_array, number
FROM t1
CROSS JOIN t2;
+----+---------------+--------+
| id | numbers_array | number |
+----+---------------+--------+
| 1  | [0, 1]        | 0      |
| 1  | [0, 1]        | 1      |
| 1  | [0, 1]        | 2      |
| 1  | [0, 1]        | 4      |
| 1  | [0, 1]        | 5      |
| 2  | [2, 4, 5]     | 0      |
| 2  | [2, 4, 5]     | 1      |
| 2  | [2, 4, 5]     | 2      |
| 2  | [2, 4, 5]     | 4      |
| 2  | [2, 4, 5]     | 5      |
+----+---------------+--------+

So what's the difference between this and the previous query with CROSS JOIN UNNEST(numbers_array)? In this case, the contents of t2 don't change for each row from t1. For the first row in t1, there are five rows in t2. For the second row in t1, there are five rows in t2. As a result, the CROSS JOIN between the two of them returns 5 + 5 = 10 rows in total.

🌐
Sheetgo
sheetgo.com › home › how to use unnest in bigquery
How to use unnest in BigQuery - Sheetgo
December 17, 2025 - The UNNEST function in BigQuery is used to flatten nested or repeated data structures into separate rows. What it does is take as input a column with a nested data type like an ARRAY, and expand the nested or repeated elements into multiple rows.
🌐
Stack Overflow
stackoverflow.com › questions › 73803539 › unnest-array-of-integers-sql-bigquery
Unnest array of integers SQL BigQuery - Stack Overflow
-- Solution1 select product_id from (select "123,234,345,456,456,678,789" as product_ids),unnest(split(product_ids)) as product_id --Solution2 select product_id from (select struct("ios" as os, "123,234,345,456,456,678,789" as product_ids) as os_products), unnest(split(os_products.product_ids)) as product_id --Solution3 select product_id from (select array_agg(os_products) as os_products from (select struct("ios" as os, "123,234,345,456,456,678,789" as product_ids) as os_products union all select struct("android" as os, "abc,cde,efg" as product_ids) as os_products )), unnest(os_products) as op
Top answer
1 of 2
10

UNNEST operator takes an ARRAY and returns a table, with one row for each element in the ARRAY. You can also use UNNEST outside of the FROM clause with the IN operator.

For input ARRAYs of most element types, the output of UNNEST generally has one column. This single column has an optional alias, which you can use to refer to the column elsewhere in the query. ARRAYS with these element types return multiple columns:

STRUCT UNNEST destroys the order of elements in the input ARRAY. Use the optional WITH OFFSET clause to return a second column with the array element indexes (see below).

For an input ARRAY of STRUCTs, UNNEST returns a row for each STRUCT, with a separate column for each field in the STRUCT. The alias for each column is the name of the corresponding STRUCT field.

You can read much more about UNNESTin a much more applicable section - FROM clause - go there and scroll a little down till UNNEST section

2 of 2
3

The outer query that selects from events_20180725 introduces the event_params into the scope of the select list. When you put a scalar subquery in the select list, that subquery can reference columns from the outer scope. The UNNEST function returns a relation given a column reference, which introduces other columns into the scope of the subquery, namely key and value in this case. In the case of this scalar subquery:

(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'TITLE')

Filtering on key = 'TITLE' restricts the rows returned by UNNEST just to the one where the key has that value.

🌐
Google Cloud
cloud.google.com › bigquery › work with arrays
Work with arrays | BigQuery | Google Cloud Documentation
SELECT * FROM UNNEST(['foo', 'bar', ... of the other columns in each row, use a correlated INNER JOIN to join the table containing the ARRAY column to the UNNEST output of that ARRAY column....
🌐
Datawise
datawise.dev › pay-attention-to-this-when-unnesting-in-bigquery
BigQuery UNNEST: LEFT JOIN vs Comma — Which Drops Rows?
3 weeks ago - Here's a common confusion that I encounter while working with ARRAYs in BigQuery. This can lead to wrong queries in some situations. Consider the following two snippets: FROM table, UNNEST(repeated_column) AS single_item
🌐
Medium
medium.com › data-engineers-notes › pay-attention-to-this-when-unnesting-in-bigquery-c06a1e911bef
Pay attention to this when UNNESTing in BigQuery | by Constantin Lungu | Data Engineer’s Notes | Medium
April 15, 2024 - Here’s a common confusion that I encounter while working with ARRAYs in BigQuery. This can lead to wrong queries in some situations. Consider the following two snippets: FROM table, UNNEST(repeated_column) AS single_item
🌐
Towards Data Science
towardsdatascience.com › home › latest › bigquery unnest: how to work with nested data in bigquery
BigQuery UNNEST: How to work with nested data in BigQuery | Towards Data Science
January 24, 2025 - Again, we use the BigQuery UNNEST function to achieve this. The basic approach is to: ... Reconstruct the structure using the ARRAY and STRUCT datatypes accordingly.
Top answer
1 of 1
1

it did not work as expected because of my arrays does not have the same size.

for your specific sample in your question, you can left join unnested arrays.

WITH tbl AS (
  SELECT ['Unknown','Eletric','High Voltage'] AS product_category, ['Premium','New'] as client_cluster 
)
SELECT p AS product_category, c AS client_cluster 
  FROM tbl, UNNEST(product_category) p WITH offset
  LEFT JOIN UNNEST(client_cluster) c WITH offset USING (offset);

But the length of product_category is less than that of client_cluster, it won't work as you wish.

WITH tbl AS (
  SELECT ['Eletric','High Voltage'] AS product_category, ['Supreme', 'Premium','New'] as client_cluster 
)
SELECT p AS product_category, c AS client_cluster 
  FROM tbl, UNNEST(product_category) p WITH offset
  LEFT JOIN UNNEST(client_cluster) c WITH offset USING (offset);

I might be wrong, but as far as I know you can't use FULL JOIN or RIGHT JOIN with flattened array to solve this issue. If you try to do so, you will get:

Query error: Array scan is not allowed with FULL JOIN: UNNEST expression at [31:13]

So you might consider below workaround using a hidden table(array) for join key.

WITH tbl AS (
  SELECT 1 id, ['Unknown','Eletric','High Voltage'] AS product_category, ['Premium','New'] as client_cluster,
   UNION ALL
  SELECT 2 id, ['Eletric','High Voltage'], ['Premium','New', 'Supreme']
)
SELECT id, p AS product_category, c AS client_cluster 
  FROM tbl, UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(client_cluster), ARRAY_LENGTH(product_category)) - 1)) o0
  LEFT JOIN UNNEST(product_category) p WITH offset o1 ON o0 = o1
  LEFT JOIN UNNEST(client_cluster) c WITH offset o2 ON o0 = o2;

Query results