# Filtering in a Dedicated Engine: Qdrant — Vector Databases

Source: https://www.geekswithgeeks.com/en/vector-databases/f-qdrant

> See payload filters in an engine built around them.

## Payload filters are first-class

**Qdrant** is an open-source vector database written in Rust. Its model: a **collection** of **points**, each with an ID, one or more vectors, and a JSON **payload**. You create a collection with the vector size and distance metric, `upsert` points, and `query` with a vector plus an optional **filter** built from `must`, `should` and `must_not` conditions on payload fields (match, range, geo, and more). Qdrant's HNSW search is **filter-aware**: it evaluates the payload conditions while traversing the graph (and you should create **payload indexes** on fields you filter on), which avoids the empty-result trap of naive post-filtering. The Python client also has a **local in-memory mode**, used here, that exposes the same API without running a server, which is excellent for tests.

## Qdrant: upsert, search, filter, delete, run

I ran this Python in a virtual environment with qdrant-client 1.19.1 (local in-memory mode) and chromadb 1.5.9 (in-memory client). The collection holds four points. A plain query returns points 1, 4 and 2 (4 belongs to another tenant). With a filter for tenant `acme` and year 2025, only points 1 and 3 qualify. After deleting point 4 the count drops from 4 to 3. Local mode is used, so no server is involved.

```python
from qdrant_client import QdrantClient, models

client = QdrantClient(":memory:")              # local mode: same API as a server, nothing to install or run
client.create_collection("docs", vectors_config=models.VectorParams(size=3, distance=models.Distance.COSINE))
client.upsert("docs", points=[
    models.PointStruct(id=1, vector=[0.9, 0.1, 0.0], payload={"tenant": "acme", "year": 2025, "text": "leave policy 2025"}),
    models.PointStruct(id=2, vector=[0.8, 0.2, 0.1], payload={"tenant": "acme", "year": 2022, "text": "old leave policy"}),
    models.PointStruct(id=3, vector=[0.1, 0.9, 0.1], payload={"tenant": "acme", "year": 2025, "text": "travel and hotels"}),
    models.PointStruct(id=4, vector=[0.9, 0.0, 0.2], payload={"tenant": "beta", "year": 2025, "text": "beta confidential memo"}),
])
print("points:", client.count("docs").count)

hits = client.query_points("docs", query=[1, 0, 0], limit=3).points
print("no filter :", [(h.id, round(h.score, 3)) for h in hits])

only_acme_2025 = models.Filter(must=[
    models.FieldCondition(key="tenant", match=models.MatchValue(value="acme")),
    models.FieldCondition(key="year", match=models.MatchValue(value=2025)),
])
hits = client.query_points("docs", query=[1, 0, 0], query_filter=only_acme_2025, limit=3).points
print("with filter:", [(h.id, h.payload["text"]) for h in hits])

client.delete("docs", points_selector=models.PointIdsList(points=[4]))
print("after delete:", client.count("docs").count)

```

Output:

```
points: 4
no filter : [(1, 0.994), (4, 0.976), (2, 0.963)]
with filter: [(1, 'leave policy 2025'), (3, 'travel and hotels')]
after delete: 3
```

## Create payload indexes

In a dedicated engine, index every payload field you filter on, otherwise filtered search may fall back to slower paths.

**Quiz:** What makes Qdrant's filtered search robust?

- [ ] It only supports exact search
- [ ] It ignores filters
- [x] Payload conditions are checked during graph traversal, with payload indexes
- [ ] Filters run in the browser

*Answer:* Payload conditions are checked during graph traversal, with payload indexes. Filter-aware traversal avoids returning too few results.
