Introduction
I have IoT devices that are constantly sending data to the server. The data consists of those fields:
state;state_valid;progress;timestamp;
There is no guarantee that data will be received in correct time order, meaning that sometimes it might send data captured in the past, that removes the option to analyze and enrich data at the time of ingestion.
Received data is stored in BigQuery table. Each device has a separate table. The table structure looks like this:
state: INTEGER, REQUIREDstate_valid: BOOLEAN, NULLABLEprogress: INTEGER, REQUIREDtimestamp: TIMESTAMP, REQUIRED
Requirements
After data collection, I need to analyze data adhering to those rules:
- Device is in received
statevalue until different state is received; - If record's
state_validisfalse-statevalue should be ignored and0should be used instead of it; - If record's
state_validisNULL, last receivedstate_validvalue should be used; - In analyzation phase, data should be viewed in one minute intervals;
- For example there shouldn't be a final record that starts at 20:51:07. Start date should be 20:51:00.
- The state that was on for most of the time of one minute interval - should be used for the whole minute.
- For example, if device had state
0from 20:51:01 to 20:51:18 and state2for 20:51:18 to 20:52:12, 20:51:00 to 20:51:59 should be marked as state2.
- For example, if device had state
- The resulting data should group all consecutive intervals with same
statevalue and represent it as one record withstartandendtimestamps - The grouped intervals of same
stateshould have calculated progress difference (max_progress-min_progress)
Example
Let's say I receive this data from device:
| state | state_valid | progress | timestamp |
|---|---|---|---|
| 2 | 1 | 2451 | 20:50:00 |
| 0 | 1 | 2451 | 20:50:20 |
| 2 | 1 | 2451 | 20:52:29 |
| 3 | 1 | 2451 | 20:53:51 |
| 3 | 1 | 2500 | 20:54:20 |
| 2 | 0 | 2500 | 20:55:09 |
Below I provide a visualization of that data on a timeline to better understand the next procedures:
So the received data should be processed in one minute intervals, assigning each minute the state that device was in for the better part of that minute. So the above data becomes:

Then, consecutive intervals of same state value should be merged:

Result
So, I need a query that would, adhering to the requirements described in Requirements section and given the data shown in the Example section provide me such result:
| group_id | state | progress | start_timestamp | end_timestamp | duration |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 20:50:00 | 20:52:00 | 120s |
| 1 | 2 | 0 | 20:52:00 | 20:54:00 | 120s |
| 2 | 3 | 49 | 20:54:00 | 20:55:00 | 60s |
| 3 | 0 | 0 | 20:55:00 | 20:56:00 | 60s |
Sample data
Consider those two data sets as sample data
Sample data 1
Data:
WITH data as (
SELECT * FROM UNNEST([
STRUCT(NULL AS state, 0 AS state_valid, 0 as progress, CURRENT_TIMESTAMP() as timestamp),
(2, 1, 2451, TIMESTAMP('2022-07-01 20:50:00 UTC')),
(0, 1, 2451, TIMESTAMP('2022-07-01 20:50:20 UTC')),
(2, 1, 2451, TIMESTAMP('2022-07-01 20:52:29 UTC')),
(3, 1, 2451, TIMESTAMP('2022-07-01 20:53:51 UTC')),
(3, 1, 2500, TIMESTAMP('2022-07-01 20:54:20 UTC')),
(2, 0, 2500, TIMESTAMP('2022-07-01 20:55:09 UTC')),
])
WHERE NOT state IS NULL
)
Expected outcome:
| group_id | state | progress | start_timestamp | end_timestamp | duration |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 20:50:00 | 20:52:00 | 120s |
| 1 | 2 | 0 | 20:52:00 | 20:54:00 | 120s |
| 2 | 3 | 49 | 20:54:00 | 20:55:00 | 60s |
| 3 | 0 | 0 | 20:55:00 | current_timestamp |
current_timestamp - 20:55:00 |
Sample data 2
Data:
WITH data as (
SELECT * FROM UNNEST([
STRUCT(NULL AS state, 0 AS state_valid, 0 as progress, CURRENT_TIMESTAMP() as timestamp),
(2, 1, 2451, TIMESTAMP('2022-07-01 20:50:00 UTC')),
(0, 1, 2451, TIMESTAMP('2022-07-01 20:50:20 UTC')),
(2, 1, 2451, TIMESTAMP('2022-07-01 20:52:29 UTC')),
(3, 1, 2451, TIMESTAMP('2022-07-01 20:53:51 UTC')),
(3, 1, 2500, TIMESTAMP('2022-07-01 20:54:20 UTC')),
(3, 1, 2580, TIMESTAMP('2022-07-01 20:55:09 UTC')),
(3, 1, 2600, TIMESTAMP('2022-07-01 20:59:09 UTC')),
(3, 1, 2700, TIMESTAMP('2022-07-01 21:20:09 UTC')),
(2, 0, 2700, TIMESTAMP('2022-07-01 22:11:09 UTC'))
])
WHERE NOT state IS NULL
)
Expected outcome:
| group_id | state | progress | start_timestamp | end_timestamp | duration |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 20:50:00 | 20:52:00 | 120s |
| 1 | 2 | 0 | 20:52:00 | 20:54:00 | 120s |
| 2 | 3 | 249 | 20:54:00 | 22:11:00 | 4620s |
| 3 | 0 | 0 | 22:11:00 | current_timestamp |
current_timestamp - 22:11:00 |



