Skip to main content
A materialized view is a table defined by a query over a source table. LanceDB records the query in the view’s schema at creation time, computes the rows when you call refresh(), and updates them incrementally as the source changes. Use a materialized view when you want a persisted, queryable projection of a source table, for example a filtered subset, a set of derived columns, or a capped sample. Once refreshed, the view is a normal table, so you can search, index, and scan it like any other.
Materialized views are available in the LanceDB Python and TypeScript clients on local databases. Remote (db://) connections raise the core’s not-supported error up front (Python: NotImplementedError).
This page covers the OSS materialized-view API on a plain LanceDB connection. If you are looking for Geneva’s UDF-driven materialized views used to backfill expensive columns, see Materialized views with UDFs.

Prerequisites

The source table must have stable row IDs. LanceDB uses them to track which source rows a view has already materialized, so incremental refresh can survive source compactions. Enable stable row IDs when the source table is created. Stable row IDs cannot be enabled on a table that already exists.

Create a view

Call create_materialized_view (Python) or createMaterializedView (TypeScript) on the connection.
  • select accepts column names, (alias, SQL expression) pairs, or a dict / record of the same. A bare column name is quoted as an identifier, so column names with spaces or reserved words work. Omit select to project every source column.
  • where is a SQL predicate. Only matching source rows appear in the view.
  • limit caps the view at that many rows.
The view is created empty. Its query is recorded in the view’s schema metadata, so reopening the view later does not require any side channel.

Refresh the view

refresh() computes the view from its source. LanceDB picks between two modes:
  • Incremental: apply only the source rows added, changed, or removed since the last refresh. Chosen when the source’s changes can be reconciled into the view.
  • Rebuild: recompute the view from scratch. Chosen on the first refresh or when the source has changed in ways incremental refresh cannot reconcile (for example, an update on legacy-storage data).
refresh() returns a RefreshMaterializedViewResult with: Force a full rebuild by passing full=True (Python) or { full: true } (TypeScript). Refresh against a specific source version with source_version= or { sourceVersion: N }.
Concurrent refreshes of the same view do not duplicate rows. If two refreshes plan the same source rows, the second one to commit conflicts and raises instead of writing the rows again.

Query a view

The view’s underlying table is available as view.table in Python (a LanceTable) and view.table() in TypeScript (a Table). Query, index, and search it like any other table.
Writes to the view’s underlying table are not blocked, but a rebuild replaces them. Treat the view as read-only outside of refresh().

Open an existing view

open_materialized_view / openMaterializedView returns a handle whose definition is read back from the stored schema. Opening a table that is not a materialized view raises an error.
list_materialized_views / listMaterializedViews returns the names of every materialized view in the database. It reads every table’s schema, so it costs one open per table.

Async Python API

The same operations are available on Python’s AsyncConnection, and return AsyncMaterializedView. definition() and refresh() are coroutines. The TypeScript API is already async and matches the examples above.
Python