How to Query with Vector Search

Last verified 13 Jul 2026

DigitalOcean Managed PostgreSQL for vector search uses the same managed PostgreSQL engine available under Managed Databases, with the pgvector and pgvectorscale extensions for storing and querying vector embeddings alongside relational data.

After you index vectors, you can query them with vector similarity search. PostgreSQL returns the nearest vectors to your query vector and sorts the results by distance. To combine keyword relevance with vector similarity, see Query with Hybrid Search.

Make sure you have set your environment variables and verified the connection.

To run queries, first go to the Vector Databases page and select the cluster you want to use.

Vector Databases overview page listing OpenSearch and PostgreSQL vector database clusters.

Then, go to the Network Access tab, find the trusted source you want to connect to, and then open a terminal session.

Network Access tab showing trusted source entries and an Add Trusted Sources button.

Run a Vector Search Query

Use ORDER BY with a pgvector distance operator to search a vector column for the nearest neighbors to a query vector.

SELECT
    id,
    title,
    source,
    1 - (embedding <=> '[0.013, -0.041, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.013, -0.041, ...]'::vector
LIMIT 5;
import os
import psycopg

conninfo = (
    f"host={os.environ['PGHOST']} "
    f"port={os.environ.get('PGPORT', '25060')} "
    f"user={os.environ['PGUSER']} "
    f"password={os.environ['PGPASSWORD']} "
    f"dbname={os.environ['PGDATABASE']} "
    f"sslmode={os.environ.get('PGSSLMODE', 'require')}"
)

query_vector = "[0.013, -0.041, ...]"

with psycopg.connect(conninfo) as conn:
    with conn.cursor() as cur:
        cur.execute(
            """
            SELECT
                id,
                title,
                source,
                1 - (embedding <=> %s::vector) AS similarity
            FROM documents
            ORDER BY embedding <=> %s::vector
            LIMIT 5;
            """,
            (query_vector, query_vector),
        )

        for row in cur.fetchall():
            print(row)

PostgreSQL returns rows sorted by distance. With cosine distance, lower distance means closer vectors. The example calculates 1 - distance as a similarity score so higher values are better.

Use the distance operator that matches your vector index operator class:

  • Use <=> with vector_cosine_ops for cosine distance.
  • Use <#> with vector_ip_ops for inner product distance.
  • Use <-> with vector_l2_ops for L2 distance.
  • Use <+> with vector_l1_ops for L1 distance.

Keep the LIMIT value close to the number of results you need. Vector indexes use the ORDER BY embedding <operator> query_vector LIMIT n pattern.

Your query vector must match the dimension of the table’s vector column. For example, a vector(1536) column requires a query vector with 1536 dimensions.

Filter Vector Search Results

Use filters when you need to restrict vector search by metadata, such as source, account, permissions, date, language, or document type.

SELECT
    id,
    title,
    source,
    created_at,
    1 - (embedding <=> '[0.013, -0.041, ...]'::vector) AS similarity
FROM documents
WHERE source = 'blog'
  AND created_at >= now() - interval '30 days'
ORDER BY embedding <=> '[0.013, -0.041, ...]'::vector
LIMIT 10;
query_vector = "[0.013, -0.041, ...]"

with psycopg.connect(conninfo) as conn:
    with conn.cursor() as cur:
        cur.execute(
            """
            SELECT
                id,
                title,
                source,
                created_at,
                1 - (embedding <=> %s::vector) AS similarity
            FROM documents
            WHERE source = %s
              AND created_at >= now() - interval '30 days'
            ORDER BY embedding <=> %s::vector
            LIMIT 10;
            """,
            (query_vector, "blog", query_vector),
        )

        for row in cur.fetchall():
            print(row)

PostgreSQL applies the SQL filter and sorts the matching rows by vector distance. For filtered workloads, add supporting indexes on commonly filtered columns such as source, tenant_id, created_at, or document type.

If you use a partial vector index, make sure the query includes the same predicate used in the index definition.

Use exact vector search when you need exact recall or when filters reduce the candidate set to a small number of rows. Exact search scans matching rows and computes the distance exactly, so it is simpler but slower on large datasets.

To run exact vector search, use the same ORDER BY pattern without relying on an approximate index:

SELECT
    id,
    title,
    source,
    1 - (embedding <=> '[0.013, -0.041, ...]'::vector) AS similarity
FROM documents
WHERE source = 'internal-wiki'
ORDER BY embedding <=> '[0.013, -0.041, ...]'::vector
LIMIT 10;
query_vector = "[0.013, -0.041, ...]"

with psycopg.connect(conninfo) as conn:
    with conn.cursor() as cur:
        cur.execute(
            """
            SELECT
                id,
                title,
                source,
                1 - (embedding <=> %s::vector) AS similarity
            FROM documents
            WHERE source = %s
            ORDER BY embedding <=> %s::vector
            LIMIT 10;
            """,
            (query_vector, "internal-wiki", query_vector),
        )

        for row in cur.fetchall():
            print(row)

Exact search is useful for small tables, small filtered candidate sets, or validation tests where you want to compare approximate index results against exact results.

Rerank Candidate Results

For higher precision, retrieve a wider candidate set with the vector index, then rerank those candidates with another score.

For example, the following query retrieves 50 candidates using cosine distance, then reranks those candidates using inner product distance:

WITH candidates AS (
    SELECT
        id,
        title,
        source,
        embedding
    FROM documents
    ORDER BY embedding <=> '[0.013, -0.041, ...]'::vector
    LIMIT 50
)
SELECT
    id,
    title,
    source,
    embedding <#> '[0.013, -0.041, ...]'::vector AS score
FROM candidates
ORDER BY embedding <#> '[0.013, -0.041, ...]'::vector
LIMIT 10;

This pattern is useful when you want fast candidate retrieval and more precise final ranking. Keep the candidate set large enough for the reranker to improve result quality, but small enough to avoid unnecessary latency.

Tune HNSW Search

Use hnsw.ef_search to tune recall and query latency at query time. Higher values usually improve recall but increase query latency.

Set hnsw.ef_search before running the query:

SET hnsw.ef_search = 100;

SELECT
    id,
    title,
    source,
    1 - (embedding <=> '[0.013, -0.041, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.013, -0.041, ...]'::vector
LIMIT 10;

Tune ef_search with representative queries and known relevant results. Increase it when recall is too low. Decrease it when latency is too high.

Tune IVFFlat Search

Use ivfflat.probes to control how many lists PostgreSQL scans at query time. Higher values usually improve recall but increase query latency.

Set ivfflat.probes before running the query:

SET ivfflat.probes = 10;

SELECT
    id,
    title,
    source,
    1 - (embedding <=> '[0.013, -0.041, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.013, -0.041, ...]'::vector
LIMIT 10;

A common starting point is the square root of the lists value. For example, if the index uses lists = 100, start with ivfflat.probes = 10.

Tune ivfflat.probes with representative queries and known relevant results. Increase it when recall is too low. Decrease it when latency is too high.

Check Query Plans

Use EXPLAIN (ANALYZE, BUFFERS) to confirm that PostgreSQL uses the vector index:

EXPLAIN (ANALYZE, BUFFERS)
SELECT
    id,
    title,
    source,
    1 - (embedding <=> '[0.013, -0.041, ...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.013, -0.041, ...]'::vector
LIMIT 10;

If the plan shows a sequential scan, check that:

  • The query uses ORDER BY embedding <operator> query_vector.
  • The operator matches the index operator class.
  • The query includes a LIMIT.
  • The table has enough rows for PostgreSQL to prefer the index.
  • You have run ANALYZE after large inserts or updates.

After you query vectors, tune your indexes and query settings with representative queries and expected results.

We can't find any results for your search.

Try using different keywords or simplifying your search terms.