子查询 - Cosmos DB 中的查询语言

子查询是在查询语言中嵌套于另一个查询中的查询。 子查询也称为 内部查询内部 SELECT查询。 包含子查询的语句通常称为 外部查询

子查询的类型

有两种主要类型的子查询:

  • 相关:引用外部查询中的值的子查询。 外部查询每处理一行,该子查询就会被求值一次。
  • 不相关:独立于外部查询的子查询。 它可以自行运行,而无需依赖外部查询。

可以根据子查询返回的行数和列数进一步分类。 有三种类型:

  • :返回多行和多列。
  • 多值:返回多行和单个列。
  • 标量:返回单个行和单个列。

查询语言中的查询始终返回单个列(简单值或复杂项)。 因此,只有多值子查询和标量子查询才适用。 只能在子句中使用 FROM 多值子查询作为关系表达式。 可以将标量子查询用作 SELECTWHERE 子句中的标量表达式,或者用作 FROM 子句中的关系表达式。

多值子查询

多值子查询返回一组项,并且始终在子句中使用 FROM 。 它们用于:

  • 优化 JOIN (自联接)表达式。
  • 对高开销表达式只计算一次,并多次引用其结果。

优化自联接表达式

多值子查询可以通过将谓词下推到每个 select-many 表达式之后,而不是下推到 WHERE 子句中所有 cross-joins 之后,来优化 JOIN 表达式。

请考虑下列查询:

SELECT VALUE
  COUNT(1)
FROM
  products p
JOIN 
  t in p.tags
JOIN
  s in p.sizes
JOIN
  c in p.colors
WHERE
  t.key IN ("fabric", "material") AND
  s["order"] >= 3 AND
  c LIKE "%gray%"

对于此查询,索引会匹配任何满足以下条件的项:具有一个标签,其 keyfabricmaterial;至少有一个大小,其 order 值大于 *three;并且至少有一种颜色包含子字符串 gray。 此处的 JOIN 表达式会在应用任何筛选器之前,对每个匹配项执行 tagssizescolors 数组中所有项的 笛卡儿积

然后,WHERE 子句对每个 $<c, t, n, s>$ 元组应用过滤谓词。 例如,如果某个匹配项在三个数组中的每一个都有 10 个项,则根据此公式会扩展为 1,000 个元组:

$$1 x 10 x 10 x 10$$

在此处使用子查询有助于在与下一个表达式联接之前筛选出联接的数组项。

此查询等效于前面的查询,但使用了子查询:

SELECT VALUE
  COUNT(1)
FROM
  products p
JOIN 
  (SELECT VALUE t FROM t IN p.tags WHERE t.key IN ("fabric", "material"))
JOIN 
  (SELECT VALUE s FROM s IN p.sizes WHERE s["order"] >= 3)
JOIN 
  (SELECT VALUE c FROM c in p.colors WHERE c LIKE "%gray%")

假设标记数组中只有一个项目与筛选器匹配,并且数量数组和库存数组都有五个项目。 然后,该 JOIN 表达式使用此公式扩展为 25 个元组,而不是第一个查询中的 1,000 个项:

$$1 x 1 x 5 x 5$$

评估一次,多次复用

子查询可以帮助优化具有昂贵表达式的查询,例如用户定义的函数(UDF)、复杂字符串或算术表达式。 可以使用子查询配合 JOIN 表达式,对该表达式求值一次,但多次引用该表达式。

此示例查询在查询中多次按 25% 的加价计算价格。

SELECT VALUE {
  subtotal: p.price,
  total: (p.price * 1.25)
}
FROM
  products p
WHERE
  (p.price * 1.25) < 22.25

下面是仅运行计算一次的等效查询:

SELECT VALUE {
  subtotal: p.price,
  total: totalPrice
}
FROM
  products p
JOIN
  (SELECT VALUE p.price * 1.25) totalPrice
WHERE
  totalPrice < 22.25
[
  {
    "subtotal": 15,
    "total": 18.75
  },
  {
    "subtotal": 10,
    "total": 12.5
  },
  ...
]

小窍门

请注意 JOIN 表达式的跨产品行为。 如果该表达式的计算结果可能为 undefined,则应确保 JOIN 表达式始终只返回单行,方法是让子查询返回一个对象,而不是直接返回值。

模拟与外部引用数据的关系联接

你通常需要引用很少更改的静态数据,例如 度量单位。 最好不要复制查询中每个项的静态数据。 避免这种重复可以节省存储,并通过减小单个项的大小来提高写入性能。 可以使用子查询来模拟静态引用数据的集合的内部联接语义。

例如,请考虑一组表示服装长度的度量值:

尺寸 长度 单位
xs 63.5 cm
s 64.5 cm
m 66.0 cm
l 67.5 cm
xl 69.0 cm
xxl 70.5 cm

以下查询模拟与此数据联接,以便将单元的名称添加到输出中:

SELECT
  p.name,
  p.subCategory,
  s.description AS size,
  m.length,
  m.unit
FROM
  products p
JOIN
  s IN p.sizes
JOIN m IN (
  SELECT VALUE [
    {size: 'xs', length: 63.5, unit: 'cm'},
    {size: 's', length: 64.5, unit: 'cm'},
    {size: 'm', length: 66, unit: 'cm'},
    {size: 'l', length: 67.5, unit: 'cm'},
    {size: 'xl', length: 69, unit: 'cm'},
    {size: 'xxl', length: 70.5, unit: 'cm'}
  ]
)
WHERE
  s.key = m.size

标量子查询

标量子查询表达式是结果为单个值的子查询。 标量子查询表达式的值是子查询的投影(即 SELECT 子句)的值。 可以在标量表达式有效的许多位置使用标量子查询表达式。 例如,可以在 SELECTWHERE 子句中的任何表达式中使用标量子查询。

使用标量子查询并不总是有助于优化你的查询。 例如,将标量子查询作为参数传递给系统函数或用户定义函数,并不会在降低资源单位(RU)消耗或延迟方面带来任何收益。

标量子查询可以进一步分类为:

  • 简单表达式的标量子查询
  • 聚合标量子查询

简单表达式的标量子查询

简单表达式标量子查询是一种相关子查询,它具有一个不包含任何聚合表达式的 SELECT 子句。 这些子查询没有优化优势,因为编译器将它们转换为一个更大的简单表达式。 内部查询和外部查询之间没有关联的上下文。

作为第一个示例,请考虑此简单查询。

SELECT
  1 AS a,
  2 AS b

可以通过使用简单表达式的标量子查询来重写此查询。

SELECT
  (SELECT VALUE 1) AS a, 
  (SELECT VALUE 2) AS b

这两个查询都生成相同的输出。

[
  {
    "a": 1,
    "b": 2
  }
]

下一个示例查询将唯一标识符与前缀连接起来,作为一个简单表达式标量子查询。

SELECT 
  (SELECT VALUE CONCAT('ID-', p.id)) AS internalId
FROM
  products p

此示例使用简单表达式标量子查询来仅返回每个条目的相关字段。 查询会为每个项返回某些结果,但只有当该项满足子查询中的筛选条件时,结果中才会包含投影字段。

SELECT
  p.id,
  (SELECT p.name WHERE CONTAINS(p.name, "Shoes")).name
FROM
  products p
[
  {
    "id": "00000000-0000-0000-0000-000000004041",
    "name": "Remdriel Shoes"
  },
  {
    "id": "00000000-0000-0000-0000-000000004322"
  },
  {
    "id": "00000000-0000-0000-0000-000000004055"
  }
]

聚合标量子查询

聚合标量子查询是指其投影列表或筛选器中包含聚合函数,且计算结果为单个值的子查询。

作为第一个示例,请考虑具有以下字段的项。

[
  {
    "name": "Blators Snowboard Boots",
    "colors": [
      "turquoise",
      "cobalt",
      "jam",
      "galliano",
      "violet"
    ],
    "sizes": [ ... ],
    "tags": [ ... ]
  }
]

下面是在其投影中具有单个聚合函数表达式的子查询。 此查询统计每个项的所有标记。

SELECT
  p.name,
  (SELECT VALUE COUNT(1) FROM c IN p.colors) AS colorsCount
FROM
  products p
WHERE
  p.id = "00000000-0000-0000-0000-000000004389"
[
  {
    "name": "Blators Snowboard Boots",
    "colorsCount": 5
  }
]

下面是与筛选器相同的子查询。

SELECT
  p.name,
  (SELECT VALUE COUNT(1) FROM c IN p.colors) AS colorsCount,
  (SELECT VALUE COUNT(1) FROM c IN p.colors WHERE c LIKE "%t") AS colorsEndsWithTCount
FROM
  products p
[
  {
    "name": "Blators Snowboard Boots",
    "colorsCount": 5,
    "colorsEndsWithTCount": 2
  }
]

下面是具有多个聚合函数表达式的另一个子查询:

SELECT
  p.name,
  (SELECT VALUE COUNT(1) FROM c IN p.colors) AS colorsCount,
  (SELECT VALUE COUNT(1) FROM s in p.sizes) AS sizesCount,
  (SELECT VALUE COUNT(1) FROM t IN p.tags) AS tagsCount
FROM
  products p
[
  {
    "name": "Blators Snowboard Boots",
    "colorsCount": 5,
    "sizesCount": 7,
    "tagsCount": 2
  }
]

最后,下面是一个查询,其中包含投影和筛选器中的聚合子查询:

SELECT
  p.name,
  (SELECT VALUE COUNT(1) FROM s in p.sizes WHERE s.description LIKE "%Small") AS smallSizesCount,
  (SELECT VALUE COUNT(1) FROM s in p.sizes WHERE s.description LIKE "%Large") AS largeSizesCount
FROM
  products p
WHERE
  (SELECT VALUE COUNT(1) FROM c IN p.colors) >= 5

编写此查询的更最佳方法是在子查询上联接,并在 SELECT 和 WHERE 子句中引用子查询别名。 此查询更高效,因为只需在联接语句中执行子查询,而不在投影和筛选器中执行。

SELECT
  p.name,
  colorCount,
  smallSizesCount,
  largeSizesCount
FROM
  products p
JOIN
  (SELECT VALUE COUNT(1) FROM c IN p.colors) AS colorCount
JOIN
  (SELECT VALUE COUNT(1) FROM s in p.sizes WHERE s.description LIKE "%Small") AS smallSizesCount
JOIN
  (SELECT VALUE COUNT(1) FROM s in p.sizes WHERE s.description LIKE "%Large") AS largeSizesCount
WHERE
  colorCount >= 5 AND
  largeSizesCount > 0 AND
  smallSizesCount > 0

EXISTS 表达式

查询语言支持 EXISTS 表达式。 此表达式是查询语言内置的聚合标量子查询。 EXISTS 获取子查询表达式,如果子查询返回任何行,则返回 true 该表达式。 否则,它将返回 false

由于查询引擎不区分布尔表达式和任何其他标量表达式,因此可以在 SELECTWHERE 子句中使用 EXISTS。 此行为与 T-SQL 不同,其中布尔表达式仅限于筛选器。

如果 EXISTS 子查询返回一个值,且该值为 undefined,则 EXISTS 的计算结果为 false。 例如,请考虑以下不会返回任何结果的查询。

SELECT VALUE
  undefined

如果使用表达式 EXISTS 和前面的查询作为子查询,则表达式返回 false

SELECT VALUE
  EXISTS (SELECT VALUE undefined)
[
  false
]

如果省略上述子查询中的 VALUE 关键字,则子查询的计算结果为具有单个空对象的数组。

SELECT
  undefined
[
  {}
]

此时,表达式 EXISTS 的计算结果为 true,因为对象({})严格来说确实存在。

SELECT VALUE
  EXISTS (SELECT undefined)
[
  true
]

一种常见用例 ARRAY_CONTAINS 是按数组中某个项的存在来筛选项。 在这种情况下,我们会检查 tags数组中是否包含名为“outerwear”的项。

SELECT
  p.name,
  p.colors
FROM
  products p
WHERE
  ARRAY_CONTAINS(p.colors, "cobalt")

同一查询也可以使用 EXISTS 作为替代选项。

SELECT
  p.name,
  p.colors
FROM
  products p
WHERE
  EXISTS (SELECT VALUE c FROM c IN p.colors WHERE c = "cobalt")

此外, ARRAY_CONTAINS 只能检查某个值是否等于数组中的任何元素。 如果需要对数组属性使用更复杂的筛选器,请改用 JOIN

请考虑在一个包含多个项目的集合中的这个示例项目,其中每个项目都包含一个 accessories 数组。

[
  {
    "name": "Cosmoxy Pack",
    "tags": [
      {
        "key": "fabric",
        "value": "leather",
        "description": "Leather"
      },
      {
        "key": "volume",
        "value": "68-gal",
        "description": "6.8 Gal"
      }
    ]
  }
]

现在,考虑以下查询,它根据每个项内数组中的 typequantityOnHand 属性进行筛选。

SELECT
  p.name,
  t.description AS tag
FROM
  products p
JOIN
  t in p.tags
WHERE
  t.key = "fabric" AND
  t["value"] = "leather"
[
  {
    "name": "Cosmoxy Pack",
    "tag": "Leather"
  }
]

对于集合中的每个项,使用其数组元素执行跨产品。 此 JOIN 操作可以根据数组中的属性进行筛选。 但是,此查询的 RU 消耗量很大。 例如,如果每个数组中 1,000 个项有 100 个项,则它使用此公式扩展到 100,000 个元组:

$$1,000 x 100$$

使用 EXISTS 有助于避免这种昂贵的跨产品。 在下一个示例中,查询会筛选子查询中的 EXISTS 数组元素。 如果数组元素与筛选器匹配,则将其投影出来,并且 EXISTS 的计算结果为 true。

SELECT VALUE
  p.name
FROM
  products p
WHERE
  EXISTS (
    SELECT VALUE
      t
    FROM
      t IN p.tags
    WHERE
      t.key = "fabric" AND
      t["value"] = "leather"
  )
[
  "Cosmoxy Pack"
]

查询还可以为 EXISTS 设置别名,并在投影中引用该别名:

SELECT
  p.name,
  EXISTS (
    SELECT VALUE
      t
    FROM
      t IN p.tags
    WHERE
      t.key = "fabric" AND
      t["value"] = "leather"
  ) AS containsFabricLeatherTag
FROM
  products p
[
  {
    "name": "Cosmoxy Pack",
    "containsFabricLeatherTag": true
  }
]

ARRAY 表达式

可以使用表达式 ARRAY 将查询的结果投影为数组。 只能在查询的 SELECT 子句中使用此表达式。

对于这些示例,假设至少有一个包含此项的容器。

[
  {
    "name": "Menti Sandals",
    "sizes": [
      {
        "key": "5"
      },
      {
        "key": "6"
      },
      {
        "key": "7"
      },
      {
        "key": "8"
      },
      {
        "key": "9"
      }
    ]
  }
]

在第一个示例中,表达式是在 SELECT 子句中使用的。

SELECT
  p.name,
  ARRAY (
    SELECT VALUE
      s.key
    FROM
      s IN p.sizes
  ) AS sizes
FROM
  products p
WHERE
  p.name = "Menti Sandals"
[
  {
    "name": "Menti Sandals",
    "sizes": [
      "5",
      "6",
      "7",
      "8",
      "9"
    ]
  }
]

与其他子查询一样,也可以使用带有 ARRAY 表达式的筛选器。

SELECT
  p.name,
  ARRAY (
    SELECT VALUE
      s.key
    FROM
      s IN p.sizes
    WHERE
      STRINGTONUMBER(s.key) <= 6
  ) AS smallSizes,
  ARRAY (
    SELECT VALUE
      s.key
    FROM
      s IN p.sizes
    WHERE
      STRINGTONUMBER(s.key) >= 9
  ) AS largeSizes
FROM
  products p
WHERE
  p.name = "Menti Sandals"
[
  {
    "name": "Menti Sandals",
    "smallSizes": [
      "5",
      "6"
    ],
    "largeSizes": [
      "9"
    ]
  }
]

在子查询中,数组表达式也可以出现在 FROM 子句之后。

SELECT
  p.name,
  z.s.key AS sizes
FROM
  products p
JOIN
  z IN (
    SELECT VALUE
      ARRAY (
        SELECT
          s
        FROM
          s IN p.sizes
        WHERE
          STRINGTONUMBER(s.key) <= 8
      )
  )
[
  {
    "name": "Menti Sandals",
    "sizes": "5"
  },
  {
    "name": "Menti Sandals",
    "sizes": "6"
  },
  {
    "name": "Menti Sandals",
    "sizes": "7"
  },
  {
    "name": "Menti Sandals",
    "sizes": "8"
  }
]