适用于 NoSQL 的 Azure Cosmos DB 中的自联接

适用于: NoSQL

在适用于 NoSQL 的 Azure Cosmos DB 中,数据是无架构的,并且通常是非规范化的。 联接发生在单个项中,而不是像关系数据库中那样跨实体和集联接数据。 具体而言,联接范围限定为该项,而不能跨多个项和容器进行联接。

提示

如果你需要跨项和容器进行联接,请考虑修改数据模型以避免这种情况。

自联接单个项

让我们学习一个在项中自联接的示例。 假设某个容器包含单个项。 此项代表一个具有各种标记的产品:

[
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "categoryId": "bbbbbbbb-1111-2222-3333-cccccccccccc",
    "name": "Teapo Surfboard (6'10\") Grape",
    "sku": "teapo-surfboard-72109",
    "tags": [
      {
        "id": "cccccccc-2222-3333-4444-dddddddddddd",
        "slug": "tail-shape-swallow",
        "name": "Tail Shape: Swallow"
      },
      {
        "id": "dddddddd-3333-4444-5555-eeeeeeeeeeee",
        "slug": "length-inches-82",
        "name": "Length: 82 inches"
      },
      {
        "id": "eeeeeeee-4444-5555-6666-ffffffffffff",
        "slug": "color-group-purple",
        "name": "Color Group: Purple"
      }
    ]
  }
]

如果需要查找此产品的颜色组,该怎样做? 通常,你需要编写一个查询,并在其中包含一个筛选器,用于在 tags 数组中检查前缀为 color-group- 的值的每个潜在索引。

SELECT
  * 
FROM
  products p
WHERE
  STARTSWITH(p.tags[0].slug, "color-group-") OR
  STARTSWITH(p.tags[1].slug, "color-group-") OR
  STARTSWITH(p.tags[2].slug, "color-group-")

这种方法很快就会变得不切实际。 查询语法的复杂性或长度会增加数组中潜在项的数量。 此外,此查询不够灵活,无法处理将来具有三个以上标记的产品。

在传统的关系数据库中,标记会隔离到单独的表中,并根据应用于结果的筛选器执行跨表联接。 在适用于 NoSQL 的 API 中,我们可以使用 JOIN 关键字在项中执行自联接操作。

SELECT
  p.id,
  p.sku,
  t.slug
FROM
  products p
JOIN
  t IN p.tags

此查询返回一个简单数组,其中为 tags 数组中的每个值包含一个项。

[
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "sku": "teapo-surfboard-72109",
    "slug": "tail-shape-swallow"
  },
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "sku": "teapo-surfboard-72109",
    "slug": "length-inches-82"
  },
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "sku": "teapo-surfboard-72109",
    "slug": "color-group-purple"
  }
]

让我们分解该查询。 该查询现在有两个别名:结果集中每个产品项的 p,以及自联接 tags 数组的 t。 仅当可以推理输入集时,* 关键字才可用于投影所有字段,但现在有两个输入集(pt)。 由于这种限制,我们必须将返回的字段显式定义为来自产品的 idsku,以及来自标记的 slug。 为使此查询更易于阅读和理解,我们可以删除 id 字段,并使用标记的 name 字段的别名将其重命名为 tag

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
[
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Tail Shape: Swallow"
  },
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Length: 82 inches"
  },
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Color Group: Purple"
  }
]

最后,我们可以使用筛选器来查找标记 color-group-purple。 由于使用了 JOIN 关键字,因此我们的筛选器足够灵活,可以处理任意可变数量的标记。

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  STARTSWITH(t.slug, "color-group-")
[
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Color Group: Purple"
  }
]

自联接多个项

让我们再学习一个示例:我们需要在数组中查找某个存在于多个项中的值。 对于此示例,假设某个容器包含两个产品项。 每个项包含该项的相关标记。

[
  {
    "id": "ffffffff-5555-6666-7777-aaaaaaaaaaaa",
    "categoryId": "cccccccc-8888-9999-0000-dddddddddddd",
    "categoryName": "Sleeping Bags",
    "name": "Maresse Sleeping Bag (6') Ming",
    "sku": "maresse-sleeping-bag-65503",
    "tags": [
      {
        "id": "b1b1b1b1-cccc-dddd-eeee-f2f2f2f2f2f2",
        "slug": "bag-shape-mummy",
        "name": "Bag Shape: Mummy"
      },
      {
        "id": "bbbbbbbb-7777-8888-9999-cccccccccccc",
        "slug": "bag-insulation-down-fill",
        "name": "Bag Insulation: Down Fill"
      }
    ]
  },
  {
    "id": "c2c2c2c2-dddd-eeee-ffff-a3a3a3a3a3a3",
    "categoryId": "cccccccc-8888-9999-0000-dddddddddddd",
    "categoryName": "Sleeping Bags",
    "name": "Vareno Sleeping Bag (6') Turmeric",
    "sku": "vareno-sleeping-bag-65508",
    "tags": [
      {
        "id": "dddddddd-9999-0000-1111-eeeeeeeeeeee",
        "slug": "bag-insulation-synthetic-fill",
        "name": "Bag Insulation: Synthetic Fill"
      },
      {
        "id": "a0a0a0a0-bbbb-cccc-dddd-e1e1e1e1e1e1",
        "slug": "color-group-yellow",
        "name": "Color Group: Yellow"
      },
      {
        "id": "b1b1b1b1-cccc-dddd-eeee-f2f2f2f2f2f2",
        "slug": "bag-shape-mummy",
        "name": "Bag Shape: Mummy"
      }
    ]
  }
]

如果需要查找每个具有木乃伊(代码示例中的 mummy)睡袋形状的项,该怎样做? 可以搜索标记 bag-shape-mummy,但需要编写一个考虑这些项的两个特征的复杂查询:

  • 带有 bag-shape- 前缀的标记出现在每个数组的不同索引处。 对于 Vareno 睡袋,该标记是第三个项(索引:2)。 对于 Maresse 睡袋,该标记是第一个项(索引:0)。

  • 每个项的 tags 数组的长度不同。 Vareno 睡袋有两个标记,而 Maresse 睡袋有三个标记。

在此处,非常适合使用 JOIN 关键字来创建项和标记的叉积。 联接创建参与联接的集的完整叉积。 结果是一个元组集,其中包含项的每种排列和目标数组中的值。

对示例睡袋产品和标记执行联接操作会创建以下项:

标记
Maresse Sleeping Bag (6') Ming Bag Shape: Mummy
Maresse Sleeping Bag (6') Ming Bag Insulation: Down Fill
Vareno Sleeping Bag (6') Turmeric Bag Insulation: Synthetic Fill
Vareno Sleeping Bag (6') Turmeric Color Group: Yellow
Vareno Sleeping Bag (6') Turmeric Bag Shape: Mummy

下面是对包含容器中多个项的联接执行的 SQL 查询和 JSON 结果集。

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  p.categoryName = "Sleeping Bags"
[
  {
    "sku": "maresse-sleeping-bag-65503",
    "tag": "Bag Shape: Mummy"
  },
  {
    "sku": "maresse-sleeping-bag-65503",
    "tag": "Bag Insulation: Down Fill"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Insulation: Synthetic Fill"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Color Group: Yellow"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Shape: Mummy"
  }
]

就像查询单个项一样,在此处可以应用筛选器以仅查找与特定标记匹配的项。 例如,此查询根据本部分前面提到的初始要求,查找带有名为 bag-shape-mummy 的标记的所有项。

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  p.categoryName = "Sleeping Bags" AND
  t.slug = "bag-shape-mummy"
[
  {
    "sku": "maresse-sleeping-bag-65503",
    "tag": "Bag Shape: Mummy"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Shape: Mummy"
  }
]

还可以更改筛选器以获取不同的结果集。 例如,此查询查找带有名为 bag-insulation-synthetic-fill 的标记的所有项。

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  p.categoryName = "Sleeping Bags" AND
  t.slug = "bag-insulation-synthetic-fill"
[
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Insulation: Synthetic Fill"
  }
]