7.13 Filtering: mệnh đề và điều kiện
7.8 nói filter được thực thi thế nào. 7.9 nói phải đánh index gì. Mục này là bảng tra cú pháp, cộng với một cái bẫy đủ nghiêm trọng để nó là lý do chính mục này tồn tại.
Bốn mệnh đề
Phần tiêu đề “Bốn mệnh đề”Filter là một cây, và cây đó lồng nhau tuỳ ý:
| Mệnh đề | Tương đương | Nghĩa |
|---|---|---|
must | AND | mọi điều kiện phải thoả |
should | OR | ít nhất một điều kiện thoả |
must_not | NOT | không điều kiện nào được thoả |
min_should | “ít nhất N trong số” | chỉ định số lượng should tối thiểu |
min_should là thứ hay bị bỏ quên. Nó cho phép diễn đạt “khớp ít nhất 2 trong 5 thẻ” mà
không phải liệt kê thủ công mọi tổ hợp.
Các điều kiện
Phần tiêu đề “Các điều kiện”Khớp giá trị
Phần tiêu đề “Khớp giá trị”| Cú pháp | Nghĩa | Cần index |
|---|---|---|
match: {value: x} | bằng đúng x | keyword / integer / bool |
match: {any: [...]} | IN | như trên |
match: {except: [...]} | NOT IN | như trên |
match: {prefix: "..."} | bắt đầu bằng | keyword có bật prefix (từ v1.19) |
match: {text: "..."} | chứa các token | text |
match: {text_any: "..."} | chứa bất kỳ token nào (từ v1.16) | text |
match: {phrase: "..."} | khớp cụm đúng thứ tự | text có bật phrase_matching |
Phân biệt text và text_any rất đáng nhớ: text yêu cầu mọi token của truy vấn có
mặt (giống AND); text_any chỉ cần một (giống OR). Nhầm hai cái này là nguyên nhân
của cả “lọc quá chặt, không ra gì” lẫn “lọc quá lỏng, ra tất cả”.
Khoảng
Phần tiêu đề “Khoảng”range: {gt, gte, lt, lte} cho integer / float, và cùng cú pháp đó cho datetime
với giá trị theo RFC 3339. Nếu ngày là trục lọc chính, nhớ is_principal: true
(7.9).
Địa lý
Phần tiêu đề “Địa lý”geo_bounding_box (hình chữ nhật), geo_radius (hình tròn, bán kính tính bằng mét),
geo_polygon (đa giác, có thể khoét lỗ bằng interiors).
Điều kiện về chính payload
Phần tiêu đề “Điều kiện về chính payload”| Điều kiện | Nghĩa | Bẫy |
|---|---|---|
is_empty: {key: f} | trường thiếu, null, hoặc mảng rỗng | Khác is_null |
is_null: {key: f} | trường tồn tại và có giá trị null | Khác is_empty |
values_count: {gt, lt, …} | lọc theo số phần tử của mảng | Rất hữu ích, hay bị quên |
has_id: [...] | lọc theo ID cụ thể | Không dùng được trong nested |
has_vector: "ten" | điểm có named vector này không | Hữu ích khi migrate model |
slice: {index, total} | chia collection thành phần rời nhau, xác định (v1.19) | Cho quét song song, xem 7.11 |
is_empty và is_null khác nhau và người ta nhầm liên tục. Nếu bạn muốn “những điểm chưa
được gán phòng ban”, trong đó có cả điểm thiếu trường lẫn điểm có trường bằng null, thì
is_empty mới là cái bạn cần.
has_vector đáng chú ý trong quy trình đổi model embedding: nạp vector v4 dần dần, rồi
dùng has_vector: "v4" để biết còn bao nhiêu điểm chưa xử lý.
⚠️ Bẫy lớn: lọc trên mảng object
Phần tiêu đề “⚠️ Bẫy lớn: lọc trên mảng object”Đây là lý do chính mục này tồn tại. Nó âm thầm, phổ biến, và cho ra kết quả sai mà trông đúng.
Giả sử payload:
{ "phe_duyet": [ {"nguoi": "an", "trang_thai": "tu_choi"}, {"nguoi": "binh","trang_thai": "dong_y"} ] }Bạn muốn: “tài liệu có người an đã đồng ý.” Viết theo bản năng:
{ "must": [ {"key": "phe_duyet[].nguoi", "match": {"value": "an"}}, {"key": "phe_duyet[].trang_thai", "match": {"value": "dong_y"}} ] }Điểm này khớp. Nhưng nó không đúng ý bạn: an có mặt (ở phần tử 1) và dong_y có mặt
(ở phần tử 2) — hai điều kiện thoả ở hai phần tử khác nhau. Qdrant, như phần lớn kho
tài liệu, mặc định làm phẳng mảng trước khi lọc.
Cách đúng là nested:
{ "must": [ { "nested": { "key": "phe_duyet", "filter": { "must": [ {"key": "nguoi", "match": {"value": "an"}}, {"key": "trang_thai", "match": {"value": "dong_y"}} ] } } } ] }nested áp bộ điều kiện lên từng phần tử một, và điểm chỉ khớp nếu có ít nhất một
phần tử thoả toàn bộ. Đó mới là “an đã đồng ý”.
Quy tắc nhớ: hễ payload có mảng object và bạn ràng buộc hai trường trở lên bên trong cùng một phần tử, bạn phải dùng
nested. Nếu không, bạn đang lọc lỏng hơn mình nghĩ — và không có gì báo cho bạn biết.
Hạn chế: has_id và slice không dùng được bên trong nested.
Ba điều nên nhớ khi thiết kế filter
Phần tiêu đề “Ba điều nên nhớ khi thiết kế filter”- Mọi trường trong
filterphải có payload index. Không phải để nhanh — mà để cơ chế ở 7.8 hoạt động. - Filter càng chặt càng nên đo. Trực giác “lọc bớt thì nhanh hơn” đúng với SQL, không luôn đúng với vector search: filter chặt làm đứt đồ thị. Đo cặp (c)(d) ở 7.8.
must_nottrên trường cardinality cao là dấu hiệu xấu. “Tất cả trừ X” thường ước lượng cardinality kém và đẩy query planner vào lựa chọn tồi. Nếu diễn đạt lại được thànhmustthì nên làm.