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
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.
List<Float> target = List.of(0.1f, 0.2f, 0.3f);
Query Top N
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
| Goal | Method | Notes |
|---|---|---|
| L2 distance | orderByL2 | General nearest-neighbor search. |
| Cosine distance | orderByCosine | Common for text semantic vectors. |
| Inner product | orderByIP | Common for recommendation and ranking. |
| Runtime metric | orderByMetric | Metric comes from configuration or runtime input. |
MetricType metric = MetricType.COSINE;
List<ProductVector> rows = lambda.query(ProductVector.class)
.orderByMetric(metric, ProductVector::getEmbedding, target)
.initPage(10, 0)
.queryForList();
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.
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
- Vector Type Mapping, vector fields and TypeHandler setup.
- Combined Queries, scalar predicates with vector ordering.
- Builder Query, LambdaTemplate query basics.