适用于 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
。 仅当可以推理输入集时,*
关键字才可用于投影所有字段,但现在有两个输入集(p
和 t
)。 由于这种限制,我们必须将返回的字段显式定义为来自产品的 id
和 sku
,以及来自标记的 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"
}
]