计算属性 - Cosmos DB 中的查询语言

Cosmos DB 中的计算属性具有派生自现有项属性的值,但这些属性本身不会保留在项中。 计算属性的范围限定为单个项,可以在查询中引用它们,就像它们是持久化属性一样。 通过计算属性,可以更轻松地编写复杂的查询逻辑一次,并多次引用它。 可以在这些属性上添加单个索引,或者将它们用作复合索引的一部分,以提高性能。

什么是计算属性?

计算属性必须位于项的顶层,并且不能有嵌套路径。 每个计算属性定义都有两个组件:名称和查询。 该名称是计算的属性名称,查询定义逻辑来计算每个项的属性值。 计算属性的范围限定为单个项,因此不能使用来自多个项的值或依赖于其他计算属性。 每个容器最多可以有 20 个计算属性。

计算属性定义示例:

{
  "computedProperties": [
    {
      "name": "cp_lowerName",
      "query": "SELECT VALUE LOWER(c.name) FROM c"
    }
  ]
}

名称约束

强烈建议命名计算属性,以免与持久化属性名称冲突。 为了避免属性名称重叠,可以将前缀或后缀添加到所有计算的属性名称。 本文在所有名称定义中使用前缀 cp_

重要

使用与持久化属性相同的名称定义计算属性不会导致错误,但可能会导致意外行为。 无论计算属性是否编制索引,索引中不包含与计算属性共享名称的持久属性中的值。 如果 SELECT 子句中有通配符投影,则查询始终使用计算属性而不是持久化属性,但返回的持久属性除外。 通配符投影不会自动包含计算属性。

计算属性名称的约束包括:

  • 所有计算属性都必须具有唯一名称。
  • 属性的值表示可用于引用计算属性的 name 顶级属性名称。
  • 保留的系统属性名称,例如 id_rid不能 _ts 用作计算属性名称。
  • 计算属性名称与已编制索引的属性路径不匹配。 此约束适用于指定的所有索引路径,包括:
    • 包含的路径
    • 排除的路径
    • 空间索引
    • 组合索引

查询约束

计算属性定义中的查询在语法上和语义上必须有效,否则创建或更新作将失败。 查询的计算结果应为容器中所有项的确定性值。 查询的计算结果可能为某些项未定义或 null,并且未定义或 null 值的计算属性的行为与查询中使用时具有未定义或 null 值的持久属性的行为相同。

计算属性查询定义的限制包括:

  • 查询必须指定表示根项引用的 FROM 子句。 支持的 FROM 子句的示例包括: FROM cFROM root cFROM MyContainer c
  • 查询必须在投影中使用 VALUE 子句。
  • 查询不能包含 JOIN。
  • 查询不能使用不确定的标量表达式。 非确定性标量表达式的示例包括:GetCurrentDateTime、GetCurrentTimeStamp、GetCurrentTicks 和 RAND。
  • 查询不能使用以下任何子句:WHERE、GROUP BY、ORDER BY、TOP、DISTINCT、OFFSET LIMIT、EXISTS、ALL、LAST、FIRST 和 NONE。
  • 查询不能包含标量子查询。
  • 不支持聚合函数、空间函数、不确定函数和用户定义的函数(UDF)。

创建计算属性

创建计算属性后,可以使用任何方法执行引用属性的查询,包括 Azure 门户中的所有软件开发工具包(SDK)和 Azure 数据资源管理器。

支持的版本 注释
.NET SDK v3 >= 3.34.0-preview 计算属性目前仅在预览包版本中可用。
Java SDK v4 >= 4.46.0 计算属性当前处于预览版下。
Python SDK >= v4.5.2b5 计算属性当前处于预览版下。

使用 SDK 创建计算属性

可以创建已定义计算属性的新容器,也可以将计算属性添加到现有容器。

下面是如何在新容器中创建计算属性的示例:

ContainerProperties containerProperties = new ContainerProperties("myContainer", "/pk")
{
    ComputedProperties = new Collection<ComputedProperty>
    {
        new ComputedProperty
        {
            Name = "cp_lowerName",
            Query = "SELECT VALUE LOWER(c.name) FROM c"
        }
    }
};

Container container = await client.GetDatabase("myDatabase").CreateContainerAsync(containerProperties);

下面是有关如何更新现有容器的计算属性的示例:

var container = client.GetDatabase("myDatabase").GetContainer("myContainer");

// Read the current container properties
var containerProperties = await container.ReadContainerAsync();
// Make the necessary updates to the container properties
containerProperties.Resource.ComputedProperties = new Collection<ComputedProperty>
    {
        new ComputedProperty
        {
            Name = "cp_lowerName",
            Query = "SELECT VALUE LOWER(c.name) FROM c"
        },
        new ComputedProperty
        {
            Name = "cp_upperName",
            Query = "SELECT VALUE UPPER(c.name) FROM c"
        }
    };
// Update the container with changes
await container.ReplaceContainerAsync(containerProperties);

小窍门

每次更新容器属性时,都会覆盖旧值。 如果你有现有的计算属性并且想要添加新属性,请确保同时向集合添加新的计算属性和现有的计算属性。

使用数据资源管理器创建计算属性

可以使用数据资源管理器为容器创建计算属性。

  1. 在数据资源管理器中打开现有容器。

  2. 导航到容器的 “设置” 部分。 然后,导航到*计算属性 子部分。

  3. 编辑容器的计算属性定义 JSON。 在此示例中,此 JSON 用于定义一个计算属性,以使用SKU分隔符拆分-零售产品的字符串。

    [
      {
        "name": "cp_splitSku",
        "query": "SELECT VALUE StringSplit(p.sku, \"-\") FROM products p"
      }
    ]
    
  4. 保存 计算属性。

在查询中使用计算属性

在查询中可以引用计算属性的方式与引用持久化属性的方式相同。 使用计算属性定义在运行时计算未编制索引的计算属性的值。 如果为计算属性编制索引,则索引的使用方式与用于持久化属性的方式相同,并根据需要计算计算属性。 建议 在计算属性上添加索引 ,以获得最佳成本和性能。

下面是项的示例:

{
  "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
  "categoryId": "bbbbbbbb-1111-2222-3333-cccccccccccc",
  "categoryName": "Bikes, Touring Bikes",
  "sku": "BK-T79U-50",
  "name": "Touring-1000 Blue, 50",
  "description": "The product called \"Touring-1000 Blue, 50\"",
  "price": 2384.07,
  "tags": [
    {
      "id": "cccccccc-2222-3333-4444-dddddddddddd",
      "name": "Tag-61"
    }
  ],
  "_rid": "n7AmAPTJ480GAAAAAAAAAA==",
  "_self": "dbs/n7AmAA==/colls/n7AmAPTJ480=/docs/n7AmAPTJ480GAAAAAAAAAA==/",
  "_etag": "\"01002683-0000-0800-0000-6451fb4b0000\"",
  "_attachments": "attachments/",
  "_ts": 1683094347
}

投影

如果需要投影计算属性,则必须显式引用它们。 通配符投影(如 SELECT * 返回所有持久化属性)但不包括计算属性。

下面是将属性转换为 name 小写的示例计算属性定义:

{ 
  "name": "cp_lowerName", 
  "query": "SELECT VALUE LOWER(c.name) FROM c" 
} 

然后,可以在查询中投影此属性:

SELECT 
    c.cp_lowerName 
FROM 
    c

WHERE 子句

可以在筛选器谓词中引用计算属性,如任何持久化属性。 建议在筛选器中使用计算属性时添加任何相关的单一索引或复合索引。

下面是一个计算属性定义示例,用于计算 20% 的价格折扣:

{ 
  "name": "cp_20PercentDiscount", 
  "query": "SELECT VALUE (c.price * 0.2) FROM c" 
} 

然后,可以筛选此属性,以确保仅返回折扣小于 50 美元的产品:

SELECT 
    c.price - c.cp_20PercentDiscount as discountedPrice, 
    c.name 
FROM 
    c 
WHERE 
    c.cp_20PercentDiscount < 50.00

GROUP BY 子句

与持久化属性一样,计算属性可以在 GROUP BY 子句中引用,并尽可能使用索引。 为了获得最佳性能,请添加任何相关的单一索引或复合索引。

下面是一个计算属性定义示例,该定义从 categoryName 属性中查找每个项的主要类别:

{
  "name": "cp_primaryCategory",
  "query": "SELECT VALUE SUBSTRING(c.categoryName, 0, INDEX_OF(c.categoryName, ',')) FROM c"
}

然后,可以按 cp_primaryCategory 分组来获取每个主要类别中的项计数:

SELECT 
    COUNT(1), 
    c.cp_primaryCategory 
FROM 
    c 
GROUP BY 
    c.cp_primaryCategory

小窍门

尽管也可以在不使用计算属性的情况下实现此查询,但使用计算属性可以大大简化查询的编写,并允许提高性能,因为 cp_primaryCategory 可以编制索引。 SUBSTRING()INDEX_OF() 都需要对容器中的所有项进行完全扫描,但如果为计算属性编制索引,则可以改为从索引提供整个查询。 从索引提供查询而不是依赖完全扫描的能力可以提高性能,并降低查询请求单位 (RU) 成本。

ORDER BY 子句

与持久化属性一样,计算属性可以在 ORDER BY 子句中引用,并且必须为查询编制索引才能成功。 通过使用计算属性,可以 ORDER BY 复杂逻辑或系统函数的结果,这将在使用 Cosmos DB 时打开许多新的查询方案。

下面是一个计算属性定义示例,用于从值中获取 _ts 月份:

{
  "name": "cp_monthUpdated",
  "query": "SELECT VALUE DateTimePart('m', TimestampToDateTime(c._ts*1000)) FROM c"
}

在 ORDER BY cp_monthUpdated之前,必须将其添加到索引策略。 更新索引策略后,可以按计算属性进行排序。

SELECT
    *
FROM
    c
ORDER BY
    c.cp_monthUpdated

索引计算属性

默认情况下,计算属性不会编制索引,索引 策略中的通配符路径不会覆盖这些属性。 可以在索引策略中的计算属性上添加单索引或复合索引,就像在持久化属性上添加索引一样。 建议向所有计算属性添加相关索引。 建议使用这些索引,因为它们有利于提高性能和减少请求单位(RU)。 为计算属性编制索引时,在项写入作期间计算实际值以生成和保留索引词。

对计算属性编制索引有几个注意事项,包括:

  • 可以在包含的路径、排除的路径和复合索引路径中指定计算属性
  • 计算属性不能对其定义空间索引
  • 计算属性路径下的通配符路径的工作方式类似于常规属性的通配符路径
  • 还必须删除已删除和索引属性上的相关索引

注释

所有计算属性均在项的顶层定义。 路径始终 为 < a0/>。

小窍门

每次更新容器属性时,都会覆盖旧值。 如果你有现有的计算属性并且想要添加新属性,请确保同时向集合添加新的计算属性和现有的计算属性。

注释

修改索引计算属性的定义时,不会自动重新编制索引。 若要为修改的计算属性编制索引,请从索引中删除计算属性。 然后,在重新编制索引完成后,将计算属性添加回索引策略。

如果要删除计算属性,请先将其从索引策略中删除。

为计算属性添加单个索引

若要为名为 cp_myComputedProperty: 的计算属性添加单个索引,

{
  "indexingMode": "consistent",
  "automatic": true,
  "includedPaths": [
    {
      "path": "/*"
    },
    {
      "path": "/cp_myComputedProperty/?"
    }
  ],
  "excludedPaths": [
    {
      "path": "/\"_etag\"/?"
    }
  ]
}

为计算属性添加复合索引

若要在两个属性上添加复合索引,其中一个属性计算为 cp_myComputedProperty,另一个属性保留为 myPersistedProperty

{
  "indexingMode": "consistent",
  "automatic": true,
  "includedPaths": [
    {
      "path": "/*"
    }
  ],
  "excludedPaths": [
    {
      "path": "/\"_etag\"/?"
    }
  ],
  "compositeIndexes": [
    [
      {
        "path": "/cp_myComputedProperty"
      },
      {
        "path": "/path/to/myPersistedProperty"
      }
    ]
  ]
}

了解请求单位消耗

将计算属性添加到容器不会使用 RU。 对已定义计算属性的容器执行写入作可能会略有 RU 增加。 如果为计算属性编制索引,则写入作上的 RU 将增加,以反映计算属性的索引和计算成本。