Skip to main content

KNN Ordering

KNN ordering answers "find the N most similar records". dbVisitor uses orderBy* to generate vector-distance ordering, usually combined with initPage to limit the result count.

Suitable For

  • Search the N most similar articles, products, images, or knowledge chunks.
  • The result count is fixed, such as Top 5 or Top 10.
  • Scalar predicates narrow the candidates before vector-distance ordering.

Not Suitable For

  • The query needs all records below a distance threshold; use Distance Range Filtering.
  • Complex reranking from multiple retrieval scores is required; use JdbcTemplate.
  • The data source does not support vector ordering SQL.

KNN Query Pattern

KNN query
All records or candidate records
|
| Compute distance between embedding and query vector
v
Sort by distance ascending
|
| initPage(N, 0)
v
Return the N most similar records

orderBy* is emitted in the SQL ORDER BY part. It does not decide the candidate set; ordinary WHERE predicates decide that.

Build Query Vector

orderBy* converts vector arguments with the field mapping TypeHandler. After a PostgreSQL entity field is configured with PgVectorTypeHandler, pass List<Float> directly.

pgvector query argument
List<Float> target = List.of(0.1f, 0.2f, 0.3f);

Query Top N

Top-K query
List<ProductVector> rows = lambda.query(ProductVector.class)
.orderByL2(ProductVector::getEmbedding, target)
.initPage(5, 0)
.queryForList();

SQL shape with pgvector:

SELECT * FROM product_vector
ORDER BY embedding <-> ? ASC
LIMIT 5

initPage(5, 0) returns only the first 5 results. On PostgreSQL, omitting LIMIT sorts matching records without bounding their count. Milvus uses a search iterator to read ordinary KNN results on demand when LIMIT is omitted; this is not a fixed Top-K query. Hybrid Search requires an explicit LIMIT.

Choose A Metric

GoalMethodNotes
L2 distanceorderByL2General nearest-neighbor search.
Cosine distanceorderByCosineCommon for text semantic vectors.
Inner productorderByIPCommon for recommendation and ranking.
Runtime metricorderByMetricMetric comes from configuration or runtime input.
Choose metric at runtime
MetricType metric = MetricType.COSINE;

List<ProductVector> rows = lambda.query(ProductVector.class)
.orderByMetric(metric, ProductVector::getEmbedding, target)
.initPage(10, 0)
.queryForList();
Inner Product

pgvector's <#> operator returns the negative inner product. With orderByIP ascending order, records with larger inner product rank first.

Compose With Scalar Predicates

KNN query commonly narrows candidates with business fields before vector ordering.

Top-K within a category
List<ProductVector> rows = lambda.query(ProductVector.class)
.eq(ProductVector::getCategory, "book")
.orderByCosine(ProductVector::getEmbedding, target)
.initPage(10, 0)
.queryForList();

SQL shape:

SELECT * FROM product_vector
WHERE category = ?
ORDER BY embedding <=> ? ASC
LIMIT 10

Common Questions

How to bind vectors without entity mapping

queryFreedom, Map mode, and fields without a vector TypeHandler have no field conversion rule to reuse. Use SqlArg to specify a TypeHandler explicitly, or pass a vector object recognized by the database driver.

When initPage is needed

KNN usually expects a fixed number of nearest-neighbor results. Without initPage, PostgreSQL has no Top-K bound; Milvus also allows unbounded ordinary KNN iteration. Explicitly set the number of neighbors required by the application.

Further Reading