自联接:Cosmos DB 中的查询语言

在查询语言中,数据是无架构的,通常非规范化。 不同于关系数据库中跨实体和集合联接数据的方式,联接是在单个项内进行的。 具体而言,联接仅限于该项内,不能跨多个项或容器之间进行。

小窍门

如果你发现自己需要在项和容器之间进行联接查询,请考虑重新设计数据模型,以避免这种反模式。

使用单个项进行自联接

让我们看看项内自联接的示例。 考虑包含单个项的容器。 此项表示具有各种大小的产品:

[
  {
    "name": "Raiot Jacket",
    "sizes": [
      {
        "key": "s",
        "description": "Small"
      },
      {
        "key": "m",
        "description": "Medium"
      },
      {
        "key": "l",
        "description": "Large"
      },
      {
        "key": "xl",
        "description": "Extra Large"
      }
    ]
  }
]

如果需要查找特定大小的产品,该怎么办? 通常,需要编写一个查询,该查询具有一个筛选器,该查询检查数组中 sizes 每个潜在索引是否有前缀的值。 在此示例中,该查询查找出所有尺寸以 Large 结尾的产品:

SELECT
  *
FROM
  products p
WHERE
  p.sizes[0].description LIKE "%Large" OR
  p.sizes[1].description LIKE "%Large" OR
  p.sizes[2].description LIKE "%Large" OR
  p.sizes[3].description LIKE "%Large"

此方法可以快速变得不可维持。 查询语法的复杂性或长度会增加数组中潜在项的数量。 此外,此查询不够灵活,无法处理将来可能具有三个以上的尺寸的产品。

在传统的关系型数据库中,尺寸信息通常会单独存放在一张表中,并通过跨表连接后对结果应用过滤条件。 在查询语言中,我们可以使用 JOIN 关键字在项内执行自联接操作:

SELECT
  p.name,
  s.key,
  s.description
FROM
  products p
JOIN
  s in p.sizes

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

[
  {
    "name": "Raiot Jacket",
    "key": "s",
    "description": "Small"
  },
  {
    "name": "Raiot Jacket",
    "key": "m",
    "description": "Medium"
  },
  {
    "name": "Raiot Jacket",
    "key": "l",
    "description": "Large"
  },
  {
    "name": "Raiot Jacket",
    "key": "xl",
    "description": "Extra Large"
  }
]

让我们来分析一下这个查询。 该查询现在有两个别名:结果集中每个产品项对应的 p,以及自联接的 sizes 数组对应的 s*如果关键字可以推断输入集,则仅对投影所有字段有效,但现在有两个输入集(pt)。 由于这一限制,我们必须显式地将返回字段定义为来自 product 的 namekey,以及来自 sizes 的 description

最后,可以使用筛选器查找以 Large结尾的大小。 由于我们使用了 JOIN 关键字,因此筛选器足够灵活,可以处理任何可变数量的标记:

SELECT
  p.name,
  s.key AS size
FROM
  products p
JOIN
  s in p.sizes
WHERE
  s.description LIKE "%Large"
[
  {
    "name": "Raiot Jacket",
    "size": "l"
  },
  {
    "name": "Raiot Jacket",
    "size": "xl"
  }
]

多个项目自连接

让我们转到一个示例,在此示例中,我们需要在多个项中存在的数组中找到值。 对于此示例,请考虑包含两个产品项的容器。 每个项目都包含与其相关的 colors

[
  {
    "name": "Gremon Fins",
    "colors": [
      "science-blue",
      "turbo"
    ]
  },
  {
    "name": "Elecy Jacket",
    "colors": [
      "indigo-shark",
      "jordy-blue-shark"
    ]
  },
  {
    "name": "Tresko Pack",
    "colors": [
      "golden-dream"
    ]
  }
]

如果需要查找名称中包含颜色 blue 的每个项,该怎么办? 可以手动搜索字符串 blue,但需要编写一个复杂查询,该查询包含以下两个特征:

  • 包含 blue 子字符串的颜色在各个数组中的索引位置各不相同。 Elecy Jacket对于产品,颜色为第二项(索引: 1)。 对于 Gremon Fins 产品,标签是第一项(索引:0)。 该Tresko Pack产品没有任何包含此子字符串的内容。

  • 每个项的 colors 数组长度不同。 Gremon Fins产品和Elecy Jacket产品都有种颜色,而Tresko Pack产品只有一种颜色。

在这里,关键字 JOIN 是创建项和颜色的交叉积的绝佳工具。 联接对参与联接的各个集合创建一个完整的交叉积。 结果是一组元组,包含该项与目标数组中各个值的所有排列组合。

对我们的示例产品和颜色执行连接操作会生成以下项:

产品 颜色
Gremon Fins science-blue
Gremon Fins turbo
Elecy Jacket indigo-shark
Elecy Jacket jordy-blue-shark
Tresko Pack golden-dream

此示例 NoSQl 查询使用 JOIN 关键字创建跨产品并返回所有排列:

SELECT
  p.name,
  c AS color
FROM
  products p
JOIN
  c in p.colors
[
  {
    "name": "Elecy Jacket",
    "color": "indigo-shark"
  },
  {
    "name": "Elecy Jacket",
    "color": "jordy-blue-shark"
  },
  {
    "name": "Gremon Fins",
    "color": "science-blue"
  },
  {
    "name": "Gremon Fins",
    "color": "turbo"
  },
  {
    "name": "Tresko Pack",
    "color": "golden-dream"
  }
]

与单个项一样,可以在此处应用筛选器以仅查找与特定标记匹配的项目。 例如,此查询查找包含满足本节前面提到的初始要求的所有子字符串 blue 的项目。

SELECT
  p.name,
  c AS color
FROM
  products p
JOIN
  c in p.colors
WHERE
  c LIKE "%blue%"
[
  {
    "name": "Elecy Jacket",
    "color": "jordy-blue-shark"
  },
  {
    "name": "Gremon Fins",
    "color": "science-blue"
  }
]

可以进一步优化此查询,以便仅返回满足筛选器的产品的名称。 此示例不投影颜色值,但筛选器仍按预期工作:

SELECT VALUE
  p.name
FROM
  products p
JOIN
  c in p.colors
WHERE
  c LIKE "%blue%"
[
  "Elecy Jacket",
  "Gremon Fins"
]