Azure Cosmos DB for NoSQL 中的计算属性

适用范围: NoSQL

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

注意

是否想要提供有关计算属性的任何反馈? 我们想听一听! 欢迎直接与 Azure Cosmos DB 工程团队分享反馈:cosmoscomputedprops@microsoft.com

什么是计算属性?

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

示例计算属性定义:

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

名称约束

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

重要

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

计算属性名称的约束如下:

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

查询约束

计算属性定义中的查询必须在语法和语义上有效,否则创建或更新操作会失败。 查询应该计算为容器中所有项的确定性值。 对于某些项,查询的计算结果可能为 undefined 或 null,并且在查询中使用时,具有 undefined 或 null 值的计算属性的行为与具有 undefined 或 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"
      }
    ]
    

    数据资源管理器界面中计算属性 JSON 编辑器的屏幕截图。

  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,这样就可以在使用 Azure 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)。 为计算属性编制索引时,将在项写入操作期间计算实际值,以生成并持久保存索引词。

为计算属性编制索引时请注意几个事项,包括:

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

注意

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

提示

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

注意

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

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

为计算属性添加单个索引

要为名为 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 将增加,以反映对计算属性编制索引和进行评估的成本。