发布于 2026-01-06 0 阅读
0

Vector Search 筛选完整指南 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

向量搜索中过滤的完整指南

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

假设你销售电脑硬件。为了帮助顾客轻松地在你的网站上找到产品,你需要一个用户友好的搜索引擎

矢量搜索电子商务

如果你销售电脑,并且拥有关于笔记本电脑、台式机和配件的大量数据,那么你的搜索功能应该引导客户找到他们想要的确切设备,或者找到非常相似的匹配项。

在 Qdrant 中存储数据时,每个产品都是一个点,由一个id、一个vector和组成payload

{
  "id": 1, 
  "vector": [0.1, 0.2, 0.3, 0.4],
  "payload": {
    "price": 899.99,
    "category": "laptop"
  }
}
Enter fullscreen mode Exit fullscreen mode

id是集合中该点的唯一标识符。这vector是与集合中其他点相似度的数学表示。
最后,payload包含直接描述该点的元数据。

虽然我们可能无法解读向量,但我们可以从其元数据中推导出有关该物品的更多信息。在本例中,我们正在查看一台售价为 899.99 美元的笔记本电脑的数据点

什么是过滤?

当客户搜索理想的电脑时,他们最终可能会得到一些在数学上与搜索词相似但不完全相同的结果。例如,如果他们搜索的是价格低于 1000 美元的笔记本电脑,那么即使使用简单的向量搜索而不设任何限制,也可能仍然会显示价格高于 1000 美元的其他笔记本电脑。

这就是为什么仅靠语义搜索可能不够的原因。为了获得精确的结果,您需要对有效载荷施加过滤条件price。只有这样,才能确保搜索结果符合所选特征。

这称为过滤,是矢量数据库的关键特性之一。
下面展示了过滤后的矢量搜索的底层工作原理。我们将在下一节详细介绍其机制。

POST /collections/online_store/points/search
{
  "vector": [ 0.2, 0.1, 0.9, 0.7 ],
  "filter": {
    "must": [
      {
        "key": "category",
        "match": { "value": "laptop" }
      },
      {
        "key": "price",
        "range": {
          "gt": null,
          "gte": null,
          "lt": null,
          "lte": 1000
        }
      }
    ]
  },
  "limit": 3,
  "with_payload": true,
  "with_vector": false
}
Enter fullscreen mode Exit fullscreen mode

过滤后的结果将是语义搜索和对查询施加的过滤条件的结合。在接下来的篇幅中,我们将阐述过滤是向量搜索中的一项关键实践,原因有二:

  1. 通过筛选功能,您可以显著提高搜索精度。下一节将对此进行更详细的介绍。
  2. 过滤有助于控制资源并减少计算资源的使用。更多相关内容请参见有效载荷索引

本指南将教会你以下内容:

向量搜索中,过滤和排序之间的相互依赖性比传统数据库中更强。虽然像 SQL 这样的数据库使用诸如 `filter` 和 `sort` 之类的命令WHEREORDER BY但向量搜索中这些过程之间的相互作用要复杂得多。

大多数人使用默认设置,构建的矢量搜索应用程序配置不当,甚至没有针对精确检索进行设置。在本指南中,我们将向您展示如何使用过滤功能,通过一些易于实施的基本和高级策略,最大限度地发挥矢量搜索的优势。

请记住在 Qdrant 的控制面板中运行所有教程代码。

实现“Hello World”功能最简单的方法就是在实际集群中尝试过滤。我们的交互式教程将向您展示如何创建集群、添加数据并尝试一些过滤语句。

qdrant过滤教程

Qdrant的过滤方法

Qdrant 采用特定的方法,通过密集向量进行搜索和过滤。

我们来看一下这个三阶段图。在这个例子中,我们试图找到查询向量(绿色)的最近邻。您的搜索过程从底部(橙色)开始。

默认情况下,Qdrant 会将向量索引内的所有数据点连接起来引入过滤器后,部分数据点会断开连接。向量搜索无法跨越灰色区域,也无法找到最近邻。
我们该如何弥合这一鸿沟?

图 1: Qdrant 如何维护可过滤的向量索引。
可过滤向量索引

可过滤向量索引:此技术在剩余数据点之间建立额外的链接(橙色)。被过滤后的数据点现在可以再次访问。Qdrant 使用特殊的基于类别的方法来连接这些数据点。

Qdrant 的方法与传统过滤方法的比较

步进透镜

Qdrant 的可过滤向量索引通过在搜索图中添加专用链接,解决了预过滤和后过滤的问题。它旨在保持向量搜索的速度优势,同时实现精确过滤,从而解决在向量搜索后应用过滤器时可能出现的效率低下问题。

预过滤

在预过滤阶段,搜索引擎首先根据选定的元数据值缩小数据集范围,然后在过滤后的子集中进行搜索。这可以减少对可能规模更大的数据集进行不必要的计算。

预过滤和使用可过滤的 HNSW 索引之间的选择取决于过滤器的基数。当元数据基数过低时,过滤器会变得过于严格,并可能破坏图内的连接。这会导致搜索路径碎片化(如图1所示)。语义搜索过程开始时,将无法到达这些位置。

然而,在某些情况下,Qdrant 仍然可以从预过滤中获益。当数据基数较低时,Qdrant 的查询规划器会停止使用 HNSW,而仅使用有效载荷索引。这使得搜索过程比使用 HNSW 时更加高效快捷。

图 2:在用户端,筛选过程如下所示。我们从五款价格不同的产品开始。首先,应用 1000 美元的价格筛选条件,缩小笔记本电脑的选择范围。然后,通过向量搜索在这个筛选后的集合中找到相关结果

预过滤向量搜索

总之,预过滤在处理基数较低的元数据的小型数据集时非常有效。然而,预过滤不应用于大型数据集,因为它会破坏 HNSW 图中的过多链接,从而导致准确率降低。

后过滤

在后过滤中,搜索引擎首先查找相似向量并检索更大的结果集。然后,它根据元数据对这些结果应用过滤器。当使用低基数过滤器时,后过滤的问题就显现出来了。

在执行向量搜索后应用低基数过滤器时,通常会丢弃向量搜索返回的大部分结果。

图 3:在同一个示例中,我们有五台笔记本电脑。首先,向量搜索找到了最相关的两个结果,但它们可能不符合价格匹配条件。应用1000 美元的价格过滤器后,其他潜在结果将被丢弃。

后过滤向量搜索

该系统会浪费计算资源,因为它会先找到相似的向量,然后丢弃许多不符合筛选条件的向量。此外,您只能从初始向量搜索结果集中进行筛选。如果您想要查找的项不在初始结果集中,即使它们存在于数据库中,您也无法找到它们。

基本筛选示例:电子商务和笔记本电脑

我们知道有三款笔记本电脑符合我们的预算。
接下来,让我们看看 Qdrant 的可过滤向量索引是如何工作的,以及为什么它是获取所有可用结果的最佳方法。

首先,在您的网店中添加五款新笔记本电脑。以下是一个示例输入:

laptops = [
    (1, [0.1, 0.2, 0.3, 0.4], {"price": 899.99, "category": "laptop"}),
    (2, [0.2, 0.3, 0.4, 0.5], {"price": 1299.99, "category": "laptop"}),
    (3, [0.3, 0.4, 0.5, 0.6], {"price": 799.99, "category": "laptop"}),
    (4, [0.4, 0.5, 0.6, 0.7], {"price": 1099.99, "category": "laptop"}),
    (5, [0.5, 0.6, 0.7, 0.8], {"price": 949.99, "category": "laptop"})
]
Enter fullscreen mode Exit fullscreen mode

这个四维向量可以表示笔记本电脑的CPU、内存或电池续航时间等特征,但具体内容并未明确说明。然而,有效载荷则明确指定了确切的价格和产品类别。

现在,将筛选条件设置为“价格低于 1000 美元”:

{
  "key": "price",
  "range": {
    "gt": null,
    "gte": null,
    "lt": null,
    "lte": 1000
  }
}
Enter fullscreen mode Exit fullscreen mode

当应用价格筛选条件为等于或小于 1000 美元时,向量搜索返回以下结果:

[
  {
    "id": 3,
    "score": 0.9978443564622781,
    "payload": {
      "price": 799.99,
      "category": "laptop"
    }
  },
  {
    "id": 1,
    "score": 0.9938079894227599,
    "payload": {
      "price": 899.99,
      "category": "laptop"
    }
  },
  {
    "id": 5,
    "score": 0.9903751498208603,
    "payload": {
      "price": 949.99,
      "category": "laptop"
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

如您所见,Qdrant 的过滤方法更有可能捕获所有可能的搜索结果。

这个例子使用了range条件过滤。然而,Qdrant 还提供了许多其他构建过滤器的方法。

有关详细使用示例,过滤文档是最好的资源。

滚动浏览而非搜索

您无需使用我们的searchAPIquery来筛选数据。APIscroll是另一种选择,可用于检索符合筛选条件的点列表。

如果您不想查找相似点,可以直接列出符合特定筛选条件的点。搜索功能会根据查询向量为您提供最相似的点,而滚动浏览则会显示所有符合筛选条件的点,而不考虑相似性。

在 Qdrant 中,滚动用于从集合中迭代地检索大量点。当处理大量点且不想一次性全部加载时,滚动尤其有用。Qdrant 提供了一种逐页滚动浏览点的方法。

首先,向 Qdrant 发送滚动请求,并指定特定条件,例如按有效载荷筛选、矢量搜索或其他标准。

让我们获取商店中按价格排序的前 10 款笔记本电脑列表:

POST /collections/online_store/points/scroll
{
    "filter": {
        "must": [
            {
                "key": "category",
                "match": {
                    "value": "laptop"
                }
            }
        ]
    },
    "limit": 10,
    "with_payload": true,
    "with_vector": false,
    "order_by": [
        {
            "key": "price",
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

响应包含一批符合条件的点,以及用于检索下一组点的引用(偏移量或下一页标记)。

滚动设计旨在提高效率。它每次只返回可管理的数据块,从而最大限度地减少服务器负载并降低客户端内存消耗。

可用的过滤条件

健康)状况 用法 健康)状况 用法
匹配 数值完全匹配。 范围 按数值范围筛选。
匹配任意 匹配多个值。 日期时间范围 按日期范围筛选。
比赛例外 排除特定值。 UUID匹配 按唯一ID筛选。
嵌套键 按嵌套数据筛选。 地理 按地点筛选。
嵌套对象 按嵌套对象筛选。 数值计数 按元素数量筛选。
全文匹配 在文本框中搜索。 为空 过滤掉空字段。
有ID 按唯一ID筛选。 为空 过滤掉空值。

所有条款和条件均在 Qdrant 的过滤文档中列出

需要记住的过滤条件

条款 描述 条款 描述
必须 包括符合条件的物品(类似于AND)。 应该 如果满足至少一个条件,则进行筛选(类似于OR)。
不能 不包括符合以下条件的商品(类似于NOT)。 条款组合 结合多个子句来细化筛选(类似于AND)。

高级筛选示例:恐龙的饮食

高级有效载荷过滤

我们还可以使用嵌套过滤来查询有效负载中的对象数组。在这个例子中,我们有两个点。它们分别代表一只恐龙,并附有食物偏好(饮食)列表,表明它们喜欢或不喜欢哪种类型的食物:

[
  {
    "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}
    ]
  }
]
Enter fullscreen mode Exit fullscreen mode

为确保两个条件都应用于同一个数组元素(例如,food = meat 和 likes = true 必须指代同一个饮食项目),您需要使用嵌套过滤器。

嵌套过滤器用于在对象数组中应用条件。它们确保条件是针对每个数组元素单独评估的,而不是针对所有元素。

POST /collections/dinosaurs/points/scroll
{
    "filter": {
        "must": [
            {
                "key": "diet[].food",
                  "match": {
                    "value": "meat"
                }
            },
            {
                "key": "diet[].likes",
                  "match": {
                    "value": true
                }
            }
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode
client.scroll(
    collection_name="dinosaurs",
    scroll_filter=models.Filter(
        must=[
            models.FieldCondition(
                key="diet[].food", match=models.MatchValue(value="meat")
            ),
            models.FieldCondition(
                key="diet[].likes", match=models.MatchValue(value=True)
            ),
        ],
    ),
)
Enter fullscreen mode Exit fullscreen mode
client.scroll("dinosaurs", {
  filter: {
    must: [
      {
        key: "diet[].food",
        match: { value: "meat" },
      },
      {
        key: "diet[].likes",
        match: { value: true },
      },
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode
use qdrant_client::qdrant::{Condition, Filter, ScrollPointsBuilder};

client
    .scroll(
        ScrollPointsBuilder::new("dinosaurs").filter(Filter::must([
            Condition::matches("diet[].food", "meat".to_string()),
            Condition::matches("diet[].likes", true),
        ])),
    )
    .await?;
Enter fullscreen mode Exit fullscreen mode
import java.util.List;

import static io.qdrant.client.ConditionFactory.match;
import static io.qdrant.client.ConditionFactory.matchKeyword;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.Filter;
import io.qdrant.client.grpc.Points.ScrollPoints;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .scrollAsync(
        ScrollPoints.newBuilder()
            .setCollectionName("dinosaurs")
            .setFilter(
                Filter.newBuilder()
                    .addAllMust(
                        List.of(matchKeyword("diet[].food", "meat"), match("diet[].likes", true)))
                    .build())
            .build())
    .get();
Enter fullscreen mode Exit fullscreen mode
using Qdrant.Client;
using static Qdrant.Client.Grpc.Conditions;

var client = new QdrantClient("localhost", 6334);

await client.ScrollAsync(
    collectionName: "dinosaurs",
    filter: MatchKeyword("diet[].food", "meat") & Match("diet[].likes", true)
);
Enter fullscreen mode Exit fullscreen mode

这是因为这两个点都满足以下两个条件:

  • “t-rex”匹配 food=meatdiet[1].food和 likes=truediet[1].likes
  • “梁龙”与 food=meatdiet[1].food和 likes=true匹配diet[0].likes

要仅检索满足数组中特定元素(例如本例中 id 为 1 的点)条件的点,您需要使用嵌套对象过滤器。

嵌套对象过滤器可以独立查询对象数组,确保在各个数组元素内检查条件。

这是通过使用nested条件类型实现的,条件类型包含一个指向数组的有效负载键和一个要应用的过滤器。该键应引用一个对象数组,并且可以用方括号表示(例如,“data”或“data[]”),也可以不用方括号表示。

POST /collections/dinosaurs/points/scroll
{
    "filter": {
        "must": [{
            "nested": {
                "key": "diet",
                "filter":{
                    "must": [
                        {
                            "key": "food",
                            "match": {
                                "value": "meat"
                            }
                        },
                        {
                            "key": "likes",
                            "match": {
                                "value": true
                            }
                        }
                    ]
                }
            }
        }]
    }
}
Enter fullscreen mode Exit fullscreen mode
client.scroll(
    collection_name="dinosaurs",
    scroll_filter=models.Filter(
        must=[
            models.NestedCondition(
                nested=models.Nested(
                    key="diet",
                    filter=models.Filter(
                        must=[
                            models.FieldCondition(
                                key="food", match=models.MatchValue(value="meat")
                            ),
                            models.FieldCondition(
                                key="likes", match=models.MatchValue(value=True)
                            ),
                        ]
                    ),
                )
            )
        ],
    ),
)
Enter fullscreen mode Exit fullscreen mode
client.scroll("dinosaurs", {
  filter: {
    must: [
      {
        nested: {
          key: "diet",
          filter: {
            must: [
              {
                key: "food",
                match: { value: "meat" },
              },
              {
                key: "likes",
                match: { value: true },
              },
            ],
          },
        },
      },
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode
use qdrant_client::qdrant::{Condition, Filter, NestedCondition, ScrollPointsBuilder};

client
    .scroll(
        ScrollPointsBuilder::new("dinosaurs").filter(Filter::must([NestedCondition {
            key: "diet".to_string(),
            filter: Some(Filter::must([
                Condition::matches("food", "meat".to_string()),
                Condition::matches("likes", true),
            ])),
        }
        .into()])),
    )
    .await?;
Enter fullscreen mode Exit fullscreen mode
import java.util.List;

import static io.qdrant.client.ConditionFactory.match;
import static io.qdrant.client.ConditionFactory.matchKeyword;
import static io.qdrant.client.ConditionFactory.nested;

import io.qdrant.client.grpc.Points.Filter;
import io.qdrant.client.grpc.Points.ScrollPoints;

client
    .scrollAsync(
        ScrollPoints.newBuilder()
            .setCollectionName("dinosaurs")
            .setFilter(
                Filter.newBuilder()
                    .addMust(
                        nested(
                            "diet",
                            Filter.newBuilder()
                                .addAllMust(
                                    List.of(
                                        matchKeyword("food", "meat"), match("likes", true)))
                                .build()))
                    .build())
            .build())
    .get();
Enter fullscreen mode Exit fullscreen mode
using Qdrant.Client;
using static Qdrant.Client.Grpc.Conditions;

var client = new QdrantClient("localhost", 6334);

await client.ScrollAsync(
    collectionName: "dinosaurs",
    filter: Nested("diet", MatchKeyword("food", "meat") & Match("likes", true))
);
Enter fullscreen mode Exit fullscreen mode

匹配逻辑经过调整,可以在有效载荷数组中的各个元素级别上进行操作。

嵌套过滤器的工作方式如同对数组中的每个元素进行单独评估。如果至少有一个数组元素满足嵌套过滤器的条件,则父文档将被视为匹配项。

滤镜的其他创意用途

您可以使用筛选器检索数据点,而无需了解它们的具体含义id。您可以仅使用筛选器来搜索和管理数据。让我们来看一些筛选器的创新用法:

行动 描述 行动 描述
删除积分 删除所有符合筛选条件的点。 设置有效载荷 为所有符合筛选条件的点添加有效载荷字段。
滚动点 列出所有符合筛选条件的点。 更新有效载荷 更新符合筛选条件的点的有效载荷字段。
订单点 列出所有点,并按筛选条件排序。 删除有效载荷 删除符合筛选条件的点的字段。
计算分数 计算符合筛选条件的分数总和。

使用有效载荷索引进行过滤

向量搜索过滤向量搜索

当您开始使用 Qdrant 时,您的数据默认以向量索引的形式组织。
除此之外,我们建议您添加一个辅助数据结构——有效载荷索引

正如向量索引组织向量一样,有效载荷索引将组织你的元数据。

图 4:有效载荷索引是一种支持向量搜索的附加数据结构。有效载荷索引(绿色部分)按基数组织候选结果,以便语义搜索(红色部分)能够快速遍历向量索引。

有效载荷索引向量搜索

对TB级数据进行语义搜索本身就会占用大量内存。过滤索引是两种简单易行的策略,既能降低计算资源占用,又能获得最佳搜索结果。请记住,这只是一个指南。如需查看完整的过滤选项列表,请阅读过滤文档

以下是如何为元数据字段“category”创建单个索引的方法:

PUT /collections/computers/index
{
    "field_name": "category",
    "field_schema": "keyword"
}
Enter fullscreen mode Exit fullscreen mode
from qdrant_client import QdrantClient

client = QdrantClient(url="http://localhost:6333")

client.create_payload_index(
   collection_name="computers",
   field_name="category",
   field_schema="keyword",
)
Enter fullscreen mode Exit fullscreen mode

将字段标记为可索引后,无需执行任何其他操作。Qdrant 会在后台处理所有优化。

为什么要索引元数据?

有效载荷索引过滤

有效载荷索引作为一种辅助数据结构,可以加快检索速度。每当您使用过滤器运行向量搜索时,Qdrant 都会查询有效载荷索引(如果存在)。

如果您对元数据进行索引,搜索性能的提升可能会非常显著。

随着数据集复杂性的增加,Qdrant 需要占用更多资源来遍历所有数据点。如果没有合适的数据结构,搜索可能会耗时更长,甚至耗尽资源。

有效载荷索引有助于评估最严格的过滤器

有效载荷索引还用于精确估计过滤器基数,这有助于查询规划选择合适的搜索策略。过滤器基数指的是过滤器在数据集中可以匹配的不同值的数量。如果基数过低, Qdrant 的搜索策略可以从基于 HNSW 的搜索切换基于有效载荷索引的搜索。

它如何影响您的查询:根据搜索中使用的筛选条件,查询执行可能有多种方案。Qdrant 会根据可用索引、条件的复杂性和筛选结果的基数选择其中一种查询执行方案。

  • 规划器在选择策略之前会估计过滤结果的基数。
  • 如果基数低于阈值,Qdrant 将使用有效载荷索引检索点。
  • 如果基数大于阈值,Qdrant 将使用可过滤向量索引。

如果不使用有效负载索引会发生什么?

如果仅依赖于搜索最近向量,Qdrant 将需要遍历整个向量索引。它会计算集合中每个向量的相似度,无论是否相关。而使用有效载荷索引进行过滤时,HSNW 算法则无需评估每个点。此外,有效载荷索引还能帮助 HNSW 构建包含更多链接的图。

有效载荷索引看起来是什么样的?

有效载荷索引类似于传统的面向文档的数据库。它将元数据字段与其对应的点 ID 连接起来,以便快速检索。

在这个例子中,你要把所有计算机硬件都索引到集合中computers。我们来看一个字段的示例有效负载索引category

Payload Index by keyword:
+------------+-------------+
| category   | id          |
+------------+-------------+
| laptop     | 1, 4, 7     |
| desktop    | 2, 5, 9     |
| speakers   | 3, 6, 8     |
| keyboard   | 10, 11      |
+------------+-------------+
Enter fullscreen mode Exit fullscreen mode

当字段被正确索引后,搜索引擎就能大致知道从哪里开始搜索。它可以从包含相关元数据的点开始查找,而无需扫描整个数据集。这大大减轻了引擎的负担。因此,查询结果响应速度更快,系统也更容易扩展。

您可以创建任意数量的有效负载索引,我们建议您为每个常用字段都创建一个索引。
如果您的用户在查找产品类别时经常按笔记本电脑进行筛选,那么对所有计算机元数据进行索引将加快检索速度并提高结果的精确度。

不同类型的有效载荷索引

索引类型 描述
全文索引 支持对大型数据集进行高效的文本搜索。
租户指数 针对多租户架构中的数据隔离和检索效率。
主要指数 根据用户或帐户等主要实体管理数据。
磁盘索引 将索引存储在磁盘上,以便在不占用内存的情况下管理大型数据集。
参数化索引 支持动态查询,索引可以根据用户提供的不同参数或条件进行调整。适用于价格或时间戳等数值数据。

在多租户环境中索引有效载荷

有些应用程序需要进行数据隔离,即不同的用户需要在同一程序中查看不同的数据。在为这类复杂的应用程序设置存储时,许多用户认为需要为不同的用户分别设置多个数据库。

我们经常遇到这种情况。用户经常犯的错误是,在同一个集群中为每个租户创建一个单独的集合。这会迅速耗尽集群资源。对过多的集合运行向量搜索会占用过多内存。您可能会遇到内存不足 (OOM) 错误和性能下降的情况。

为了缓解这个问题,我们为多租户系统提供广泛的支持,以便您可以在单个 Qdrant 集合中构建整个全球应用程序。

PUT /collections/{collection_name}/index
{
   "field_name": "payload_field_name",
   "field_schema": {
       "type": "keyword",
       "is_tenant": true
   }
}
Enter fullscreen mode Exit fullscreen mode

租户索引是有效负载索引的另一种变体。创建或更新集合时,您可以将元数据字段标记为可索引。此时,请求会将该字段指定为租户。这意味着您可以将各种用户类型和客户 ID 标记为is_tenant“可索引”。

过滤和索引的关键要点

最佳实践

使用浮点(十进制)数进行过滤

如果按浮点数据类型进行筛选,搜索精度可能会受到限制,导致搜索不准确。

浮点型数据类型的数字带有小数点,长度为 64 位。例如:

{
   "price": 11.99
}
Enter fullscreen mode Exit fullscreen mode

当您筛选某个特定的浮点数,例如 11.99 时,可能会得到不同的结果,例如 11.98 或 12.00。这是因为小数的舍入方式不同,逻辑上相同的值可能显示为不同的结果。在这种情况下,精确匹配的搜索可能不太可靠。

为避免误差,请使用其他过滤方法。我们建议您尝试使用范围过滤而非精确匹配。这种方法可以处理数据中的细微差异,并显著提升性能,尤其是在处理大型数据集时。

以下是一个 JSON 范围筛选示例,用于筛选大于等于 11.99 和小于等于 11.99 的值。这将检索所有在 11.99 范围内的值,包括小数位数更多的值。

{
 "key": "price",
 "range": {
   "gt": null,
   "gte": 11.99,
   "lt": null,
   "lte": 11.99
  }
}
Enter fullscreen mode Exit fullscreen mode

在查询中使用分页

在筛选查询中实现分页时,索引变得尤为重要。对结果进行分页时,通常需要排除已查看过的条目。这通常通过应用过滤器来实现,过滤器会指定哪些 ID 不应包含在下一组结果中。

然而,Qdrant 数据模型的一个有趣之处在于,同一个字段可以有多个值,例如同一产品的不同颜色选项。这意味着在筛选过程中,如果一个 ID 匹配到同一字段的不同值,则该 ID 可能会出现多次。

正确的索引可以确保这些查询高效,防止出现重复结果,并使分页更加流畅。

结论:过滤的实际应用案例

在 Qdrant 等向量数据库中进行过滤可以显著增强搜索能力,从而实现更精确、更高效的数据检索。

作为本指南的总结,让我们来看一些过滤功能至关重要的实际应用案例:

用例 向量搜索 过滤
电子商务产品搜索 按风格或视觉相似度搜索产品 按价格、颜色、品牌、尺寸、评分筛选
推荐系统 推荐类似内容(例如,电影、歌曲) 按上映日期、类型等筛选(例如,2020 年以后的电影)
共享出行中的地理空间搜索 寻找类似的司机或配送合作伙伴 按评分、距离半径、车辆类型筛选
欺诈和异常检测 检测与已知欺诈案件类似的交易 按金额、时间、地点筛选

在你离开之前——所有代码都在 Qdrant 的控制面板中。

实现“Hello World”功能最简单的方法就是在实际集群中尝试过滤。我们的交互式教程将向您展示如何创建集群、添加数据并尝试一些过滤语句。

文章来源:https://dev.to/qdrant/a-complete-guide-to-filtering-in-vector-search-33lk