Skip to content
API ReferenceZilliz CloudMilvusAttu

Data Operations - Query & Search

This guide covers querying and searching data in Milvus collections.

Query retrieves entities based on filter expressions.

Query entities with a filter expression:

const results = await client.query({
collection_name: 'my_collection',
expr: 'age > 25',
output_fields: ['id', 'vector', 'text'],
});

Query specific entities by their IDs:

const results = await client.query({
collection_name: 'my_collection',
ids: [1, 2, 3, 4, 5],
output_fields: ['id', 'vector', 'text'],
});
const results = await client.query({
collection_name: 'my_collection',
expr: 'age > 25',
output_fields: ['id', 'text', 'price'],
limit: 10,
offset: 0,
order_by_fields: [{ field: 'price', order: 'desc' }],
});

order_by_fields can be a string, an array of strings, or an array of { field, order } objects:

await client.query({
collection_name: 'my_collection',
expr: 'age > 25',
output_fields: ['id', 'price', 'rating'],
order_by_fields: 'price:asc,rating:desc',
});
await client.query({
collection_name: 'my_collection',
expr: 'age > 25',
output_fields: ['id', 'price', 'rating'],
order_by: ['price:asc', { field: 'rating', order: 'desc' }], // alias
});
const results = await client.query({
collection_name: 'my_collection',
expr: 'age > 25',
partition_names: ['partition_1', 'partition_2'],
output_fields: ['id', 'text'],
});

Iterate through large result sets:

const iterator = await client.queryIterator({
collection_name: 'my_collection',
expr: 'age > 25',
output_fields: ['id', 'text'],
batchSize: 100,
});
while (true) {
const batch = await iterator.next();
if (batch.done) break;
console.log('Batch:', batch.value);
}

Get entities by IDs (simpler than query):

const results = await client.get({
collection_name: 'my_collection',
ids: [1, 2, 3],
output_fields: ['id', 'vector', 'text'],
});

Count entities matching a filter:

const result = await client.count({
collection_name: 'my_collection',
expr: 'age > 25',
});
console.log('Count:', result.data);
Milvus 3.0

Put aggregation expressions in output_fields. Add group_by_fields when you want one result row per scalar group.

const result = await client.query({
collection_name: 'sales',
filter: 'price > 0',
group_by_fields: ['category'],
output_fields: [
'category',
'count(price)',
'min(price)',
'max(price)',
'sum(price)',
'avg(price)',
],
limit: 20,
});
result.data.forEach((row) => {
console.log(row.category, row['count(price)'], row['avg(price)']);
});

The supported functions are count, min, max, sum, and avg. Without group_by_fields, Milvus returns collection-level aggregation values.

Search finds similar vectors using distance metrics.

const results = await client.search({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]], // Search vector
limit: 10,
output_fields: ['id', 'text'],
});
Milvus 3.0

Search aggregation groups the candidate set into buckets. Each bucket can include metrics, sorted top hits, and nested sub-aggregations.

const results = await client.search({
collection_name: 'products',
anns_field: 'embedding',
data: [0.1, 0.2, 0.3, 0.4],
limit: 100,
filter: 'in_stock == true',
output_fields: ['category', 'brand', 'price'],
search_aggregation: {
fields: ['category'],
size: 5,
metrics: {
item_count: { op: 'count', field_name: '*' },
average_price: { op: 'avg', field_name: 'price' },
},
order: [{ key: 'average_price', direction: 'desc' }],
top_hits: {
size: 2,
sort: [{ field_name: 'price', direction: 'asc' }],
},
sub_aggregation: {
fields: ['brand'],
size: 3,
},
},
});
console.log(results.agg_buckets);

For one query vector, agg_buckets is an AggregationBucket[]. For multiple query vectors, it is an AggregationBucket[][]. Aggregation searches return their grouped data through agg_buckets instead of regular hit rows. Search aggregation is not available for hybrid search and cannot be combined with highlighters, grouping, or a positive offset.

perform a vector similarity search using the vectors associated with exact entity IDs:

const results = await client.search({
collection_name: 'my_collection',
ids: [1, 2, 3], // primary keys
limit: 10,
output_fields: ['id', 'text'],
});

[!NOTE]

  • When ids are provided, the data parameter (dummy vectors) is not required.
  • The provided ids must match the collection’s Primary Key type (Int64 or VarChar).
  • For collections with multiple vector fields, explicitly specifying anns_field is recommended (and required to disambiguate).
const results = await client.search({
collection_name: 'my_collection',
data: [
[0.1, 0.2, 0.3, 0.4],
[0.5, 0.6, 0.7, 0.8],
],
limit: 10,
output_fields: ['id', 'text'],
});

Combine vector search with scalar filters:

const results = await client.search({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]],
expr: 'age > 25 AND category == "tech"',
limit: 10,
output_fields: ['id', 'text', 'age'],
});

Sort search results by scalar fields after vector retrieval:

const results = await client.search({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]],
limit: 10,
output_fields: ['id', 'price', 'rating'],
order_by_fields: [
{ field: 'price', order: 'asc' },
{ field: 'rating', order: 'desc' },
],
});

You can also pass order_by_fields as a string, for example 'price:asc,rating:desc'. If you also pass params.order_by_fields, the top-level order_by_fields value takes precedence.

Configure search parameters for different index types:

// For IVF indexes
const results = await client.search({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]],
params: {
nprobe: 64, // Number of clusters to search
},
limit: 10,
});
// For HNSW indexes
const results = await client.search({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]],
params: {
ef: 100, // Search width
},
limit: 10,
});
const results = await client.search({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]],
partition_names: ['partition_1'],
limit: 10,
});

Iterate through large search results:

const iterator = await client.searchIterator({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]],
expr: 'age > 25',
limit: 1000,
batchSize: 100,
});
while (true) {
const batch = await iterator.next();
if (batch.done) break;
console.log('Batch:', batch.value);
}

Search with multiple vector fields:

const results = await client.hybridSearch({
collection_name: 'my_collection',
reqs: [
{
data: [[0.1, 0.2, 0.3, 0.4]],
anns_field: 'text_vector',
param: { nprobe: 64 },
limit: 10,
},
{
data: [[0.5, 0.6, 0.7, 0.8]],
anns_field: 'image_vector',
param: { nprobe: 64 },
limit: 10,
},
],
ranker: 'rrf', // Reciprocal Rank Fusion
limit: 10,
output_fields: ['id', 'text'],
});

Filter expressions use Milvus expression syntax:

'age > 25';
'age >= 25';
'age < 25';
'age <= 25';
'age == 25';
'age != 25';
'age > 25 AND category == "tech"';
'age > 25 OR age < 18';
'NOT (age > 25)';
'id in [1, 2, 3, 4, 5]';
'category in ["tech", "science"]';
'text like "milvus%"';
'text like "%database%"';
'metadata.category == "tech"';
'metadata.tags[0] == "ai"';
Milvus 3.0

Use element_filter and $[...] to apply predicates to fields in the same StructArray element:

'element_filter(profile, $[score] >= 90 AND $[active] == true)';

Select child fields with bracketed paths such as profile[score] and profile[tag]. Element-level query and search results include an offset identifying the matching array element.

Regex, Raw Strings, and Bitwise Operators Milvus 3.0

Section titled “Regex, Raw Strings, and Bitwise Operators Milvus 3.0”

These expressions are passed through to the Milvus expression parser:

// Regular expression match and negative match
'name =~ "^milvus-[0-9]+$"';
'name !~ "-archived$"';
// Raw string literal for patterns with backslashes
'path == r"C:\\data\\vectors"';
// Bitwise AND, OR, and XOR
'(permissions & 4) != 0';
'(flags | 2) == 3';
'(mask ^ 1) == 0';

Search results include:

const results = await client.search({
collection_name: 'my_collection',
data: [[0.1, 0.2, 0.3, 0.4]],
limit: 10,
});
results.results.forEach((result) => {
console.log('ID:', result.id);
console.log('Distance:', result.distance);
console.log('Score:', result.score);
console.log('Entity:', result.entity);
});

For element-level retrieval, Milvus can return the matching element offsets for array or multi-vector fields. The SDK flattens element_indices into result rows and adds an offset property that identifies the matched element within the source field.

const results = await client.search({
collection_name: 'chunked_docs',
data: [
[
/* query vector */
],
],
anns_field: 'chunk_embeddings',
limit: 10,
output_fields: ['doc_id', 'title'],
});
results.results.forEach((row) => {
console.log('Document:', row.doc_id);
console.log('Matched element offset:', row.offset);
});

Grouped search responses can also include group_by_field_values, which the SDK exposes on each result row:

results.results.forEach((row) => {
console.log('Score:', row.score);
console.log('Group by values:', row.group_by_field_values);
});

Specify which fields to return:

// Return specific fields
output_fields: ['id', 'text', 'age'];
// Return all fields
output_fields: ['*'];
// Return vector fields
output_fields: ['id', 'vector'];

IVF indexes:

  • nprobe: Higher values improve recall but slower (typical: 16-256)

HNSW indexes:

  • ef: Higher values improve recall but slower (typical: 50-200)

Search multiple vectors efficiently:

const results = await client.search({
collection_name: 'my_collection',
data: [
[
/* vector 1 */
],
[
/* vector 2 */
],
[
/* vector 3 */
],
],
limit: 10,
});

Load collections before searching:

await client.loadCollectionSync({
collection_name: 'my_collection',
});
// Now search
const results = await client.search({
/* ... */
});
  1. Use appropriate limits: Set reasonable limits to avoid large result sets
  2. Filter before search: Use filter expressions to narrow down results
  3. Tune search parameters: Adjust nprobe/ef based on your recall/performance needs
  4. Use iterators: For large result sets, use query/search iterators
  5. Load collections: Always load collections before searching
  6. Batch operations: Search multiple vectors in one call when possible
import { MilvusClient, DataType } from '@zilliz/milvus2-sdk-node';
const client = new MilvusClient({
address: 'localhost:19530',
});
await client.connectPromise;
// Load collection
await client.loadCollectionSync({
collection_name: 'my_collection',
});
// Query
const queryResults = await client.query({
collection_name: 'my_collection',
expr: 'age > 25',
output_fields: ['id', 'text', 'age'],
limit: 10,
});
console.log('Query results:', queryResults.data);
// Search
const searchResults = await client.search({
collection_name: 'my_collection',
data: [Array.from({ length: 128 }, () => Math.random())],
expr: 'age > 25',
limit: 10,
output_fields: ['id', 'text'],
params: {
nprobe: 64,
},
});
console.log('Search results:', searchResults.results);