Search code examples
sqlgroup-bygoogle-bigquerywindow-functionsgaps-and-islands

How to Do Data-Grouping in BigQuery?


I have list of database that needed to be grouped. I've successfully done this by using R, yet now I have to do this by using BigQuery. The data is shown as per following table

| category  | sub_category  | date          | day   | timestamp     | type  | cpc   | gmv       |
|---------- |-------------- |-----------    |-----  |-------------  |------ |------ |---------  |
| ABC       | ABC-1         | 2/17/2020     | Mon   | 11:37:36 PM   | BI    | 1.94  | 252,293   |
| ABC       | ABC-1         | 2/17/2020     | Mon   | 11:37:39 PM   | RT    | 1.94  | 252,293   |
| ABC       | ABC-1         | 2/17/2020     | Mon   | 11:38:29 PM   | RT    | 1.58  | 205,041   |
| ABC       | ABC-1         | 2/18/2020     | Tue   | 12:05:14 AM   | BI    | 1.6   | 208,397   |
| ABC       | ABC-1         | 2/18/2020     | Tue   | 12:05:18 AM   | RT    | 1.6   | 208,397   |
| ABC       | ABC-1         | 2/18/2020     | Tue   | 12:05:52 AM   | RT    | 1.6   | 208,397   |
| ABC       | ABC-1         | 2/18/2020     | Tue   | 12:06:33 AM   | BI    | 1.55  | 201,354   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 11:55:47 PM   | PP    | 1     | 129,282   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 11:56:23 PM   | PP    | 0.98  | 126,928   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 11:57:19 PM   | PP    | 0.98  | 126,928   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 11:57:34 PM   | PP    | 0.98  | 126,928   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 11:58:46 PM   | PP    | 0.89  | 116,168   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 11:59:27 PM   | PP    | 0.89  | 116,168   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 11:59:51 PM   | RT    | 0.89  | 116,168   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 12:00:57 AM   | BI    | 0.89  | 116,168   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 12:01:11 AM   | PP    | 0.89  | 116,168   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 12:03:01 AM   | PP    | 0.89  | 116,168   |
| XYZ       | XYZ-1         | 2/17/2020     | Mon   | 12:12:42 AM   | RT    | 1.19  | 154,886   |

I wanted to group the rows. A row that has <= 8 minutes timestamp-difference with the next row will be grouped as one row with below output example:

| category  | sub_category  | date                      | day       | time      | start_timestamp       | end_timestamp         | type      | cpc   | gmv       |
|---------- |-------------- |-----------------------    |---------  |---------- |---------------------  |---------------------  |---------- |------ |---------  |
| ABC       | ABC-1         | 2/17/2020                 | Mon       | 23:37:36  | (02/17/20 23:37:36)   | (02/17/20 23:38:29)   | BI|RT     | 1.82  | 236,542   |
| ABC       | ABC-1         | 2/18/2020                 | Tue       | 0:05:14   | (02/18/20 00:05:14)   | (02/18/20 00:06:33)   | BI|RT     | 1.59  | 206,636   |
| XYZ       | XYZ-1         | 02/17/2020|02/18/2020     | Mon|Tue   | 0:06:21   | (02/17/20 23:55:47)   | (02/18/20 00:12:42)   | PP|RT|BI  | 0.95  | 123,815   |

There were some new-generated fields as per below:

| fields            | definition                                                |
|-----------------  |--------------------------------------------------------   |
| day               | Day of the row (combination if there's different days)    |
| time              | Start of timestamp                                        |
| start_timestamp   | Start timestamp of the first row in group                 |
| end_timestamp     | Start timestamp of the last row in group                  |
| type              | Type of Row (combination if there's different types)      |
| cpc               | Average CPC of the Group                                  |
| gwm               | Average GMV of the Group                                  |

Could anyone help me to make the query as per above requirements?

Thank you


Solution

  • This is a gaps and island problem. Here is a solution that uses lag() and a cumulative sum() to define groups of adjacent records with less than 8 minutes gap; the rest is aggregation.

    select
        category,
        sub_category,
        string_agg(distinct day, '|' order by dt) day,
        min(dt) start_dt,
        max(dt) end_dt,
        string_agg(distinct type, '|' order by dt) type,
        avg(cpc) cpc,
        avg(gwm) gwm
    from (
        select
            t.*,
            sum(case when dt <= datetime_add(lag_dt, interval 8 minute) then 0 else 1 end)
                over(partition by category, sub_category order by dt) grp
        from (
            select
                t.*,
                lag(dt) over(partition by category, sub_category order by dt) lag_dt
                from (
                    select t.*, datetime(date, timestamp) dt
                    from mytable t
                ) t
            ) t
        ) t
    ) t
    group by category, sub_category, grp
    

    Note that you should not be storing the date and time parts of your timestamps in separated columns: this makes the logic more complicated when you need to combine them (I added another level of nesting to avoid repeated conversions, which would have obfuscated the code).