CHUNK_TEXT — Document Chunking
CHUNK_TEXT is a built-in table-valued function that splits text into overlapping chunks suitable for embedding and retrieval-augmented generation (RAG) pipelines. It is markdown-aware — it prefers to break at paragraph boundaries, headings, and list items rather than mid-sentence.
Syntax
Section titled “Syntax”SELECT chunk_index, chunk_text, chunk_posFROM CHUNK_TEXT( content, [max_chars], [overlap_chars], [title]);All parameters except content are optional and can be passed positionally or as named arguments.
| Parameter | Type | Default | Description |
|---|---|---|---|
content | TEXT | — | The document text to chunk (max 1 MB). |
max_chars | INT | 3600 | Target maximum chunk size (~900 tokens of English text). Zero or negative values fall back to the default. |
overlap_chars | INT | 540 | How far each chunk reaches back into the previous one. The default is a fixed 540 — it does not scale with max_chars. Must be less than max_chars; negative values fall back to the default. |
title | TEXT | NULL | Document title prepended to each chunk. |
Output Columns
Section titled “Output Columns”| Column | Type | Description |
|---|---|---|
chunk_index | INT | Zero-based chunk number. |
chunk_text | TEXT | The chunk content (with title prefix if provided). |
chunk_pos | INT | Byte offset of this chunk in the original document (equal to the character offset for ASCII text). |
Basic Usage
Section titled “Basic Usage”SELECT chunk_index, length(chunk_text) AS len, chunk_posFROM CHUNK_TEXT( content => E'DB9 is a serverless database that speaks the PostgreSQL wire protocol. Every database ships with built-in extensions for HTTP calls, vector search and file storage.\n\n' || E'## Chunking\n\n' || E'CHUNK_TEXT splits a long document into overlapping pieces. Each piece is small enough to embed, and the overlap carries context across the boundary.\n\n' || E'## Retrieval\n\n' || 'Store each chunk with its embedding, then rank chunks by vector distance to answer a question.', max_chars => 200, overlap_chars => 30); chunk_index | len | chunk_pos-------------+-----+----------- 0 | 165 | 0 1 | 193 | 135 2 | 139 | 298Chunks 0 and 1 end at the paragraph break before a heading rather than at the 200-byte limit,
and each following chunk starts 30 bytes (overlap_chars) before that break — chunk 1 starts at
135, which is chunk 0’s length of 165 minus 30. The overlap is a raw byte offset, so a chunk can
begin mid-word.
With Title
Section titled “With Title”When title is provided, it is prepended to each chunk so that embedding models have document context:
SELECT chunk_index, chunk_textFROM CHUNK_TEXT( 'Hello world. This is the document body.', title => 'My Document'); chunk_index | chunk_text-------------+-------------------------------------------------------------------- 0 | title: My Document | text: Hello world. This is the document body.Smart Splitting
Section titled “Smart Splitting”CHUNK_TEXT identifies natural break points in the text and assigns them a score based on proximity to the target chunk boundary:
- Paragraph breaks (double newlines) — strongest break point
- Markdown headings (
## ...) — strong break point - List items (
- ...,1. ...) — moderate break point - Sentence endings — fallback break point
Code fences (triple backticks) are treated as no-break zones — the function will not split inside a fenced code block.
RAG Pipeline Example
Section titled “RAG Pipeline Example”Combine CHUNK_TEXT with embedding() and HNSW indexes for a complete RAG pipeline:
-- 1. Create the documents and chunks tablesCREATE TABLE rag_documents ( id SERIAL PRIMARY KEY, title TEXT, content TEXT);
CREATE TABLE rag_chunks ( id SERIAL PRIMARY KEY, doc_id INT REFERENCES rag_documents(id), chunk_index INT, chunk_text TEXT, -- Must match the embedding model's output size: the default -- text-embedding-v4 model returns 1024 dimensions. embedding VECTOR(1024));
-- 2. Add a document to chunkINSERT INTO rag_documents (title, content)VALUES ('Branching', repeat('DB9 branching copies the full dataset asynchronously. ', 80));
-- 3. Chunk and embed that documentINSERT INTO rag_chunks (doc_id, chunk_index, chunk_text, embedding)SELECT 1, c.chunk_index, c.chunk_text, embedding(c.chunk_text)FROM CHUNK_TEXT( content => (SELECT content FROM rag_documents WHERE id = 1), max_chars => 1500, overlap_chars => 200, title => (SELECT title FROM rag_documents WHERE id = 1)) AS c;
-- 4. Create an HNSW index for fast searchCREATE INDEX ON rag_chunks USING hnsw (embedding vector_cosine_ops);
-- 5. Semantic searchSELECT chunk_text, embedding <=> embedding('How does branching work?') AS distanceFROM rag_chunksORDER BY distanceLIMIT 5;Use in CTEs
Section titled “Use in CTEs”CHUNK_TEXT works in WITH clauses for multi-step processing:
WITH raw_chunks AS ( SELECT chunk_index, chunk_text, chunk_pos FROM CHUNK_TEXT( content => 'Long document...', max_chars => 2000 ))SELECT chunk_index, length(chunk_text) AS sizeFROM raw_chunksWHERE length(chunk_text) > 100;Edge Cases
Section titled “Edge Cases”| Input | Result |
|---|---|
NULL content | Zero rows returned, whatever the other arguments. |
Empty string '' | Zero rows returned — provided overlap_chars is less than max_chars. |
Content no longer than max_chars bytes | Single chunk with the full content. |
overlap_chars ≥ max_chars — including max_chars of 540 or less with the default overlap | ERROR: internal error (XX000), for any non-NULL content. |
| Content exceeding 1 MB | ERROR: internal error (XX000). |
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Max content size | 1 MB (1,048,576 bytes) |
Default max_chars | 3,600 bytes (~900 tokens of English text) |
Default overlap_chars | 540 bytes, fixed — it does not scale with max_chars |
Min max_chars | 1 (zero or negative falls back to the default) |
Min overlap_chars | 0 (negative falls back to the default); must be less than max_chars |
See Limits and Quotas for the complete list.
Next Steps
Section titled “Next Steps”- Vector Search — Embedding and HNSW indexes
- Built-in Functions — All SQL functions
- fs9 — File System — Ingest files to chunk from the filesystem