31

Is there any way to get row number for each record in BigQuery? (From the specs, I haven't seen anything about it) There is a NTH() function, but that applies to repeated fields.

There are some scenarios where row number is not necessary in BigQuery, such as the use of TOP() or LIMIT function. However, I need it to simulate some analytical functions, such as a cumulative sum(). For that purpose I need to identify each record with a sequential number. Any workaround on this?

Thanks in advance for your help!

Leo

mdahlman
  • 9,204
  • 4
  • 44
  • 72
Leo Stefa
  • 421
  • 1
  • 4
  • 5

7 Answers7

59

2018 update: If all you want is a unique id for each row

#standardSQL
SELECT GENERATE_UUID() uuid
 , * 
FROM table

2018 #standardSQL solution:

SELECT
  ROW_NUMBER() OVER() row_number, contributor_username,
  count
FROM (
  SELECT contributor_username, COUNT(*) count
  FROM `publicdata.samples.wikipedia`
  GROUP BY contributor_username
  ORDER BY COUNT DESC
  LIMIT 5)

But what about "Resources exceeded during query execution: The query could not be executed in the allotted memory. OVER() operator used too much memory.."

Ok, let's reproduce that error:

SELECT *, ROW_NUMBER() OVER() 
FROM `publicdata.samples.natality` 

Yes - that happens because OVER() needs to fit all data into one VM - which you can solve with PARTITION:

SELECT *, ROW_NUMBER() OVER(PARTITION BY year, month) rn 
FROM `publicdata.samples.natality` 

"But now many rows have the same row number and all I wanted was a different id for each row"

Ok, ok. Let's use partitions to give a row number to each row, and let's combine that row number with the partition fields to get an unique id per row:

SELECT *
  , FORMAT('%i-%i-%i', year, month, ROW_NUMBER() OVER(PARTITION BY year, month)) id
FROM `publicdata.samples.natality` 

enter image description here


The original 2013 solution:

Good news: BigQuery now has a row_number function.

Simple example:

SELECT [field], ROW_NUMBER() OVER()
FROM [table]
GROUP BY [field]

More complex, working example:

SELECT
  ROW_NUMBER() OVER() row_number,
  contributor_username,
  count,
FROM (
  SELECT contributor_username, COUNT(*) count,
  FROM [publicdata:samples.wikipedia]
  GROUP BY contributor_username
  ORDER BY COUNT DESC
  LIMIT 5)
Felipe Hoffa
  • 54,922
  • 16
  • 151
  • 325
3

Another HACK would be to go along the lines of:

SELECT *
FROM UNNEST(ARRAY(
    SELECT myColumn FROM myTable
)) AS myValue WITH OFFSET off

This gives you a resultset with 2 colums: myValue and off.

Benefit of this is that you could also use off in WHERE clauses create a non deterministic LIMIT, e.g. WHERE off < (SELECT SUM(amount) FROM mySecondTable)

Note that I do not consider this a viable alternative for large amounts of data. But it might suit your use case.

Melle
  • 7,639
  • 1
  • 30
  • 31
0

We don't expose a row identifier. Can you simply add one to your data when you import it?

Ryan Boyd
  • 2,978
  • 1
  • 21
  • 19
  • Thanks for your answer Ryan. Even we could import row identifier in our imports, it wouldn't be useful since we need the row number after applying a group function over the original data. – Leo Stefa Jun 18 '12 at 13:32
  • So you're looking for a result row #, not a row # that represents each row of the underlying data? – Ryan Boyd Jun 18 '12 at 23:59
0

I thought maybe I could get around the lack of a ROW_NUMBER() function by joining a table to itself on a <= and then doing a count(*) on the results (which is how you do it sometimes in MySQL). Turns out, BigQuery only supports joins on straight-up "=".

Foiled again. I think this is impossible in BQ.

John
  • 321
  • 1
  • 3
  • 9
0

I recently came upon this problem but my use case needed a continuous row number from start to end. Probably not ideal but leaving it here in case it can help someone.

I use a guide table with offsets for each partition to be added to all its rows. This offset is the sum count of rows in all it's preceding partitions.

select offset+ROW_NUMBER() OVER(PARTITION BY partitionDate) rowId
from `sample.example` input
left join
      (select partitions.partitionDate, partitions.count, SUM(duplicate.count)-partitions.count as offset
       from (
           select date(_PARTITIONTIME) partitionDate,COUNT(1) count 
           FROM `sample.example` 
           where date(_PARTITIONTIME) >= "2020-01-01" 
           group by _PARTITIONTIME) partitions
      inner join (
           select date(_PARTITIONTIME) partitionDate,COUNT(1) count 
           FROM `sample.example`
           where date(_PARTITIONTIME) >= "2020-01-01" 
           group by _PARTITIONTIME) duplicate 
      on partitions.partitionDate >= duplicate.partitionDate
      group by partitions.partitionDate, partitions.count
      order by partitions.partitionDate) guide
on date(_PARTITIONTIME) = guide.partitionDate
where date(_PARTITIONTIME) >= "2020-01-01" 
order by partitionDate
Prince
  • 1
  • 3
0

I think, to avoid "Resources exceeded during query execution" while using OVER() with ORDER BY or PARTITION

SELECT *, ROW_NUMBER() OVER(row_number_partition) rn 
FROM `publicdata.samples.natality` 
WINDOW
row_number_partition AS
(PARTITION BY year, month)
Karthi V
  • 79
  • 1
  • 3
0

A simple query to add an increasing number to all your rows :)


SELECT ROW_NUMBER() OVER (PARTITION BY 'hola') as row_number, * 
FROM <table>

Of course this is a hack.

caravana_942
  • 632
  • 1
  • 8
  • 26