Filtre
Avec Qdrant, vous pouvez définir des conditions pour rechercher ou récupérer des points, ce qui signifie que vous pouvez filtrer par attribut en plus des recherches de similarité pour les vecteurs, tout comme définir des conditions SQL where
. Par exemple, vous pouvez définir des conditions pour la charge utile et l'id
du point.
Il est important de définir des conditions supplémentaires lorsque toutes les fonctionnalités d'un objet ne peuvent pas être exprimées dans un embedding. Par exemple, divers besoins commerciaux tels que la disponibilité des stocks, la localisation de l'utilisateur ou la plage de prix attendue.
Conditions de filtre
Qdrant vous permet de combiner des conditions dans des clauses. Les clauses sont différentes opérations logiques, telles que OR
, AND
, et NOT
. Les clauses peuvent être imbriquées de manière récursive, vous permettant ainsi de recréer n'importe quelle expression booléenne.
Jetons un coup d'œil aux clauses implémentées dans Qdrant.
Supposons que nous disposions d'un ensemble de points avec des charges utiles :
[
{ "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": "Moscou", "color": "green" },
{ "id": 6, "city": "Moscou", "color": "blue" }
]
Doit
Exemple :
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Le point filtré sera :
[{ "id": 2, "city": "London", "color": "red" }]
Lors de l'utilisation de must
, la clause est true
seulement si chaque condition répertoriée dans must
est satisfaite. En ce sens, must
est équivalent à l'opérateur AND
.
Devrait
Should
est similaire à l'opérateur OR
en SQL.
Exemple :
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Les points filtrés seront :
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 2, "city": "London", "color": "red" },
{ "id": 3, "city": "London", "color": "blue" },
{ "id": 4, "city": "Berlin", "color": "red" }
]
Lors de l'utilisation de should
, la clause est true
tant qu'au moins une condition répertoriée dans should
est satisfaite. En ce sens, should
est équivalent à l'opérateur OR
.
ne doit pas
Exemple :
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must_not": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Les points filtrés seront :
[
{ "id": 5, "city": "Moscou", "color": "green" },
{ "id": 6, "city": "Moscou", "color": "blue" }
]
Lors de l'utilisation de must_not
, la sous-clause est true
seulement si aucune des conditions répertoriées dans must_not
n'est satisfaite. En ce sens, must_not
est équivalent à l'expression (NON A) ET (NON B) ET (NON C)
.
Combinaison de conditions
Il est possible d'utiliser plusieurs conditions simultanément :
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "key": "city", "match": { "value": "London" } }
],
"must_not": [
{ "key": "color", "match": { "value": "red" } }
]
}
...
}
Les points filtrés seront :
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 3, "city": "London", "color": "blue" }
]
Dans ce cas, les conditions sont combinées en utilisant ET
(AND).
De plus, les conditions peuvent être imbriquées de manière récursive. Par exemple :
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must_not": [
{
"must": [
{ "key": "city", "match": { "value": "London" } },
{ "key": "color", "match": { "value": "red" } }
]
}
]
}
...
}
Les points filtrés seront :
[
{ "id": 1, "city": "London", "color": "green" },
{ "id": 3, "city": "London", "color": "blue" },
{ "id": 4, "city": "Berlin", "color": "red" },
{ "id": 5, "city": "Moscou", "color": "vert" },
{ "id": 6, "city": "Moscou", "color": "bleu" }
]
Filtrage des conditions
Dans la charge utile, différents types de valeurs correspondent à différents types de requêtes qui peuvent leur être appliquées. Examinons les variantes de conditions existantes et les types de données auxquels elles s'appliquent.
Correspondance
{
"key": "color",
"match": {
"value": "rouge"
}
}
Pour les autres types, les conditions de correspondance se présentent exactement de la même manière, simplement avec des types différents utilisés :
{
"key": "count",
"match": {
"value": 0
}
}
La condition la plus simple vérifie si la valeur stockée est égale à la valeur donnée. Si plusieurs valeurs sont stockées, au moins l'une d'entre elles doit satisfaire la condition. Cela peut s'appliquer aux charges utiles de type mot-clé, entier et booléen.
Toute correspondance
Disponible depuis v1.1.0
Si vous souhaitez vérifier si la valeur stockée est l'une des plusieurs valeurs, vous pouvez utiliser la condition de toute correspondance. La toute correspondance traite la valeur donnée comme une opération logique OU. Elle peut également être décrite comme l'opérateur IN
.
Vous pouvez l'appliquer aux charges utiles de type mot-clé et entier.
Exemple :
{
"key": "color",
"match": {
"any": ["noir", "jaune"]
}
}
Dans cet exemple, si la valeur stockée est noir
ou jaune
, alors la condition sera satisfaite.
Si la valeur stockée est un tableau, il doit avoir au moins une valeur qui correspond à l'une des valeurs données. Par exemple, si la valeur stockée est ["noir", "vert"]
, alors la condition sera satisfaite car "noir"
est dans ["noir", "jaune"]
.
Exclusion de correspondance
Disponible depuis v1.2.0
Si vous souhaitez vérifier si la valeur stockée n'est aucune des valeurs données, vous pouvez utiliser la condition d'exclusion de correspondance. L'exclusion de correspondance traite la valeur donnée comme une opération logique NOR. Elle peut également être décrite comme l'opérateur NOT IN
.
Vous pouvez l'appliquer aux charges utiles de type mot-clé et entier.
Exemple :
{
"key": "color",
"match": {
"except": ["noir", "jaune"]
}
}
Dans cet exemple, si la valeur stockée n'est ni noir
ni jaune
, alors la condition sera satisfait
Si la valeur stockée est un tableau, il doit avoir au moins une valeur qui ne correspond à aucune des valeurs données. Par exemple, si la valeur stockée est ["noir", "vert"]
, alors la condition sera satisfaite car "vert"
ne correspond ni à "noir"
ni à "jaune"
.
Clés imbriquées
Disponible à partir de la version 1.1.0
Étant donné que la charge utile est un objet JSON arbitraire, vous pourriez avoir besoin de filtrer des champs imbriqués.
Pour votre commodité, nous utilisons une syntaxe similaire au projet Jq.
Supposons que nous ayons un ensemble de points avec la charge utile suivante :
[
{
"id": 1,
"country": {
"name": "Allemagne",
"cities": [
{
"name": "Berlin",
"population": 3.7,
"sightseeing": ["Porte de Brandebourg", "Reichstag"]
},
{
"name": "Munich",
"population": 1.5,
"sightseeing": ["Marienplatz", "Parc olympique"]
}
]
}
},
{
"id": 2,
"country": {
"name": "Japon",
"cities": [
{
"name": "Tokyo",
"population": 9.3,
"sightseeing": ["Tour de Tokyo", "Tokyo Skytree"]
},
{
"name": "Osaka",
"population": 2.7,
"sightseeing": ["Château d'Osaka", "Universal Studios Japan"]
}
]
}
}
]
Vous pouvez utiliser la notation point pour rechercher des champs imbriqués.
POST /collections/{nom_collection}/points/défilement
{
"filter": {
"should": [
{
"key": "country.name",
"match": {
"value": "Allemagne"
}
}
]
}
}
Vous pouvez également utiliser la syntaxe [ ]
pour rechercher dans le tableau en projetant des valeurs internes.
POST /collections/{nom_collection}/points/défilement
{
"filter": {
"should": [
{
"key": "country.cities[].population",
"range": {
"gte": 9.0,
}
}
]
}
}
Cette requête ne renvoie que le point avec l'identifiant 2, car seul le Japon a une ville avec une population supérieure à 9,0.
Les champs imbriqués peuvent également être un tableau.
POST /collections/{nom_collection}/points/défilement
{
"filter": {
"should": [
{
"key": "country.cities[].sightseeing",
"match": {
"value": "Château d'Osaka"
}
}
]
}
}
Cette requête ne renvoie que le point avec l'identifiant 2, car seul le Japon a une ville avec un lieu touristique qui inclut "Château d'Osaka".
Filtrage d'Objets Nommés
Disponible depuis la version 1.2.0
Par défaut, les conditions prennent en considération l'ensemble des données d'un point.
Par exemple, étant donné les deux points dans les données ci-dessous :
[
{
"id": 1,
"dinosaur": "t-rex",
"diet": [
{ "food": "feuilles", "aime": false },
{ "food": "viande", "aime": true }
]
},
{
"id": 2,
"dinosaur": "diplodocus",
"diet": [
{ "food": "feuilles", "aime": true },
{ "food": "viande", "aime": false }
]
}
]
La requête suivante correspondrait à ces deux points :
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{
"key": "diet[].food",
"match": {
"value": "viande"
}
},
{
"key": "diet[].aime",
"match": {
"value": true
}
}
]
}
}
Les deux points ci-dessus correspondent car ils satisfont tous deux ces deux conditions :
- "t-rex" satisfait
diet[1].food
avec food = viande etdiet[1].aime
avec aime = true - "diplodocus" satisfait
diet[1].food
avec food = viande etdiet[0].aime
avec aime = true
Pour obtenir uniquement des points qui correspondent aux conditions pour les éléments d'array, par exemple, le point avec l'ID 1 dans cet exemple, vous devez utiliser des filtres d'objets nommés imbriqués.
Les filtres d'objets nommés imbriqués permettent de interroger les tableaux d'objets indépendamment.
Cela peut être réalisé en utilisant le type de condition nested
, qui consiste en la clé des données d'intérêt et le filtre à appliquer.
La clé devrait pointer vers un tableau d'objets et peut éventuellement utiliser la notation entre crochets ("data" ou "data[]") en option.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter": {
"must": [
{
"key": "food",
"match": {
"value": "viande"
}
},
{
"key": "aime",
"match": {
"value": true
}
}
]
}
}
}
]
}
}
La logique de correspondance est modifiée pour s'appliquer au niveau de l'élément du tableau dans les données.
Le filtre imbriqué fonctionne de la même manière que lors de l'application d'un filtre imbriqué à un seul élément d'un tableau. Tant qu'au moins un élément du tableau correspond au filtre imbriqué, le document parent est considéré comme correspondant à la condition.
Limitation
Les filtres d'objets nommés imbriqués ne prennent pas en charge la condition has_id
. Si vous avez besoin de l'utiliser, placez-la dans une clause must
adjacente.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter": {
"must": [
{
"key": "food",
"match": {
"value": "viande"
}
},
{
"key": "aime",
"match": {
"value": true
}
}
]
}
}
},
{ "has_id": [1] }
]
}
}
Correspondance de Texte Exacte
Disponible depuis la version 0.10.0
Un cas particulier de la condition match
est la condition de correspondance de texte
. Elle vous permet de rechercher des sous-chaînes, des jetons ou des phrases spécifiques dans le champ de texte.
Les textes exacts qui satisfont cette condition dépendent de la configuration de l'indexation de texte intégral. La configuration est définie lors de la création de l'index et est décrite dans l'index de texte intégral.
Si le champ n'a pas d'index de texte intégral, cette condition fonctionnera sur la base d'une correspondance exacte de sous-chaîne.
{
"key": "description",
"match": {
"text": "bon et pas cher"
}
}
Si la requête comporte plusieurs mots, cette condition ne sera satisfaite que lorsque tous les mots apparaissent dans le texte.
Plage
{
"key": "price",
"range": {
"gt": null,
"gte": 100.0,
"lt": null,
"lte": 450.0
}
}
La condition range
définit la plage possible de valeurs pour la charge utile stockée. Si plusieurs valeurs sont stockées, au moins une valeur doit correspondre à la condition.
Les opérations de comparaison disponibles incluent :
-
gt
- strictement supérieur à -
gte
- supérieur ou égal à -
lt
- strictement inférieur à -
lte
- inférieur ou égal à
Elle peut être appliquée à des nombres à virgule flottante et à des charges utiles entières.
Boîte de délimitation géographique
{
"key": "location",
"geo_bounding_box": {
"bottom_right": {
"lat": 52.495862,
"lon": 13.455868
},
"top_left": {
"lat": 52.520711,
"lon": 13.403683
}
}
}
Cela correspond à la location
dans le rectangle avec des coordonnées en bas à droite comme bottom_right
et des coordonnées en haut à gauche comme top_left
.
Rayon géographique
{
"key": "location",
"geo_radius": {
"center": {
"lat": 52.520711,
"lon": 13.403683
},
"radius": 1000.0
}
}
Cela correspond à la location
dans le cercle avec le centre à center
et un rayon de radius
mètres.
Si plusieurs valeurs sont stockées, au moins une valeur doit correspondre à la condition. Ces conditions ne peuvent être appliquées qu'à des charges utiles correspondant au format de données géographiques.
Nombre de valeurs
En plus de la comparaison de valeur directe, le filtrage peut également être basé sur le nombre de valeurs.
Par exemple, étant donné les données suivantes :
[
{ "id": 1, "name": "Produit A", "comments": ["Très bon!", "Excellent"] },
{ "id": 2, "name": "Produit B", "comments": ["Correct", "Attendant plus", "Bien"] }
]
Nous pouvons rechercher uniquement les éléments avec plus de deux commentaires :
{
"key": "comments",
"values_count": {
"gt": 2
}
}
Le résultat sera :
[{ "id": 2, "name": "Produit B", "comments": ["Correct", "Attendant plus", "Bien"] }]
Si la valeur stockée n'est pas un tableau, on suppose que le nombre de valeurs est égal à 1.
Est vide
Parfois, il est utile de filtrer les enregistrements qui manquent de certaines valeurs. La condition IsEmpty
peut vous aider à y parvenir :
{
"is_empty": {
"key": "reports"
}
}
Cette condition correspondra à tous les enregistrements où le champ reports
n'existe pas ou a une valeur de null
ou []
.
IsEmpty est souvent très utile lorsqu'il est utilisé en conjonction avec la négation logique must_not. Dans ce cas, il sélectionnera toutes les valeurs non vides.
Est nul
La condition match ne peut pas tester les valeurs NULL
. Nous devons utiliser la condition IsNull
:
{
"is_null": {
"key": "reports"
}
}
Cette condition correspondra à tous les enregistrements où le champ reports
existe et a une valeur de NULL
.
A un ID
Ce type de requête n'est pas lié aux charges utiles, mais il est très utile dans certaines situations. Par exemple, les utilisateurs peuvent vouloir étiqueter certains résultats de recherche comme non pertinents, ou nous ne voulons peut-être rechercher qu'entre des points spécifiques.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "has_id": [1,3,5,7,9,11] }
]
}
...
}
Les points filtrés seront :
[
{ "id": 1, "ville": "Londres", "couleur": "verte" },
{ "id": 3, "ville": "Londres", "couleur": "bleue" },
{ "id": 5, "ville": "Moscou", "couleur": "verte" }
]