Add subsection about stable ordering to pagination section (#2463)

* Add 'Stable Ordering' section to 'Pagination' section

* Add FAQ entry

* Small edits to the Pagination section

* Apply title case to all headers on page

* Small edit
This commit is contained in:
Abdon Pijpelink
2026-06-30 13:07:43 +02:00
committed by GitHub
parent 708c40573d
commit cf4119fa3f
42 changed files with 526 additions and 43 deletions
@@ -183,6 +183,18 @@ Results are generally expected to be consistent for the overlapping portion. How
The time value is in seconds and represents the total duration the Qdrant server spent processing the request. It does not include network round-trip time between the client and the server.
### Why do I get duplicate results when paginating through search results?
Because HNSW is an approximate algorithm, the ranking of results can shift slightly between requests. As a result, paginating with `offset` can return the same point on multiple pages or skip points entirely. This is expected behavior, not a bug.
There are three ways to work around this:
- **Client-side pagination** — retrieve a large batch in a single request (for example, the top 100 results) and paginate through it on the client. This avoids multiple round-trips and guarantees no duplicates, at the cost of returning more data than the user sees at once.
- **Exact search** — use exact searches to bypass HNSW and scan all vectors, returning results in a stable, deterministic order. This ensures offset-based pagination works correctly. This is practical only for small collections due to higher latency.
- **Exclude seen IDs** — on each subsequent page, pass a `must_not: has_id` filter containing all point IDs from previous pages. The exclusion list grows by `limit` entries per page, so this works well for sequential, forward-only pagination but isn't practical for jumping to an arbitrary page.
See also: [Stable Ordering](/documentation/search/search/#stable-ordering)
### If `limit` is higher than `hnsw_ef`, does Qdrant automatically adjust `hnsw_ef`?
Yes. Qdrant internally sets `ef = max(ef, limit)` so that the candidate list is always at least as large as the requested result count.
@@ -0,0 +1 @@
This code snippet demonstrates how to run an exact nearest neighbor search by setting the `exact` parameter to `true`. Unlike the default approximate HNSW search, exact search scans all vectors and returns results in a stable, deterministic order.
@@ -0,0 +1,17 @@
using Qdrant.Client;
using Qdrant.Client.Grpc;
public class Snippet
{
public static async Task Run()
{
var client = new QdrantClient("localhost", 6334); // @hide
await client.QueryAsync(
collectionName: "{collection_name}",
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
searchParams: new SearchParams { Exact = true },
limit: 10
);
}
}
@@ -0,0 +1,11 @@
```csharp
using Qdrant.Client;
using Qdrant.Client.Grpc;
await client.QueryAsync(
collectionName: "{collection_name}",
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
searchParams: new SearchParams { Exact = true },
limit: 10
);
```
@@ -0,0 +1,15 @@
```go
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
Params: &qdrant.SearchParams{
Exact: qdrant.PtrOf(true),
},
})
```
@@ -0,0 +1,17 @@
```java
import static io.qdrant.client.QueryFactory.nearest;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.SearchParams;
client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName("{collection_name}")
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
.setParams(SearchParams.newBuilder().setExact(true).build())
.setLimit(10)
.build())
.get();
```
@@ -0,0 +1,10 @@
```python
from qdrant_client import QdrantClient, models
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
search_params=models.SearchParams(exact=True),
limit=10,
)
```
@@ -0,0 +1,13 @@
```rust
use qdrant_client::qdrant::{QueryPointsBuilder, SearchParamsBuilder};
use qdrant_client::Qdrant;
client
.query(
QueryPointsBuilder::new("{collection_name}")
.query(vec![0.2, 0.1, 0.9, 0.7])
.limit(10)
.params(SearchParamsBuilder::default().exact(true)),
)
.await?;
```
@@ -0,0 +1,9 @@
```typescript
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
params: {
exact: true,
},
limit: 10,
});
```
@@ -0,0 +1,26 @@
package snippet
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
func Main() {
// @hide-start
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
if err != nil { panic(err) }
// @hide-end
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
Params: &qdrant.SearchParams{
Exact: qdrant.PtrOf(true),
},
})
}
@@ -0,0 +1,10 @@
```http
POST /collections/{collection_name}/points/query
{
"query": [0.2, 0.1, 0.9, 0.7],
"params": {
"exact": true
},
"limit": 10
}
```
@@ -0,0 +1,26 @@
package com.example.snippets_amalgamation;
import static io.qdrant.client.QueryFactory.nearest;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.SearchParams;
public class Snippet {
public static void run() throws Exception {
// @hide-start
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
// @hide-end
client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName("{collection_name}")
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
.setParams(SearchParams.newBuilder().setExact(true).build())
.setLimit(10)
.build())
.get();
}
}
@@ -0,0 +1,10 @@
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333") # @hide
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
search_params=models.SearchParams(exact=True),
limit=10,
)
@@ -0,0 +1,17 @@
use qdrant_client::qdrant::{QueryPointsBuilder, SearchParamsBuilder};
use qdrant_client::Qdrant;
pub async fn main() -> anyhow::Result<()> {
let client = Qdrant::from_url("http://localhost:6334").build()?; // @hide
client
.query(
QueryPointsBuilder::new("{collection_name}")
.query(vec![0.2, 0.1, 0.9, 0.7])
.limit(10)
.params(SearchParamsBuilder::default().exact(true)),
)
.await?;
Ok(())
}
@@ -0,0 +1,11 @@
import { QdrantClient } from "@qdrant/js-client-rest"; // @hide
const client = new QdrantClient({ host: "localhost", port: 6333 }); // @hide
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
params: {
exact: true,
},
limit: 10,
});
@@ -0,0 +1 @@
This code snippet demonstrates how to paginate search results without duplicate points. By collecting the point IDs returned on each page and passing them to a `must_not: has_id` filter on the next request, each subsequent page excludes all previously seen results.
@@ -0,0 +1,20 @@
using Qdrant.Client;
using static Qdrant.Client.Grpc.Conditions;
public class Snippet
{
public static async Task Run()
{
var client = new QdrantClient("localhost", 6334); // @hide
ulong[] seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
// The ! operator negates the condition (must not)
await client.QueryAsync(
collectionName: "{collection_name}",
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
filter: !HasId(seenIds),
limit: 5
);
}
}
@@ -0,0 +1,14 @@
```csharp
using Qdrant.Client;
using static Qdrant.Client.Grpc.Conditions;
ulong[] seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
// The ! operator negates the condition (must not)
await client.QueryAsync(
collectionName: "{collection_name}",
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
filter: !HasId(seenIds),
limit: 5
);
```
@@ -0,0 +1,25 @@
```go
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
seenIds := []uint64{83461, 19284, 57392, 44017, 91825} // IDs returned on previous pages
pointIds := make([]*qdrant.PointId, len(seenIds))
for i, id := range seenIds {
pointIds[i] = qdrant.NewIDNum(id)
}
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
Filter: &qdrant.Filter{
MustNot: []*qdrant.Condition{
qdrant.NewHasID(pointIds...),
},
},
Limit: qdrant.PtrOf(uint64(5)),
})
```
@@ -0,0 +1,25 @@
```java
import static io.qdrant.client.ConditionFactory.hasId;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.QueryFactory.nearest;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.QueryPoints;
import java.util.List;
var seenIds = List.of(id(83461), id(19284), id(57392), id(44017), id(91825)); // IDs returned on previous pages
client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName("{collection_name}")
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
.setFilter(
Filter.newBuilder()
.addMustNot(hasId(seenIds))
.build())
.setLimit(5)
.build())
.get();
```
@@ -0,0 +1,18 @@
```python
from uuid import UUID
from qdrant_client import QdrantClient, models
seen_ids: list[int | str | UUID] = [83461, 19284, 57392, 44017, 91825] # IDs returned on previous pages
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
query_filter=models.Filter(
must_not=[
models.HasIdCondition(has_id=seen_ids),
]
),
limit=5,
)
```
@@ -0,0 +1,15 @@
```rust
use qdrant_client::qdrant::{Condition, Filter, QueryPointsBuilder};
use qdrant_client::Qdrant;
let seen_ids = vec![83461u64, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
client
.query(
QueryPointsBuilder::new("{collection_name}")
.query(vec![0.2, 0.1, 0.9, 0.7])
.filter(Filter::must_not([Condition::has_id(seen_ids)]))
.limit(5),
)
.await?;
```
@@ -0,0 +1,15 @@
```typescript
const seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
filter: {
must_not: [
{
has_id: seenIds,
},
],
},
limit: 5,
});
```
@@ -0,0 +1,36 @@
package snippet
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
func Main() {
// @hide-start
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
if err != nil { panic(err) }
// @hide-end
seenIds := []uint64{83461, 19284, 57392, 44017, 91825} // IDs returned on previous pages
pointIds := make([]*qdrant.PointId, len(seenIds))
for i, id := range seenIds {
pointIds[i] = qdrant.NewIDNum(id)
}
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
Filter: &qdrant.Filter{
MustNot: []*qdrant.Condition{
qdrant.NewHasID(pointIds...),
},
},
Limit: qdrant.PtrOf(uint64(5)),
})
}
@@ -0,0 +1,12 @@
```http
POST /collections/{collection_name}/points/query
{
"query": [0.2, 0.1, 0.9, 0.7],
"filter": {
"must_not": [
{ "has_id": [83461, 19284, 57392, 44017, 91825] }
]
},
"limit": 5
}
```
@@ -0,0 +1,34 @@
package com.example.snippets_amalgamation;
import static io.qdrant.client.ConditionFactory.hasId;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.QueryFactory.nearest;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.QueryPoints;
import java.util.List;
public class Snippet {
public static void run() throws Exception {
// @hide-start
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
// @hide-end
var seenIds = List.of(id(83461), id(19284), id(57392), id(44017), id(91825)); // IDs returned on previous pages
client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName("{collection_name}")
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
.setFilter(
Filter.newBuilder()
.addMustNot(hasId(seenIds))
.build())
.setLimit(5)
.build())
.get();
}
}
@@ -0,0 +1,18 @@
from uuid import UUID
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333") # @hide
seen_ids: list[int | str | UUID] = [83461, 19284, 57392, 44017, 91825] # IDs returned on previous pages
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
query_filter=models.Filter(
must_not=[
models.HasIdCondition(has_id=seen_ids),
]
),
limit=5,
)
@@ -0,0 +1,19 @@
use qdrant_client::qdrant::{Condition, Filter, QueryPointsBuilder};
use qdrant_client::Qdrant;
pub async fn main() -> anyhow::Result<()> {
let client = Qdrant::from_url("http://localhost:6334").build()?; // @hide
let seen_ids = vec![83461u64, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
client
.query(
QueryPointsBuilder::new("{collection_name}")
.query(vec![0.2, 0.1, 0.9, 0.7])
.filter(Filter::must_not([Condition::has_id(seen_ids)]))
.limit(5),
)
.await?;
Ok(())
}
@@ -0,0 +1,17 @@
import { QdrantClient } from "@qdrant/js-client-rest"; // @hide
const client = new QdrantClient({ host: "localhost", port: 6333 }); // @hide
const seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
filter: {
must_not: [
{
has_id: seenIds,
},
],
},
limit: 5,
});
@@ -1,10 +1,10 @@
using Qdrant.Client;
using Qdrant.Client; // @hide
public class Snippet
{
public static async Task Run()
{
var client = new QdrantClient("localhost", 6334);
var client = new QdrantClient("localhost", 6334); // @hide
await client.QueryAsync(
collectionName: "{collection_name}",
@@ -1,8 +1,4 @@
```csharp
using Qdrant.Client;
var client = new QdrantClient("localhost", 6334);
await client.QueryAsync(
collectionName: "{collection_name}",
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
@@ -5,16 +5,12 @@ import (
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
WithPayload: qdrant.NewWithPayload(true),
WithVectors: qdrant.NewWithVectors(true),
Limit: qdrant.PtrOf(uint64(10)),
Offset: qdrant.PtrOf(uint64(100)),
})
```
@@ -8,9 +8,6 @@ import io.qdrant.client.WithVectorsSelectorFactory;
import io.qdrant.client.grpc.Points.QueryPoints;
import java.util.List;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client.queryAsync(
QueryPoints.newBuilder()
.setCollectionName("{collection_name}")
@@ -1,8 +1,4 @@
```python
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
@@ -2,8 +2,6 @@
use qdrant_client::qdrant::QueryPointsBuilder;
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.query(
QueryPointsBuilder::new("{collection_name}")
@@ -1,8 +1,4 @@
```typescript
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
with_vector: true,
@@ -7,18 +7,21 @@ import (
)
func Main() {
// @hide-start
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
if err != nil { panic(err) } // @hide
if err != nil { panic(err) }
// @hide-end
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
WithPayload: qdrant.NewWithPayload(true),
WithVectors: qdrant.NewWithVectors(true),
Limit: qdrant.PtrOf(uint64(10)),
Offset: qdrant.PtrOf(uint64(100)),
})
}
@@ -11,8 +11,10 @@ import java.util.List;
public class Snippet {
public static void run() throws Exception {
// @hide-start
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
// @hide-end
client.queryAsync(
QueryPoints.newBuilder()
@@ -1,6 +1,6 @@
from qdrant_client import QdrantClient
from qdrant_client import QdrantClient # @hide
client = QdrantClient(url="http://localhost:6333")
client = QdrantClient(url="http://localhost:6333") # @hide
client.query_points(
collection_name="{collection_name}",
@@ -2,7 +2,7 @@ use qdrant_client::qdrant::QueryPointsBuilder;
use qdrant_client::Qdrant;
pub async fn main() -> anyhow::Result<()> {
let client = Qdrant::from_url("http://localhost:6334").build()?;
let client = Qdrant::from_url("http://localhost:6334").build()?; // @hide
client
.query(
@@ -1,6 +1,6 @@
import { QdrantClient } from "@qdrant/js-client-rest";
import { QdrantClient } from "@qdrant/js-client-rest"; // @hide
const client = new QdrantClient({ host: "localhost", port: 6333 });
const client = new QdrantClient({ host: "localhost", port: 6333 }); // @hide
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
@@ -8,7 +8,7 @@ aliases:
- /documentation/concepts/search/
---
# Similarity search
# Similarity Search
Searching for the nearest vectors is at the core of many representational learning applications.
Modern neural networks are trained to transform objects into vectors so that objects close in the real world appear close in vector space.
@@ -142,7 +142,7 @@ In general, the speed of the search is proportional to the number of non-zero va
{{< code-snippet path="/documentation/headless/snippets/query-points/sparse-vectors/" >}}
### Filtering results by score
### Filtering Results by Score
In addition to payload filtering, it might be useful to filter out results with a low similarity score.
For example, if you know the minimal acceptance score for your model and do not want any results which are less similar than the threshold.
@@ -151,7 +151,7 @@ It will exclude all results with a score worse than the given.
<aside role="status">This parameter may exclude lower or higher scores depending on the used metric. For example, higher scores of Euclidean metric are considered more distant and, therefore, will be excluded.</aside>
### Payload and vector in the result
### Payload and Vector in the Result
By default, retrieval methods do not return any stored information such as
payload and vectors. Additional parameters `with_vectors` and `with_payload`
@@ -205,7 +205,7 @@ $$ \text{Estimated filter selectivity} =
$$
Since ACORN is significantly slower (approximately 2-10x in typical scenarios) but improves recall for restrictive filters, tuning this parameter is about deciding when the accuracy improvement justifies the performance cost.
## Batch search API
## Batch Search API
The batch search API enables to perform multiple search requests via a single request.
@@ -266,22 +266,47 @@ collection `another_collection`.
## Pagination
Search and [recommendation](/documentation/search/explore/#recommendation-api) APIs allow to skip first results of the search and return only the result starting from some specified offset:
The Search and [recommendation](/documentation/search/explore/#recommendation-api) APIs allow you to skip the first results and return only the results starting from a specified offset:
Example:
{{< code-snippet path="/documentation/headless/snippets/query-points/with-offset/" >}}
Is equivalent to retrieving the 11th page with 10 records per page.
This is equivalent to retrieving the 11th page with 10 records per page.
<aside role="alert">Large offset values may cause performance issues</aside>
Vector-based retrieval in general and HNSW index in particular, are not designed to be paginated.
It is impossible to retrieve Nth closest vector without retrieving the first N vectors first.
Vector-based retrieval in general, and the HNSW index in particular, are not designed to be paginated. It is impossible to retrieve the Nth closest vector without internally retrieving the first N vectors first. However, using the `offset` parameter saves resources by reducing network traffic and the number of times the storage is accessed. Using the `offset` parameter internally retrieves `offset + limit` points, but only accesses the payload and vector of those points that are actually returned.
However, using the offset parameter saves the resources by reducing network traffic and the number of times the storage is accessed.
### Stable Ordering
Using an `offset` parameter, will require to internally retrieve `offset + limit` points, but only access payload and vector from the storage those points which are going to be actually returned.
Because HNSW search is approximate, the ranking of results can shift slightly between requests. As a result, paginating with `offset` can return the same point on multiple pages or skip points entirely.
There are several ways to work around this:
#### Client-Side Pagination
Retrieve a large batch in a single request and paginate through it on the client. For example, fetch the top 100 results at once and let the user browse them 10 at a time. This avoids multiple round-trips and guarantees no duplicates.
The trade-off is increased latency, and returning more data than the user actually needs.
#### Exact Search
Use exact searches to bypass HNSW and scan all vectors, returning results in a stable, deterministic order. This ensures that offset-based pagination works correctly.
The trade-off is higher latency, which makes this practical only for small collections.
{{< code-snippet path="/documentation/headless/snippets/query-points/with-exact-search/" >}}
#### Exclude Seen IDs
To avoid duplicates, on subsequent pages, add a `must_not: has_id` filter containing all point IDs collected from previous pages. This excludes all previously seen points from the results:
{{< code-snippet path="/documentation/headless/snippets/query-points/with-id-exclusion-pagination/" >}}
Repeat this pattern on every page, expanding the exclusion list with each set of results.
<aside role="status">The exclusion list grows by <code>limit</code> entries per page. This approach works well for sequential, forward-only pagination. It isn't practical for jumping directly to an arbitrary page.</aside>
## Grouping API
@@ -346,7 +371,7 @@ Consider having points with the following payloads:
With the ***groups*** API, you will be able to get the best *N* points for each document, assuming that the payload of the points contains the document ID. Of course there will be times where the best *N* points cannot be fulfilled due to lack of points or a big distance with respect to the query. In every case, the `group_size` is a best-effort parameter, akin to the `limit` parameter.
### Search groups
### Search Groups
REST API ([Schema](https://api.qdrant.tech/api-reference/search/query-points-groups)):
@@ -402,7 +427,7 @@ If the `group_by` field of a point is an array (e.g. `"document_id": ["a", "b"]`
* Only [keyword](/documentation/manage-data/payload/#keyword) and [integer](/documentation/manage-data/payload/#integer) payload values are supported for the `group_by` parameter. Payload values with other types will be ignored.
* At the moment, pagination is not enabled when using **groups**, so the `offset` parameter is not allowed.
### Lookup in groups
### Lookup in Groups
When the points in a group share large fields like titles, abstracts, or full document vectors, copying that data onto every point inflates storage and forces you to rewrite every chunk whenever a shared field changes.
@@ -484,7 +509,7 @@ Random sampling API is a part of [Universal Query API](#query-api) and can be us
{{< code-snippet path="/documentation/headless/snippets/query-points/random-sample/" >}}
## Query planning
## Query Planning
Depending on the filter used in the search - there are several possible scenarios for query execution.
Qdrant chooses one of the query execution options depending on the available indexes, the complexity of the conditions and the cardinality of the filtering result.