过滤(filter)
使用Qdrant,您可以在搜索或检索点时设置条件,也就是说除了向量的相似搜索,还可以设置类型sql的where条件,做属性过滤。例如,您可以对点的载荷(payload)和id
都设置条件。
在嵌入中无法表达对象的所有特征时,设置额外条件非常重要。例如:库存可用性、用户位置或期望价格范围等各种业务需求。
过滤条件
Qdrant允许您在子句中组合条件。子句是不同的逻辑操作,例如OR
、AND
和NOT
。子句可以递归嵌套在彼此之间,以便您可以重现任意布尔表达式。
让我们来看看Qdrant中实现的子句。
假设我们有一组具有以下载荷的点:
[
{ "id": 1, "city": "伦敦", "color": "绿色" },
{ "id": 2, "city": "伦敦", "color": "红色" },
{ "id": 3, "city": "伦敦", "color": "蓝色" },
{ "id": 4, "city": "柏林", "color": "红色" },
{ "id": 5, "city": "莫斯科", "color": "绿色" },
{ "id": 6, "city": "莫斯科", "color": "蓝色" }
]
必须(must)
示例:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "key": "city", "match": { "value": "伦敦" } },
{ "key": "color", "match": { "value": "红色" } }
]
}
...
}
过滤后的点将是:
[{ "id": 2, "city": "伦敦", "color": "红色" }]
使用must
时,只有在must
中列出的每个条件都满足时,子句才为true
。在这个意义上,must
等同于运算符AND
。
应该(should)
should类似sql的or
示例:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{ "key": "city", "match": { "value": "伦敦" } },
{ "key": "color", "match": { "value": "红色" } }
]
}
...
}
过滤后的点将是:
[
{ "id": 1, "city": "伦敦", "color": "绿色" },
{ "id": 2, "city": "伦敦", "color": "红色" },
{ "id": 3, "city": "伦敦", "color": "蓝色" },
{ "id": 4, "city": "柏林", "color": "红色" }
]
使用should
时,只要在should
中列出的至少一个条件满足,子句就为true
。在这个意义上,should
等同于运算符OR
。
不应该(must_not)
示例:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must_not": [
{ "key": "city", "match": { "value": "伦敦" } },
{ "key": "color", "match": { "value": "红色" } }
]
}
...
}
过滤后的点将是:
[
{ "id": 5, "city": "莫斯科", "color": "绿色" },
{ "id": 6, "city": "莫斯科", "color": "蓝色" }
]
使用must_not
时,只有当must_not
中列出的条件没有一个满足时,子句才为true
。在这个意义上,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" }
]
条件过滤
在有效载荷(payload)中,不同类型的值对应于可以应用于它们的不同类型的查询。让我们看一下现有的条件变体以及它们适用的数据类型。
匹配
{
"key": "color",
"match": {
"value": "red"
}
}
对于其他类型,匹配条件看起来完全一样,只是使用的类型不同:
{
"key": "count",
"match": {
"value": 0
}
}
最简单的条件是检查存储的值是否等于给定的值。如果存储了多个值,则其中至少有一个值应满足条件。您可以将其应用于关键词、整数和布尔载荷。
任意匹配
自v1.1.0起可用
如果您想检查存储的值是否是多个值中的一个,可以使用 任意匹配条件。任意匹配将给定的值视为逻辑OR运算。它也可以描述为IN
运算符。
您可以将其应用于关键词和整数载荷。
例子:
{
"key": "color",
"match": {
"any": ["black", "yellow"]
}
}
在此示例中,如果存储的值为black
或yellow
,则该条件将满足。
如果存储的值是一个数组,则它应至少具有一个值与给定值中的任何一个匹配。例如,如果存储的值是["black", "green"]
,则该条件将满足,因为"black"
在["black", "yellow"]
中。
排除匹配
自v1.2.0起可用
如果您想检查存储的值是否不是多个值中的任何一个,可以使用 排除匹配条件。排除匹配将给定的值视为逻辑NOR运算。它也可以描述为NOT IN
运算符。
您可以将其应用于关键词和整数载荷。
例子:
{
"key": "color",
"match": {
"except": ["black", "yellow"]
}
}
在此示例中,如果存储的值既不是black
也不是yellow
,则该条件将满足。
如果存储的值是一个数组,则它应至少具有一个值不与给定值中的任何一个匹配。例如,如果存储的值是["black", "green"]
,则该条件将满足,因为"green"
不匹配"black"
或"yellow"
。
嵌套键
从v1.1.0开始可用
由于负载是任意的JSON对象,您可能需要对嵌套字段进行过滤。
为了方便起见,我们使用了类似于Jq项目中的语法。
假设我们有一组带有以下负载的点:
[
{
"id": 1,
"country": {
"name": "Germany",
"cities": [
{
"name": "Berlin",
"population": 3.7,
"sightseeing": ["Brandenburg Gate", "Reichstag"]
},
{
"name": "Munich",
"population": 1.5,
"sightseeing": ["Marienplatz", "Olympiapark"]
}
]
}
},
{
"id": 2,
"country": {
"name": "Japan",
"cities": [
{
"name": "Tokyo",
"population": 9.3,
"sightseeing": ["Tokyo Tower", "Tokyo Skytree"]
},
{
"name": "Osaka",
"population": 2.7,
"sightseeing": ["Osaka Castle", "Universal Studios Japan"]
}
]
}
}
]
您可以使用点符号来搜索嵌套字段。
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.name",
"match": {
"value": "Germany"
}
}
]
}
}
您还可以使用[]
语法通过投影内部值来搜索数组。
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.cities[].population",
"range": {
"gte": 9.0,
}
}
]
}
}
此查询仅输出id为2的点,因为只有日本有一个城市的人口大于9.0。
嵌套字段也可以是一个数组。
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.cities[].sightseeing",
"match": {
"value": "Osaka Castle"
}
}
]
}
}
此查询仅输出id为2的点,因为只有日本有一个城市的观光景点中包含“大阪城”。
嵌套对象过滤
从版本1.2.0开始可用
默认情况下,条件会考虑到一个点的整个payload(有效负载)。
比如,给定以下payload的两个点:
[
{
"id": 1,
"dinosaur": "t-rex",
"diet": [
{ "food": "leaves", "likes": false},
{ "food": "meat", "likes": true}
]
},
{
"id": 2,
"dinosaur": "diplodocus",
"diet": [
{ "food": "leaves", "likes": true},
{ "food": "meat", "likes": false}
]
}
]
下面的查询会匹配这两个点:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{
"key": "diet[].food",
"match": {
"value": "meat"
}
},
{
"key": "diet[].likes",
"match": {
"value": true
}
}
]
}
}
之所以匹配上面的两个点是因为它们都满足了这两个条件:
- “t-rex”满足
diet[1].food
上的food=meat,以及diet[1].likes
上的likes=true - “diplodocus”满足
diet[1].food
上的food=meat,以及diet[0].likes
上的likes=true
为了仅获取与数组元素条件匹配的点,也就是在这个例子中id为1的点,你需要使用嵌套对象过滤器。
嵌套对象过滤器允许独立查询对象数组。
可以通过使用nested
条件类型来实现,该类型由要关注的payload键和要应用的过滤器组成。
该键应该指向一个对象数组,并且可以使用或不使用括号表示法(“data”或“data[]”)。
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter":{
"must": [
{
"key": "food",
"match": {
"value": "meat"
}
},
{
"key": "likes",
"match": {
"value": true
}
}
]
}
}
}
]
}
}
匹配逻辑修改为在payload内的数组元素级别应用。
嵌套过滤器的工作方式与将嵌套过滤器应用于数组的单个元素相同。只要数组的至少一个元素与嵌套过滤器匹配,就认为父文档与条件匹配。
限制
嵌套对象过滤器不支持has_id
条件。如果你需要使用它,请将它放在相邻的must
子句中。
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter":{
"must": [
{
"key": "food",
"match": {
"value": "meat"
}
},
{
"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
}
}
}
它与坐标为bottom_right
的右下角和坐标为top_left
的左上角的矩形内的location
匹配。
地理半径
{
"key": "location",
"geo_radius": {
"center": {
"lat": 52.520711,
"lon": 13.403683
},
"radius": 1000.0
}
}
它与中心在center
且半径为radius
米的圆内的location
匹配。
如果存储了多个值,则至少有一个值应该与条件匹配。这些条件只能应用于与地理数据格式匹配的负载。
值计数
除了直接的值比较,还可以根据值的数量进行过滤。
例如,给定以下数据:
[
{ "id": 1, "name": "产品A", "comments": ["非常好!", "优秀"] },
{ "id": 2, "name": "产品B", "comments": ["一般", "期望更多", "好"] }
]
我们可以仅在具有超过两个评论的项目中进行搜索:
{
"key": "comments",
"values_count": {
"gt": 2
}
}
结果将是:
[{ "id": 2, "name": "产品B", "comments": ["一般", "期望更多", "好"] }]
如果存储的值不是一个数组,假设值的数量等于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": "伦敦", "color": "绿色" },
{ "id": 3, "city": "伦敦", "color": "蓝色" },
{ "id": 5, "city": "莫斯科", "color": "绿色" }
]