Skip to main content
Applies to: Embedded ❌, Enterprise ✅ Every other curation task on this page keeps the row count the same or smaller: profiling, sampling, joining, deduplicating. Chunking goes the other way — one source row becomes many derived rows, each carrying its parent’s columns along with it. Use it to split a document into passages for embedding, a video into clips, or an image into tiles.

Define a chunking Function

A Function that returns a list produces more than one value per input row. A chunker is a Function whose output is a list — of structs, of strings, of anything — registered like any other Function. See Create a Function for CPU, GPU and remote-service Functions, and scalar versus batch calling, if you have not registered one before. A function that splits text into fixed-size passages turns a documents table into one row per passage. Registration details, environment and dependency declarations, and how a version stays immutable once rows are filled from it, are the same for a chunker as for any other Function — register it from Python, or the same function from SQL with CREATE FUNCTION: Source: documents

Explode it in a query

Wrap the function call in UNNEST in the SELECT list, with an alias: it yields one row per element of the list the function returns, with every other selected column repeated. Reference the exploded struct’s fields through that alias in a query over this one:
A document with five 200-character passages becomes five output rows, each carrying doc_id and title from its parent. This is the fastest way to check a chunker before committing to a materialized view: run it over a few rows and inspect the output shape.

Video into frames

Chunking a blob column is the same shape, with the chunker producing timestamps rather than text spans, and a second Function pulling the actual frame for each one. videos holds raw clips in a Blob v2 column, with each row’s duration already known: Source: videos A chunker that samples one timestamp per second needs no blob access at all — it only looks at duration_seconds:
A second Function reads the blob — incrementally, through a BlobReader, rather than loading the whole clip — and returns one JPEG per call:
Compose both in one query: explode videos into one row per sampled timestamp, then call extract_frame on each row to produce its image:
A 12-second clip becomes 12 output rows, each with its own extracted JPEG. A Function in FROM position yields one row per list element the same way regardless of what the list holds; whether the second step produces a blob, a plain value, or another struct does not change the explosion.

Persist it as a materialized view

Wrap either query in CREATE MATERIALIZED VIEW to keep a real table that stays current as the source changes, instead of recomputing the explosion on every query:
Derived: chunks (1:N expansion) The view refreshes like any other materialized view: new source rows are chunked and inserted, deleted source rows drop their children, and an updated row’s old children are replaced by freshly chunked ones on the next refresh. A single UNNEST(...) — whether unnesting a Function’s list output directly, as above, or an existing list column — is the only lateral expansion a materialized view can carry, and it can’t be combined with GROUP BY, so keep aggregation or further curation in a second step over the chunked table.

Chunk, then curate

A freshly chunked table is a normal table: dedupe it to collapse near-identical chunks, sample it for a quick look, or profile it to check how many chunks each source row produced. Continue with choosing a curation task.