Фильтр
С помощью Qdrant вы можете устанавливать условия для поиска или извлечения точек, что означает, что вы можете фильтровать по атрибутам в дополнение к поиску похожих векторов, подобно установке условий SQL where
. Например, вы можете устанавливать условия для полезной нагрузки и id
точки.
Важно устанавливать дополнительные условия, когда не все особенности объекта могут быть выражены во встраивании. Например, различные бизнес-требования, такие как наличие инвентаря, местоположение пользователя или ожидаемый диапазон цен.
Условия фильтра
Qdrant позволяет комбинировать условия в предложениях. Предложения – это различные логические операции, такие как OR
, AND
и NOT
. Предложения могут быть рекурсивно вложены друг в друга, так что вы можете воссоздать любое булево выражение.
Давайте посмотрим на предложения, реализованные в Qdrant.
Предположим, у нас есть набор точек с полезными нагрузками:
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 2, "city": "London", "color": "red" },
{ "id": 3, "city": "London", "color": "blue" },
{ "id": 4, "city": "Berlin", "color": "red" },
{ "id": 5, "city": "Moscow", "color": "green" },
{ "id": 6, "city": "Moscow", "color": "blue" }
]
Must
Пример:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Фильтрованная точка будет:
[{ "id": 2, "city": "London", "color": "red" }]
При использовании must
предложение истинно только если удовлетворяются все перечисленные условия в must
. В этом смысле must
эквивалентен оператору AND
.
Should
Should
похож на оператор OR
в SQL.
Пример:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Отфильтрованные точки будут:
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 2, "city": "London", "color": "red" },
{ "id": 3, "city": "London", "color": "blue" },
{ "id": 4, "city": "Berlin", "color": "red" }
]
При использовании should
предложение истинно, пока удовлетворяется хотя бы одно условие, перечисленное в should
. В этом смысле should
эквивалентен оператору OR
.
must_not
Пример:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must_not": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Отфильтрованные точки будут:
[
{ "id": 5, "city": "Moscow", "color": "green" },
{ "id": 6, "city": "Moscow", "color": "blue" }
]
При использовании must_not
подпредложение истинно только если ни одно из перечисленных условий в must_not
не удовлетворяется. В этом смысле must_not
эквивалентен выражению (NOT A) AND (NOT B) AND (NOT C)
.
Комбинация условий
Одновременное использование нескольких условий возможно:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "key": "city", "match": { "value": "London" } }
],
"must_not": [
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Отфильтрованные точки будут:
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 3, "city": "London", "color": "blue" }
]
В этом случае условия объединены с использованием AND
.
Кроме того, условия могут быть рекурсивно вложенными. Например:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must_not": [
{
"must": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
]
}
...
}
Отфильтрованные точки будут:
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 3, "city": "London", "color": "blue" },
{ "id": 4, "city": "Berlin", "color": "red" },
{ "id": 5, "city": "Moscow", "color": "green" },
{ "id": 6, "city": "Moscow", "color": "blue" }
]
Фильтрация условий
В полезной нагрузке различные типы значений соответствуют различным типам запросов, которые могут к ним применяться. Давайте рассмотрим существующие варианты условий и типы данных, к которым они применяются.
Соответствие
{
"key": "color",
"match": {
"value": "red"
}
}
Для других типов сопоставления условия выглядят абсолютно так же, просто используются различные типы:
{
"key": "count",
"match": {
"value": 0
}
}
Простое условие проверяет, равно ли сохраненное значение данному значению. Если сохранены несколько значений, хотя бы одно из них должно удовлетворять условию. Это можно применить к ключевым словам, целым числам и булевым нагрузкам.
Любое соответствие
Доступно с версии v1.1.0
Если вы хотите проверить, что сохраненное значение является одним из нескольких значений, вы можете использовать условие любого сопоставления. Любое сопоставление рассматривает данное значение как логическую операцию ИЛИ. Его также можно описать как оператор IN
.
Это можно применить к ключевым словам и целым числам.
Пример:
{
"key": "color",
"match": {
"any": ["black", "yellow"]
}
}
В этом примере, если сохраненное значение равно black
или yellow
, то условие будет выполнено.
Если сохраненное значение является массивом, должно быть хотя бы одно значение, которое соответствует любому из заданных значений. Например, если сохраненное значение равно ["black", "green"]
, то условие будет выполнено, потому что "black"
есть в ["black", "yellow"]
.
Исключить соответствия
Доступно с версии v1.2.0
Если вы хотите проверить, что сохраненное значение не является ни одним из нескольких значений, вы можете использовать условие исключения сопоставления. Исключить сопоставление рассматривает данное значение как логическую операцию НЕ. Его также можно описать как оператор NOT IN
.
Это можно применить к ключевым словам и целым числам.
Пример:
{
"key": "color",
"match": {
"except": ["black", "yellow"]
}
}
В этом примере, если сохраненное значение не равно ни black
, ни yellow
, то условие будет выполнено.
Если сохраненное значение является массивом, должно быть хотя бы одно значение, которое не соответствует ни одному из заданных значений. Например, если сохраненное значение равно ["black", "green"]
, то условие будет выполнено, потому что "green"
не соответствует ни "black"
, ни "yellow"
.
Вложенные ключи
Доступно с версии v1.1.0 и выше
Поскольку полезная нагрузка представляет собой произвольный объект JSON, вам может понадобиться фильтровать вложенные поля.
Для удобства мы используем синтаксис, аналогичный проекту Jq.
Предположим, у нас есть набор точек с следующей полезной нагрузкой:
[
{
"id": 1,
"country": {
"name": "Германия",
"cities": [
{
"name": "Берлин",
"population": 3.7,
"достопримечательности": ["Бранденбургские ворота", "Рейхстаг"]
},
{
"name": "Мюнхен",
"population": 1.5,
"достопримечательности": ["Мариенплац", "Олимпийский парк"]
}
]
}
},
{
"id": 2,
"country": {
"name": "Япония",
"cities": [
{
"name": "Токио",
"population": 9.3,
"достопримечательности": ["Токийская башня", "Токийский Скайтри"]
},
{
"name": "Осака",
"population": 2.7,
"достопримечательности": ["Осака-дзо", "Universal Studios Japan"]
}
]
}
}
]
Вы можете использовать точечную нотацию для поиска вложенных полей.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.name",
"match": {
"value": "Германия"
}
}
]
}
}
Вы также можете использовать синтаксис [ ]
для поиска массива, проецируя внутренние значения.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.cities[].population",
"range": {
"gte": 9.0,
}
}
]
}
}
Этот запрос выводит только точку с идентификатором 2, поскольку только в Японии есть город с населением больше 9.0.
Вложенные поля могут также быть массивом.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.cities[].достопримечательности",
"match": {
"value": "Осака-дзо"
}
}
]
}
}
Этот запрос также выводит только точку с идентификатором 2, поскольку только в Японии есть город с достопримечательностью, которая включает "Осака-дзо".
Фильтрация вложенных объектов
Доступно с версии 1.2.0
По умолчанию условия учитывают весь набор данных точки.
Например, учитывая две точки в наборе данных ниже:
[
{
"id": 1,
"dinosaur": "t-rex",
"diet": [
{ "food": "листья", "likes": false},
{ "food": "мясо", "likes": true}
]
},
{
"id": 2,
"dinosaur": "диплодок",
"diet": [
{ "food": "листья", "likes": true},
{ "food": "мясо", "likes": false}
]
}
]
Следующий запрос сопоставит эти две точки:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{
"key": "diet[].food",
"match": {
"value": "мясо"
}
},
{
"key": "diet[].likes",
"match": {
"value": true
}
}
]
}
}
Две точки сопоставляются, потому что они обе удовлетворяют этим двум условиям:
- "т-рекс" удовлетворяет
diet[1].food
с food = meat иdiet[1].likes
с likes = true - "диплодок" удовлетворяет
diet[1].food
с food = meat иdiet[0].likes
с likes = true
Чтобы получить только точки, которые соответствуют условиям для элементов массива, например, для точки с id 1 в этом примере, необходимо использовать фильтры вложенных объектов.
Фильтры вложенных объектов позволяют независимо запрашивать объектные массивы.
Это можно сделать с помощью типа условия nested
, который состоит из ключа данных, который нас интересует, и фильтра, который нужно применить.
Ключ должен указывать на массив объектов и может опционально использовать скобочную нотацию ("data" или "data[]").
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter": {
"must": [
{
"key": "food",
"match": {
"value": "мясо"
}
},
{
"key": "likes",
"match": {
"value": true
}
}
]
}
}
}
]
}
}
Логика сопоставления изменяется для применения на уровне элементов массива в наборе данных.
Фильтр вложенных объектов работает таким же образом, как при применении фильтра вложенного объекта к одному элементу массива. Пока хотя бы один элемент массива соответствует фильтру vложенных объектов, родительский документ считается соответствующим условию.
Ограничения
Фильтры вложенных объектов не поддерживают условие has_id
. Если вам нужно его использовать, поместите его в смежную клаузу must
.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter": {
"must": [
{
"key": "food",
"match": {
"value": "мясо"
}
},
{
"key": "likes",
"match": {
"value": true
}
}
]
}
}
},
{ "has_id": [1] }
]
}
}
Точное сопоставление текста
Доступно с версии 0.10.0
Специальным случаем условия match
является условие сопоставления text
. Он позволяет вам искать конкретные подстроки, токены или фразы в текстовом поле.
Точные тексты, которые удовлетворяют это условие, зависят от конфигурации полнотекстового индекса. Конфигурация определена при создании индекса и описана внутри полнотекстового индекса.
Если в поле нет полнотекстового индекса, это условие будет работать на основе точного сопоставления подстроки.
{
"key": "description",
"match": {
"text": "хороший и дешевый"
}
}
Если в запросе несколько слов, это условие будет удовлетворено только тогда, когда все слова появляются в тексте.
{
"key": "price",
"range": {
"gt": null,
"gte": 100.0,
"lt": null,
"lte": 450.0
}
}
Условие range
устанавливает возможный диапазон значений для сохраненной полезной нагрузки. Если хранится несколько значений, по крайней мере одно значение должно соответствовать условию.
Доступные операции сравнения включают:
-
gt
- больше -
gte
- больше или равно -
lt
- меньше -
lte
- меньше или равно
Это можно применять к числам с плавающей запятой и целым полезным нагрузкам.
Географический границы прямоугольника
{
"key": "location",
"geo_bounding_box": {
"bottom_right": {
"lat": 52.495862,
"lon": 13.455868
},
"top_left": {
"lat": 52.520711,
"lon": 13.403683
}
}
}
Это соответствует location
внутри прямоугольника с координатами внизу справа как bottom_right
и координатами вверху слева как top_left
.
Географический радиус
{
"key": "location",
"geo_radius": {
"center": {
"lat": 52.520711,
"lon": 13.403683
},
"radius": 1000.0
}
}
Это соответствует location
внутри окружности с центром в center
и радиусом radius
метров.
Если хранится несколько значений, по крайней мере одно значение должно соответствовать условию. Эти условия могут применяться только к полезной нагрузке, соответствующей географическому формату данных.
Количество значений
Помимо непосредственного сравнения значений, фильтрация также может быть основана на количестве значений.
Например, учитывая следующие данные:
[
{ "id": 1, "name": "Product A", "comments": ["Very good!", "Excellent"] },
{ "id": 2, "name": "Product B", "comments": ["Fair", "Expecting more", "Good"] }
]
Мы можем искать только элементы с более чем двумя комментариями:
{
"key": "comments",
"values_count": {
"gt": 2
}
}
Результат будет:
[{ "id": 2, "name": "Product B", "comments": ["Fair", "Expecting more", "Good"] }]
Если хранится не массив, считается, что количество значений равно 1.
Пусто
Иногда полезно фильтровать записи, в которых отсутствуют определенные значения. Условие IsEmpty
может помочь в этом:
{
"is_empty": {
"key": "reports"
}
}
Это условие будет соответствовать всем записям, где поле reports
не существует или имеет значение null
или []
.
IsEmpty часто очень полезен при использовании с логическим отрицанием must_not. В этом случае он будет выбирать все непустые значения.
Нулевое значение
Условие match не может проверять значения NULL
. Мы должны использовать условие IsNull
:
{
"is_null": {
"key": "reports"
}
}
Это условие будет соответствовать всем записям, где поле reports
существует и имеет значение NULL
.
Есть ID
Этот тип запроса не имеет отношения к полезным нагрузкам, но он очень полезен в определенных ситуациях. Например, пользователи могут хотеть пометить определенные результаты поиска как нерелевантные, или мы можем хотеть искать только между определенными точками.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "has_id": [1,3,5,7,9,11] }
]
}
...
}
Отфильтрованные точки будут:
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 3, "city": "London", "color": "blue" },
{ "id": 5, "city": "Moscow", "color": "green" }
]