Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UNNESTING multiple arrays in BigQuery

In this example, I have a book database, with one record per book. The records contain the book owners, the genre, and some other info. I need to return a sample of the top 20 per owner, per genre, along with all the data in the row.

I have this code, which does what I need for one data point in the row (Data_one):

WITH `project.dataset.table` AS (
  SELECT 
    Name name, 
    Genre genre, 
    Data_one org
  FROM `project.dataset.booktable`
), search AS (
  SELECT name, genre FROM
  UNNEST(['Alex','James']) name, 
  UNNEST(['HORROR','COMEDY']) genre
)
SELECT name, genre, org 
FROM (
  SELECT t.name, t.genre, ARRAY_AGG(t.org LIMIT 20) orgs
  FROM `project.dataset.table` t JOIN search s 
  ON LOWER(s.name) = LOWER(t.name) 
  AND LOWER(s.genre) = LOWER(t.genre) 
  WHERE RAND() < 0.5
  GROUP BY t.name, t.genre
), UNNEST(orgs) org
ORDER BY name, genre, org

But when I try to extend it to work for a second (and eventually quite a few) piece of data from the row, it inflates the records returned by a factor of 200:

WITH `project.dataset.table` AS (
  SELECT 
    Name name, 
    Genre genre, 
    Data_one org,
    Data_two org2
  FROM `project.dataset.booktable`
), search AS (
  SELECT name, genre FROM
  UNNEST(['Alex','James']) name, 
  UNNEST(['HORROR','COMEDY']) genre
)
SELECT name, genre, org, org2 
FROM (
  SELECT t.name, t.genre, ARRAY_AGG(t.org LIMIT 20) orgs, ARRAY_AGG(t.org2 LIMIT 20) orgs2
  FROM `project.dataset.table` t JOIN search s 
  ON LOWER(s.name) = LOWER(t.name) 
  AND LOWER(s.genre) = LOWER(t.genre) 
  WHERE RAND() < 0.5
  GROUP BY t.name, t.genre
), UNNEST(orgs) org, UNNEST(orgs2) org2
ORDER BY name, genre, org, org2

I know UNNEST turns an array into a table, but is this somehow creating an array of an array and unnesting that? I am unfamiliar with the syntax.

Edit: The data I am trying to get is all on the same level, all single data points (no arrays) and a mixture of NULLABLE STRINGS, INTEGERS, TIMESTAMPS, FLOATS

E.G:

Genre   STRING  NULLABLE
Name    STRING  NULLABLE    
Data_one    STRING  NULLABLE    
Data_two    STRING  NULLABLE    
Data_three  INTEGER NULLABLE    
Data_four   TIMESTAMP   NULLABLE    

Owner   |   Genre    |   Data_one    | Data_two   |Data_three|Data_four
Alex    |   Horror   |  Stephen King |    IT      |    3     |2018-01-02
Alex    |   Sci-fi   |   Andy Weir   |The Martian |    5     |2018-01-02
James   |   Horror   |  Bram Stoker  |   Dracula  |    2     |2018-01-02
Sarah   |   Horror   |  Stephen King | The Stand  |    3     |2018-01-02
James   |   Horror   |  Stephen King |Pet Sematary|    2     |2018-01-02
like image 575
Quirke1337 Avatar asked Sep 24 '26 06:09

Quirke1337


1 Answers

as your question leaks details - below answer is just a direction for you to explore

#standardSQL
SELECT name, genre, data_one, data_two FROM (
  SELECT t.name, t.genre, ARRAY_AGG(t.org LIMIT 20) orgs, ARRAY_AGG(t.org2 LIMIT 20) orgs2
  FROM `project.dataset.table` t JOIN search s 
  ON LOWER(s.name) = LOWER(t.name) 
  AND LOWER(s.genre) = LOWER(t.genre) 
  WHERE RAND() < 0.5
  GROUP BY t.name, t.genre
), UNNEST(orgs) data_one WITH OFFSET pos1
, UNNEST(orgs2) data_two WITH OFFSET pos2
WHERE pos1 = pos2
ORDER BY name, genre, data_one

As you can see - here OFFSET was introduced identifying position of elements within the array and then leaving in result only those combinations which have same positions

In real use case - you most likely have some yet another field that identifies which data_one and data_two belong to the same row and that field can be used to pair those data_one and data_two

Hope this helped to get you direction

Update

as you added schema/example - see below

#standardSQL
SELECT name, genre, data.data_one, data.data_two, data.data_three, data.data_four 
FROM (
  SELECT t.name, t.genre, 
    ARRAY_AGG(STRUCT(data_one, data_two, data_three, data_four) LIMIT 20) data
  FROM `project.dataset.table` t JOIN search s 
  ON LOWER(s.name) = LOWER(t.name) 
  AND LOWER(s.genre) = LOWER(t.genre) 
  WHERE RAND() < 0.5
  GROUP BY t.name, t.genre
), UNNEST(data) data
ORDER BY name, genre

That is exactly what I mentioned in comments to your very first related question in another post (you can just use org.data_one, org.data_two in you select statement)

like image 195
Mikhail Berlyant Avatar answered Sep 26 '26 20:09

Mikhail Berlyant



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!