在 Azure AI 搜索中为复杂数据类型建模

用于填充 Azure AI 搜索索引的外部数据集可以采用多种形状。 有时它们包含分层或嵌套的子结构。 示例包括单个客户的多个地址、单个 SKU 的多个颜色和大小、一本书籍的多位作者等等。 在建模术语中,这些结构可能称作复杂、组合、复合或聚合数据类型。 Azure AI 搜索对此概念使用的术语是“复杂类型”。 在 Azure AI 搜索中,复杂类型是使用复杂字段建模的。 复杂字段是包含子级(子字段)的字段,这些子级可以是任何数据类型(包括其他复杂类型)。 其工作原理类似于编程语言中的结构化数据类型。

复杂字段表示文档中的单个对象,或对象的数组,具体取决于数据类型。 Edm.ComplexType 类型的字段表示单个对象,而 Collection(Edm.ComplexType) 类型的字段表示对象的数组。

Azure AI 搜索原生支持复杂类型和集合。 使用这些类型几乎可为 Azure AI 搜索索引中的任何 JSON 结构建模。 在旧版的 Azure AI 搜索 API 中,只能导入平展的行集。 在最新版本中,索引可以更密切地对应于源数据。 换言之,如果源数据使用复杂类型,则索引也可以使用复杂类型。

若要开始,我们建议使用 Hotels 数据集,可以在 Azure 门户上“导入数据”向导中加载该数据集。 该向导会检测源中的复杂类型,并根据检测到的结构建议一个索引架构。

注意

api-version=2019-05-06 开始正式提供对复杂类型的支持。

如果你的搜索解决方案是基于以前的解决方法(集合中的平展数据集)生成的,应更改索引,使之包含最新 API 版本支持的复杂类型。 有关升级的 API 版本的详细信息,请参阅升级到最新的 REST API 版本升级到最新的 .NET SDK 版本

复杂结构的示例

以下 JSON 文档由简单字段和复杂字段构成。 复杂字段(例如 AddressRooms)包含子字段。 Address 包含这些子字段的单一值集,因为它是文档中的单个对象。 相反,Rooms 包含其子字段的多个值集,集合中的每个对象各有一个值集。

{
  "HotelId": "1",
  "HotelName": "Secret Point Motel",
  "Description": "Ideally located on the main commercial artery of the city in the heart of Beijing.",
  "Tags": ["Free wifi", "on-site parking", "indoor pool", "continental breakfast"],
  "Address": {
    "StreetAddress": "677 5th Ave",
    "City": "Beijing",
    "StateProvince": "NY"
  },
  "Rooms": [
    {
      "Description": "Budget Room, 1 Queen Bed (Cityside)",
      "RoomNumber": 1105,
      "BaseRate": 96.99,
    },
    {
      "Description": "Deluxe Room, 2 Double Beds (City View)",
      "Type": "Deluxe Room",
      "BaseRate": 150.99,
    }
    . . .
  ]
}

为复杂类型编制索引

在编制索引期间,单个文档中的所有复杂集合总共最多可包含 3000 个元素。 复杂集合的元素是该集合的成员,因此“客房”集合(此酒店示例中唯一的复杂集合)中的每个客房都是一个元素。 在上面的示例中,如果“秘密点汽车旅馆”有 500 个客房,酒店文档将包含 500 个客房元素。 对于嵌套的复杂集合,除了外部(父)元素之外,还计入每个嵌套元素。

此限制仅适用于复杂集合,不适用于复杂类型(如地址)或字符串集合(如标记)。

创建复杂字段

与处理任何索引定义时一样,可以使用门户、REST API.NET SDK 创建包含复杂类型的架构。

其他 Azure Sdk 提供 PythonJavaJavaScript 的示例。

  1. 登录 Azure 门户

  2. 在搜索服务概述页上,选择“索引”选项卡。

  3. 打开现有索引或创建新索引。

  4. 选择“字段”选项卡,然后选择“添加字段”。 添加一个空字段。 如果使用现有字段集合,请向下滚动以设置字段。

  5. 为字段命名,并将类型设置为 Edm.ComplexTypeCollection(Edm.ComplexType)

  6. 选择最右边的省略号,然后选择“添加字段”或“添加子字段”,然后分配属性。

更新复杂字段

一般情况下,应用于字段的所有重建索引规则仍会应用于复杂字段。 在此处重述一些主要规则以及将字段添加到复杂类型并不需要重建索引,但大多数修改操作需要重建索引。

对定义的结构更新

随时可以将新的子字段添加到复杂字段,而无需索引重建。 例如,允许将“ZipCode”添加到 Address或者将“Amenities”添加到 Rooms,就如同将顶级字段添加到索引一样。 在通过更新数据显式填充新字段之前,现有文档将对这些字段使用 null 值。

请注意,在复杂类型中,像顶级字段一样,每个子字段包含一个类型,有时还包含属性

数据更新

对于复杂字段和简单字段而言,使用 upload 操作更新索引中现有文档的过程是相同的:将替换所有字段。 但是,merge(应用于现有文档时使用 mergeOrUpload)对所有字段的运行方式不同。 具体而言,merge 不支持合并集合中的元素。 基元类型集合与复杂集合存在此限制。 要更新集合,需要检索整个集合值,进行更改,然后在索引 API 请求中包含新的集合。

搜索复杂字段

可按预期方式对复杂类型运行自由形式的搜索表达式。 如果文档中任何位置的任何可搜索字段或子字段匹配,则文档本身就是匹配项。

如果使用多个字词或运算符,并且某些字词指定了字段名(可以使用 Lucene 语法来指定),则查询会变得更微妙。 例如,此查询尝试将两个字词“Portland”和“OR”与 Address 字段的两个子字段相匹配:

search=Address/City:Portland AND Address/State:OR

此类查询对于全文搜索是不相关联的,这与筛选器不同。 在筛选器中,使用 anyall 中的范围变量来关联复杂集合子字段上的查询。 上述 Lucene 查询返回包含“Portland, Maine”和“Portland, Oregon”以及 Oregon 中其他城市的文档。 之所以会发生这种情况,是因为每个子句都应用于整个文档中其字段的所有值,因此没有“当前子文档”的概念。 有关详细信息,请参阅了解 Azure AI 搜索中的 OData 集合筛选器

选择复杂字段

$select 参数用于选择要在搜索结果中返回哪些字段。 要使用此参数选择复杂字段的特定子字段,请包括父字段和用斜线 (/) 分隔的子字段。

$select=HotelName, Address/City, Rooms/BaseRate

如果希望这些字段在搜索结果中出现,必须在索引中将其标记为可检索。 只有标记为可检索的字段才能在 $select 语句中使用。

筛选、分面和排序复杂字段

用作筛选和带字段搜索的 OData 路径语法同样也可用于分面、排序和选择搜索请求中的字段。 对于复杂类型,可以应用规则来控制可将哪些子字段标记为可排序或可分面。 有关这些规则的详细信息,请参阅创建索引 API 参考

分面子字段

除非类型为 Edm.GeographyPointCollection(Edm.GeographyPoint),否则任何子字段都可标记为可分面。

分面结果中返回的文档计数是根据父文档(酒店)计算的,而不是根据复杂集合中的子文档(客房)计算的。 例如,假设某家酒店有 20 间“套房”类型的客房。 如果此分面参数为 facet=Rooms/Type,则分面计数是 1 家酒店,而不是 20 间客房。

排序复杂字段

排序操作将应用于文档(酒店)而不是子文档(客房)。 使用复杂类型集合(例如客房)时必须认识到,根据无法按“客房”排序。 事实上,无法按任何集合进行排序。

当每个文档中的字段只有一个值时,无论字段是简单字段还是复杂类型中的子字段,排序操作都会正常运行。 例如,允许 Address/City 可排序,因为每家酒店只有一个地址,因此 $orderby=Address/City 会按城市对酒店排序。

根据复杂字段进行筛选

可以在筛选表达式中引用复杂字段的子字段。 只需使用对分面、排序和选择字段所用的相同 OData 路径语法。 例如,以下筛选器会返回位于加拿大的所有酒店:

$filter=Address/Country eq 'Canada'

若要根据复杂集合字段进行筛选,可以结合 anyall 运算符使用 Lambda 表达式。 在这种情况下,Lambda 表达式的范围变量是具有子字段的对象。 可以使用标准 OData 路径语法来引用这些子字段。 例如,以下筛选器会返回至少有一间豪华客房,且所有客房都禁止吸烟的所有酒店:

$filter=Rooms/any(room: room/Type eq 'Deluxe Room') and Rooms/all(room: not room/SmokingAllowed)

与顶级简单字段一样,仅当已在索引定义中将复杂字段的简单子字段的 filterable 属性设置为 true 时,才能在筛选器中包含这些子字段。 有关详细信息,请参阅创建索引 API 参考

后续步骤

尝试在“导入数据”向导中练习 Hotels 数据集。 需要使用自述文件中提供的 Azure Cosmos DB 连接信息来访问这些数据。

获取该信息后,向导中的第一步是创建新的 Azure Cosmos DB 数据源。 在向导中到达目标索引页时,会看到具有复杂类型的索引。 请创建并加载此索引,然后执行查询来了解新结构。