Skip to main content
Applies to: Embedded ✅, Enterprise ✅ A filter restricts results to rows that satisfy a condition. Use it on its own to select records, or combine it with vector, full-text or hybrid search. For example, search for similar documents only within a particular category.
These examples use three sample records and hand-written vectors, so no embedding service is needed. Each program creates a temporary local table and removes it when finished.
  • Python: Install with pip install lancedb, save as filtered_search.py, then run python filtered_search.py.
  • TypeScript: Install with npm install @lancedb/lancedb, save as filtered_search.mts, then run node filtered_search.mts with Node.js 22.18 or later.
  • Rust: Download and extract the complete Rust examples, then run cargo run --locked --bin filter_search from the extracted directory.
The query vector is closest to the gardening record, but the filter excludes that category. The results contain only the two animal records.

Prefiltering and postfiltering

  • Prefiltering, the default, limits the rows eligible for retrieval before search chooses its candidates.
  • Postfiltering removes nonmatching rows after candidates are selected. It can return fewer than limit rows, including zero, even when matching records exist elsewhere in the table.
Both modes exclude rows that fail the predicate. They differ in which matching rows the search can find. On hybrid queries, the filter applies to both the vector and full-text searches. SDK filters use SQL expressions without requiring a SQL connection. For Enterprise SQL queries, see SELECT.

Filtering examples by language

The following excerpts use a connected db handle. Create this sample table before running the queries beneath it; each query uses the same four records.

Combine predicates

Repeated filter calls combine predicates with logical AND. For example, where("a > 0").where("b < 10") is equivalent to where("(a > 0) AND (b < 10)"). Use a single expression for a fixed filter. Chain calls when your application builds the filter incrementally.
In Python SDK versions before 0.34.0 and Rust/TypeScript SDK versions before 0.31.0, a second where(...) or only_if(...) call replaced the first filter, so only the last predicate was applied. If your code needs to run on those older versions, write a single predicate with AND instead of chaining calls. When upgrading to the latest SDKs, review any existing chained filters and drop earlier calls you no longer want to apply.
You can always filter your data without search. This is useful when you need to query based on metadata:
Set limit() explicitly to control how many matching records your application receives.

Post-Filtering with Vector Search

Postfiltering can return fewer than the requested number of records because the predicate removes candidates after search.

Write SQL predicates

Pass a SQL expression as the filter string. These expressions also select rows for SDK update and delete operations; they are not standalone SQL statements. LanceDB supports a growing list of SQL expressions:

Simple SQL Filters

For example, the following filter string is acceptable:

Advanced SQL Filters

If your column name contains special characters, upper-case characters, or is a SQL Keyword, you can use backtick (`) to escape it. For nested fields, each segment of the path must be wrapped in backticks.
Periods separate nested fields. LanceDB does not allow a literal period in a top-level column name; backticks do not remove that restriction.

Dates, Timestamps, Decimals

Literals for dates, timestamps, and decimals can be written by writing the string value after the type name. For example:
For timestamp columns, the precision can be specified as a number in the type parameter. Microsecond precision (6) is the default.

Apache Arrow Mapping

LanceDB uses Arrow types for schemas and in-memory data. On disk, tables use the Lance format. The mapping from SQL types to Arrow types is:

Speed up frequently used filters

Consider a scalar index on frequently filtered columns. Use the query plan to check whether a predicate uses that index. Functions or casts applied to a column can change which optimizations are available. For list columns, a label-list index supports membership filters such as array_has_any and array_has_all.

Understand missing matches

A restrictive predicate can legitimately leave fewer rows than the requested limit. Approximate vector search can also miss qualifying neighbors, and postfiltering can discard candidates without retrieving replacements. For a recall comparison, run the same query and predicate while bypassing the vector index on a suitably sized test table. Then compare the indexed query’s results and latency while changing probe or refinement settings. Increasing those settings does not guarantee a full result set. See vector search and query plans.