--- title: Querying Vectors description: Run nearest-neighbor vector search inside the ParadeDB index canonical: https://www.paradedb.com/docs/reference/vector/querying --- This is a beta feature available in versions `0.25.0` and above. See [How Vector Search Works](/concepts/vector/overview) for background, and [Indexing Vectors](/reference/indexing/indexing-vectors) to set up the index used in these examples. Vector search returns the rows whose embeddings are closest to a query vector. In ParadeDB, this is an `ORDER BY ... LIMIT k` query over a vector column in the ParadeDB index. When one of ParadeDB's [search operators](/reference/filtering/indexed) is present at the same level as the `ORDER BY ... LIMIT`, ParadeDB can accelerate the vector search query. ## Unfiltered Nearest Neighbors When you are not filtering, use `pdb.all()`, which matches every row: ```sql SQL SELECT id, description FROM mock_items WHERE id @@@ pdb.all() ORDER BY embedding <=> '[1, 2, 3, 4, 5, 6, 7, 8]' LIMIT 5; ``` ```ts Drizzle import { search } from "@paradedb/drizzle-paradedb"; const queryEmbedding = [1, 2, 3, 4, 5, 6, 7, 8]; await db .select({ id: mockItems.id, description: mockItems.description }) .from(mockItems) .where(search.all(mockItems.id)) .orderBy(search.cosineDistance(mockItems.embedding, queryEmbedding)) .limit(5); ``` ```python Django from paradedb import All, ParadeDB from paradedb.vector import CosineDistance query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] MockItem.objects.filter( id=ParadeDB(All()) ).order_by( CosineDistance('embedding', query_embedding) ).values('id', 'description')[:5] ``` ```python SQLAlchemy from sqlalchemy import select from sqlalchemy.orm import Session from paradedb.sqlalchemy import search, vector query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] stmt = ( select(MockItem.id, MockItem.description) .where(search.all(MockItem.id)) .order_by(vector.cosine_distance(MockItem.embedding, query_embedding)) .limit(5) ) with Session(engine) as session: session.execute(stmt).all() ``` ```ruby Rails query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] MockItem.nearest(:embedding, query_embedding, metric: :cosine) .select(:id, :description) .limit(5) ``` ```cs EF Core var queryEmbedding = new float[] { 1, 2, 3, 4, 5, 6, 7, 8 }; await dbContext .MockItems.Where(item => EF.Functions.All(item.Id)) .OrderBy(item => EF.Functions.CosineDistance(item.Embedding, queryEmbedding)) .Select(item => new { item.Id, item.Description }) .Take(5) .ToListAsync(); ``` Rails' `nearest` adds the match-all predicate automatically when the relation has no other search predicate, and defaults the metric to the one the index was built with. The `<=>` operator computes cosine distance, so this returns the five rows whose embeddings are nearest the query vector `[1, 2, 3, 4, 5, 6, 7, 8]`. This operator must match the distance metric used by the index. Otherwise, ParadeDB cannot use the index to order results and falls back to a slower brute force sort. | Operator | Distance | Index setting | | -------- | ------------- | ------------------- | | `<->` | L2 | `vector_l2_ops` | | `<=>` | Cosine | `vector_cosine_ops` | | `<#>` | Inner product | `vector_ip_ops` | ## Filtered Nearest Neighbors To search within a subset of rows, replace `pdb.all()` with a real predicate using any of the [search operators](/reference/filtering/indexed#non-text-columns). For instance, the following query finds the nearest neighbors among results whose `category` contains the term `footwear`. Note the lowercase `footwear` — the default tokenizer lowercases terms, so `Footwear` would not match. ```sql SQL SELECT id, description FROM mock_items WHERE category === 'footwear' ORDER BY embedding <=> '[1, 2, 3, 4, 5, 6, 7, 8]' LIMIT 5; ``` ```ts Drizzle import { search } from "@paradedb/drizzle-paradedb"; const queryEmbedding = [1, 2, 3, 4, 5, 6, 7, 8]; await db .select({ id: mockItems.id, description: mockItems.description }) .from(mockItems) .where(search.term(mockItems.category, "footwear")) .orderBy(search.cosineDistance(mockItems.embedding, queryEmbedding)) .limit(5); ``` ```python Django from paradedb import ParadeDB, Term from paradedb.vector import CosineDistance query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] MockItem.objects.filter( category=ParadeDB(Term('footwear')) ).order_by( CosineDistance('embedding', query_embedding) ).values('id', 'description')[:5] ``` ```python SQLAlchemy from sqlalchemy import select from sqlalchemy.orm import Session from paradedb.sqlalchemy import search, vector query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] stmt = ( select(MockItem.id, MockItem.description) .where(search.term(MockItem.category, "footwear")) .order_by(vector.cosine_distance(MockItem.embedding, query_embedding)) .limit(5) ) with Session(engine) as session: session.execute(stmt).all() ``` ```ruby Rails query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] MockItem.search(:category) .term("footwear") .nearest(:embedding, query_embedding, metric: :cosine) .select(:id, :description) .limit(5) ``` ```cs EF Core var queryEmbedding = new float[] { 1, 2, 3, 4, 5, 6, 7, 8 }; await dbContext .MockItems.Where(item => EF.Functions.Term(item.Category, "footwear")) .OrderBy(item => EF.Functions.CosineDistance(item.Embedding, queryEmbedding)) .Select(item => new { item.Id, item.Description }) .Take(5) .ToListAsync(); ``` Every column you filter on (e.g. `category` above) must also be part of the ParadeDB index. Filters on unindexed columns cannot be evaluated by the ParadeDB index, forcing Postgres to recheck them afterward and eliminating the benefit of combining vector search with [filtering](/reference/filtering/overview). ## Deterministic Results Tiebreaker columns require version `0.25.1` and above. Rows whose embeddings are equidistant from the query vector are returned in an arbitrary order. When the `LIMIT` cuts through such a group, which of the tied rows come back can change between runs. Add a tiebreaker column after the distance to make the order stable: ```sql SQL SELECT id, description FROM mock_items WHERE id @@@ pdb.all() ORDER BY embedding <=> '[1, 2, 3, 4, 5, 6, 7, 8]', id LIMIT 5; ``` ```ts Drizzle import { search } from "@paradedb/drizzle-paradedb"; const queryEmbedding = [1, 2, 3, 4, 5, 6, 7, 8]; await db .select({ id: mockItems.id, description: mockItems.description }) .from(mockItems) .where(search.all(mockItems.id)) .orderBy( search.cosineDistance(mockItems.embedding, queryEmbedding), mockItems.id, ) .limit(5); ``` ```python Django from paradedb import All, ParadeDB from paradedb.vector import CosineDistance query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] MockItem.objects.filter( id=ParadeDB(All()) ).order_by( CosineDistance('embedding', query_embedding), 'id' ).values('id', 'description')[:5] ``` ```python SQLAlchemy from sqlalchemy import select from sqlalchemy.orm import Session from paradedb.sqlalchemy import search, vector query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] stmt = ( select(MockItem.id, MockItem.description) .where(search.all(MockItem.id)) .order_by(vector.cosine_distance(MockItem.embedding, query_embedding), MockItem.id) .limit(5) ) with Session(engine) as session: session.execute(stmt).all() ``` ```ruby Rails query_embedding = [1, 2, 3, 4, 5, 6, 7, 8] MockItem.nearest(:embedding, query_embedding, metric: :cosine) .order(:id) .select(:id, :description) .limit(5) ``` ```cs EF Core var queryEmbedding = new float[] { 1, 2, 3, 4, 5, 6, 7, 8 }; await dbContext .MockItems.Where(item => EF.Functions.All(item.Id)) .OrderBy(item => EF.Functions.CosineDistance(item.Embedding, queryEmbedding)) .ThenBy(item => item.Id) .Select(item => new { item.Id, item.Description }) .Take(5) .ToListAsync(); ``` The tiebreaker is applied only within a group of equal distances, so it never changes which rows are nearest. Multiple tiebreakers and `DESC` are both supported, and every tiebreaker column must be in the ParadeDB index to keep the Top K optimization. ## Verifying Pushdown Use `EXPLAIN` to confirm ParadeDB is accelerating the vector search. Look for a `Custom Scan` with an `Exec Method` of `TopKScanExecState` in the query plan: ```sql EXPLAIN SELECT id, description FROM mock_items WHERE category === 'footwear' ORDER BY embedding <=> '[1, 2, 3, 4, 5, 6, 7, 8]' LIMIT 5; ``` ```csv QUERY PLAN ------------------------------------------------------------------------ Limit -> Custom Scan (ParadeDB Base Scan) on mock_items Table: mock_items Index: search_idx Exec Method: TopKScanExecState Scores: false TopK Order By: embedding <=> vector asc TopK Limit: 5 Tantivy Query: {"with_index":{"query":{"term":{"field":"category","value":"footwear"}}}} ``` Notice that the `category === 'footwear'` filter was pushed down into the same index scan that performs the vector search. Without pushdown, the filter would run as a separate step after the nearest neighbors were fetched. If you do not see a `TopKScanExecState`, the query fell back to a less efficient sort. This usually means the distance operator does not match how the vector column was indexed, the query is missing a `LIMIT`, or an `ORDER BY` or filter column is not indexed.