Semi-Structured Data: Intermediate
UNNEST: Flattening Arrays to Rows
Recognize semi-structured data questions: JSON payloads, nested arrays, event properties, API responses.
The question that recurs at this depth: 'we have an event stream where the payload schema changes monthly. Design the warehouse model that lets downstream analysts query it without breaking every time the schema changes.' This is not a JSON extraction question. It is a schema evolution question with extraction as a tactic. The candidate who walks the layers is the one who has built the JSON pipeline; the candidate who writes JSON_VALUE everywhere is the one who has not.
Three design questions this lesson covers
- ▸"the event schema changes monthly; how do we keep the warehouse stable"
- ▸"the payload has 5 levels of nesting; flatten or keep nested?"
- ▸"count events where the items array has more than 3 entries"
- ▸"the new field appeared 3 weeks ago; how do we backfill"
- ▸"schema-on-read or schema-on-write for this workload"
Nested Aggregation and Reconstruction
Extract scalar values from nested JSON using dot-notation and bracket-notation path expressions.
Schema-on-read
Keep the JSON as a VARIANT/JSONB column. Downstream queries extract paths at query time. New fields appear in the JSON without any pipeline change; downstream queries that reference them start working as soon as the data is there. Old queries continue to work unchanged.
Schema-on-write
- Source schema changes faster than pipeline updates can ship
- Exploratory analytics where you don't know what fields will matter
- Write throughput matters more than read latency
- Audit raw events for replay or reprocessing later
- Schema is stable; new fields are rare events
- Read latency matters; consumers query the data many times per write
- Consumers prefer typed columns over JSON path syntax
- Compliance or audit requires explicit column-level data contracts
The bronze/silver/gold pattern
- ▸Bronze: raw JSON column (schema-on-read; absorbs evolution)
- ▸Silver: typed columns extracted from bronze (schema-on-write; stable contract)
- ▸Gold: aggregations from silver
- ▸Consumers query gold; the platform team owns silver; bronze is the source-of-truth
The cost of each consumer doing extraction
Lateral Joins Over JSON Arrays
Use UNNEST/LATERAL FLATTEN to explode array columns into rows and join back to the parent.
New field appears
Field renamed
Nested object reshapes
Schema contract testing
Typing and Casting Extracted Values
Aggregate over unnested data and reconstruct arrays/structs using ARRAY_AGG and STRUCT.
Array length without UNNEST
Array contains and any/all
Sum and aggregate over array elements
The trade-off
Missing Keys and NULL Extraction
Discuss with the interviewer when to parse JSON at query time vs materializing into typed columns during ETL.
Flatten when
Keep nested when
- Read latency is lower; no extraction at query time
- Typed columns are easier to index and optimize
- Consumer code is simpler; no JSON path syntax
- Schema changes are explicit; pipeline migrations needed
- Schema evolution is absorbed without pipeline change
- Storage is more compact; no per-column overhead
- Read pays the extraction cost per query
- Consumers need to know the JSON path syntax
The partial-flatten pattern
- ▸Extract high-traffic stable fields into typed columns
- ▸Keep the remaining payload as a JSON column
- ▸Consumers query the typed columns by default
- ▸JSON column is available for exploration; not the production path
The closing thought
> You are in a data engineering interview at an event-heavy platform. The interviewer asks: 'We have an event stream where the payload schema changes monthly. Design the warehouse model that lets analysts query it without breaking every time the schema changes.'
COALESCE(payload:device.platform_name, payload:device.platform) through a documented deprecation window; a substantive shape change needs detection-and-branch logic because bronze holds both shapes during the migration.expect_column_to_exist on every silver model are the canary for upstream schema drift; without them the drift surfaces as missing data in a dashboard.jsonb_array_length, ARRAY_SIZE, @>, ARRAY_CONTAINS) and save UNNEST for genuine per-element processing.JSON columns in SQL interviews separate data engineers from analysts
- Category
- SQL
- Difficulty
- intermediate
- Duration
- 25 minutes
- Challenges
- 0 hands-on challenges
Topics covered: UNNEST: Flattening Arrays to Rows, Nested Aggregation and Reconstruction, Lateral Joins Over JSON Arrays, Typing and Casting Extracted Values, Missing Keys and NULL Extraction
Lesson Sections
- UNNEST: Flattening Arrays to Rows (concepts: sqlJsonExtract)
Three design questions this lesson covers First: schema-on-read vs schema-on-write. Schema-on-read keeps the JSON in the bronze layer and extracts at query time; schema-on-write flattens to typed columns at ingestion. Each has a cost and a use case. Second: evolution handling. When the source schema changes, the pipeline either adapts automatically (schema-on-read absorbs the change) or breaks loudly (schema-on-write fails CI and forces a coordinated migration). Third: nested aggregation. Someti
- Nested Aggregation and Reconstruction (concepts: sqlJsonExtract)
The fundamental architectural decision for JSON pipelines: extract at query time (schema-on-read) or extract at ingest time (schema-on-write). Each is right for a different workload. Schema-on-read Schema-on-write At ingestion, extract the JSON into typed relational columns. Downstream queries reference the columns directly; no extraction syntax in consumer queries. New fields require a pipeline change (add a column, extract the new field). Schema changes are explicit: the pipeline either succee
- Lateral Joins Over JSON Arrays (concepts: sqlJsonExtract)
JSON schemas evolve. The product team adds a new event type with a new payload shape; a field gets renamed; a nested object reshapes into a flatter structure. The pipeline has to handle the evolution without breaking. This section covers the migration patterns. New field appears The product team adds device.os_version to the events payload. The bronze layer absorbs it (schema-on-read; no change needed). The silver layer doesn't know about it yet; downstream consumers can't query it until the sil
- Typing and Casting Extracted Values (concepts: sqlArrayOps)
UNNEST is the right tool when you need per-element rows. When you need per-row aggregates over an array (length, contains, sum of an array's numeric fields), UNNEST is overkill and slower. Engines provide array functions that operate on the array as a unit without flattening. Array length without UNNEST Each engine has a function that returns the array's length without flattening. The filter operates per-row; no UNNEST is needed. This is the right pattern for predicates over array properties (le
- Missing Keys and NULL Extraction (concepts: sqlJsonExtract)
The last design decision: when do you flatten a JSON column to typed columns, and when do you keep it nested? The choice depends on read patterns, schema stability, and consumer preferences. This section covers the trade-off and the closing. Flatten when Flatten the JSON column into typed columns when: the fields are stable (rarely change); consumers query them frequently (read amplification justifies the one-time extraction cost); the dashboard or BI tool prefers typed columns to JSON path synt