Time and JSON data
An event has two times and a payload. Pick the right time, and turn the payload into columns.
In chapters 1 to 5 most rows were orders, customers or items. Now each row can be an event. An event is one thing that happened, such as a customer opening a page.
Event data brings two new problems. The first is time: every event has a timestamp, and time has traps. A timestamp is a date plus a time of day, such as 2026-01-03 09:15:00.
The second is JSON. JSON is text that holds named values, such as {"device":"ios"}. It is semi-structured data: the values have names, but the table has no fixed column for each one.
A payload is the JSON that an event carries with it. This chapter shows how to read it with SQL.
A pipeline is the program that loads data into your tables. To ingest data is to load it.
Two new tables
This chapter adds two made-up tables to the shop. The first gives each order a timestamp:
| order_id | created_at |
|---|---|
| 101 | 2026-01-03 09:15:00+00 |
| 102 | 2026-01-10 23:40:00+00 |
| 103 | 2026-01-11 08:05:00+00 |
| 104 | 2026-01-15 14:30:00+00 |
| 105 | 2026-01-20 02:10:00+00 |
| 106 | 2026-01-21 09:45:00+00 |
Made-up times. Each time falls on the same UTC day as order_date in orders.
SELECT order_id, created_at
FROM order_times
ORDER BY order_id;The second table lists eight web events. Each has two times and a JSON payload:
| event_id | customer_id | event_name | event_time | ingested_at | payload |
|---|---|---|---|---|---|
| 401 | 1 | view | 2026-01-03 08:50:00+00 | 2026-01-03 08:50:10+00 | {"device":"ios","campaign":"winter"} |
| 402 | 1 | cart | 2026-01-03 09:10:00+00 | 2026-01-03 09:10:05+00 | {"device":"ios"} |
| 403 | 1 | checkout | 2026-01-03 09:10:00+00 | 2026-01-03 09:10:07+00 | {"device":"ios","campaign":null,"order_id":101,"items":["Pen","Notebook"],"amount":"40.00"} |
| 404 | 2 | view | 2026-01-03 09:45:00+00 | 2026-01-03 09:45:03+00 | {"device":"android","campaign":null} |
| 405 | 2 | view | 2026-01-10 23:55:00+00 | 2026-01-12 02:00:00+00 | {"device":"android","campaign":"winter"} |
| 406 | 1 | checkout | 2026-01-10 23:40:00+00 | 2026-01-10 23:40:02+00 | {"device":"web","order_id":102,"items":["Bag"],"amount":25.00} |
| 407 | 4 | checkout | 2026-01-21 09:45:00+00 | 2026-01-21 09:45:03+00 | {"device":"web","order_id":106,"items":["Chair","Lamp"],"amount":80.00} |
| 408 | 3 | view | 2026-01-20 02:10:00+00 | 2026-01-20 05:30:00+00 | {"device":"ios","campaign":"spring"} |
Made-up events. Events 403, 406 and 407 are checkouts for orders 101, 102 and 106.
SELECT event_id, customer_id, event_name,
event_time, ingested_at, payload
FROM web_events
ORDER BY event_id;The queries in this chapter use PostgreSQL. The type timestamptz means timestamp with time zone. It stores the moment in UTC, and it does not keep the zone you typed.
A plain timestamp keeps the clock time and knows no zone. The session time zone is a setting of your connection. PostgreSQL shows each timestamptz in that zone, and here it is UTC.
The type jsonb stores JSON in a form that SQL can search. Where other databases use other names, the text says so.
