How do must, should, and must_not combine inside a Qdrant Filter?
answer
- three clauses, one conjunction
- AND, OR, NOT — in that order
- should is a constraint, not a boost
- min_count raises the should bar
- nest Filters for OR-of-ANDs
basics
~20 sClauses are ANDed with each other: a point must satisfy every condition in must, at least one in should, and none in must_not. min_should raises the should bar from one to N. Nesting Filter objects inside should expresses OR-of-ANDs.
solid answer
~50 sA Qdrant `Filter` has three condition lists and they compose as a conjunction of clauses. `must` is AND — every condition in it has to hold. `should` is OR — at least one has to hold, and `min_should=models.MinShould(conditions=[...], min_count=n)` changes that bar to *n of them*. `must_not` is NOT — no condition in it may hold. If a filter populates more than one clause, a point has to satisfy all of them: all the `must` conditions **and** at least one `should` **and** none of the `must_not`. That differs from search engines where an optional clause only boosts ranking; in Qdrant `should` is a real constraint and contributes nothing to the score. Because a condition slot accepts a nested `Filter` as well as a `FieldCondition`, arbitrary boolean trees are possible — the usual shape is `(A AND B) OR (C AND D)`, written as two `Filter` objects sitting in `should`.
code
python · 10 linesfrom qdrant_client import models
flt = models.Filter(
must=[models.FieldCondition(key="status", match=models.MatchValue(value="published"))],
should=[
models.FieldCondition(key="tag", match=models.MatchAny(any=["ml", "db"])),
models.FieldCondition(key="featured", match=models.MatchValue(value=True)),
],
must_not=[models.FieldCondition(key="lang", match=models.MatchValue(value="ru"))],
)go deeper
Memorise the mapping: must is AND, should is OR, must_not is NOT, and all three clauses in one filter apply together.
Explain that clauses are ANDed with each other, that should is a hard constraint rather than a boost, and that nesting a Filter inside a clause gives arbitrary boolean trees.
Show how you verify a filter's real cardinality with an exact count before trusting it, and how you keep negation and missing-key semantics from silently narrowing production results.
Own how filter rules are expressed in the codebase — composable builders per business rule rather than ad-hoc dict literals — so that boolean intent stays reviewable as the rule set grows.
## The three clauses `models.Filter` exposes `must`, `should` and `must_not`, each a list of conditions. Their individual meanings are simple: - **`must`** — logical AND. All listed conditions must be satisfied. - **`should`** — logical OR. At least one listed condition must be satisfied. - **`must_not`** — logical NOT. None of the listed conditions may be satisfied. ## How clauses combine with each other The part people get wrong is what happens when a single filter fills in more than one clause. The clauses are themselves ANDed: the point has to pass the `must` block **and** the `should` block **and** the `must_not` block. So a filter with `must=[status=published]`, `should=[tag=ml, featured=true]` and `must_not=[lang=ru]` selects published, non-Russian points that are either tagged `ml` or flagged featured. Dropping the `should` clause would widen the result; it never merely reorders it. This is the single biggest transfer error from Lucene-family engines, where an optional clause alongside a required clause only influences scoring. Qdrant has no scoring contribution from filters at all — the similarity metric alone ranks the survivors — so an optional-looking clause that did nothing would be meaningless. `should` is a constraint. ## min_should Sometimes "at least one" is too loose. `min_should` takes a `MinShould(conditions=[...], min_count=n)` and requires *n* of its conditions to match. A recommendation filter that wants points sharing at least two of five interest tags is a natural fit. `min_should` lives on the `Filter` alongside the other clauses and is ANDed with them like everything else. ## Nesting for real boolean trees Every clause element can be either a leaf condition (`FieldCondition`, `IsEmptyCondition`, `IsNullCondition`, `HasIdCondition`, `NestedCondition`) or another `Filter`. That recursion is what makes the DSL complete rather than a flat two-level grammar. The canonical example is a disjunction of conjunctions — `(country = DE AND price < 100) OR (country = FR AND price < 80)`. You write two inner `Filter` objects, each with its own `must`, and put both of them in the outer filter's `should`. Conversely, `must` holding two `Filter`s each with their own `should` gives you `(A OR B) AND (C OR D)`. A subtlety worth knowing: a nested `Filter` placed inside `must_not` negates the whole sub-expression, which is how you express "not (A and B)". Putting A and B directly in `must_not` means something different — "neither A nor B" — because `must_not` negates each condition independently. Getting that distinction wrong is a common source of silently-too-narrow results. ## Negation and missing keys `must_not` on a `FieldCondition` excludes points where the condition matches. Points where the key is **absent** do not match the condition, and therefore survive the negation. If your intent was "only points that have a `lang` and it isn't Russian", you need `must=[IsEmptyCondition negated appropriately]` or simply a positive `MatchExcept` over the allowed values plus an existence requirement. Treat missing keys as an explicit case rather than assuming negation covers them. ## Arrays and clause semantics Because any condition on an array payload matches if any element matches, `must_not` over an array means "no element matches" — usually what you want. But two `must` conditions over the *same* array are evaluated independently, so they can be satisfied by different elements; that is a separate mechanism (nested object filters) rather than a clause-combination question. ## Debugging a filter The fastest way to see what a filter really selects is `count(count_filter=flt, exact=True)`, then progressively remove clauses. If removing a `should` clause changes the count, that proves the clause was constraining and not decorating. Building filters as small composable Python functions — one per business rule, each returning conditions that a caller drops into `must` — keeps this tractable as rule sets grow. ## Assumed version `min_should` is a comparatively recent addition; the three core clauses have been stable for a long time. Written against qdrant-client 1.19.
- How do you require that at least two of five tag conditions match?Use `min_should`: `models.Filter(min_should=models.MinShould(conditions=[c1, c2, c3, c4, c5], min_count=2))`. It behaves like `should` but with a configurable threshold instead of the implicit one. It is ANDed with any `must` and `must_not` clauses on the same filter, so you can combine a hard eligibility rule with a soft "enough overlap" rule in one request.
- What is the difference between putting two conditions in must_not versus negating a nested filter containing both?Two conditions directly in `must_not` mean "neither A nor B" — each is negated independently. A nested `Filter(must=[A, B])` placed inside `must_not` means "not (A and B)", which still admits points matching just A or just B. The second is far more permissive, and confusing the two is a frequent cause of results that look mysteriously narrow.
- Does a should clause affect the order of returned results?No. Qdrant filters never contribute to scoring; the surviving points are ranked purely by vector distance under the collection's metric. If you want "featured items ranked higher", you have to either run two queries and merge, or apply your own reranking on the returned payloads. Expecting `should` to boost is the classic habit imported from Lucene-based engines.
saying these in an interview costs you the question
- Thinking should only boosts ranking like in a search engine
- Assuming a should clause is optional when must is present
- Putting two conditions in must_not to mean not-(A-and-B)
- Expecting must_not to also exclude points missing the key
- Believing the DSL cannot express nested boolean expressions