一架梯子,一头程序猿,仰望星空!
Qdrant向量数据库教程 > 内容正文

Qdrant属性过滤(Filtering)


过滤(filter)

使用Qdrant,您可以在搜索或检索点时设置条件,也就是说除了向量的相似搜索,还可以设置类型sql的where条件,做属性过滤。例如,您可以对点的载荷(payload)和id都设置条件。

在嵌入中无法表达对象的所有特征时,设置额外条件非常重要。例如:库存可用性、用户位置或期望价格范围等各种业务需求。

过滤条件

Qdrant允许您在子句中组合条件。子句是不同的逻辑操作,例如ORANDNOT。子句可以递归嵌套在彼此之间,以便您可以重现任意布尔表达式。

让我们来看看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"]
  }
}

在此示例中,如果存储的值为blackyellow,则该条件将满足。

如果存储的值是一个数组,则它应至少具有一个值与给定值中的任何一个匹配。例如,如果存储的值是["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": "绿色" }
]


关联主题