子查询 - Cosmos DB 中的查询语言(在 Azure 和 Fabric 中)

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

子查询的类型

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

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

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

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

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

多值子查询

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

  • 优化 JOIN (自联接)表达式。
  • 计算一次昂贵的表达式并多次引用。

优化自联接表达式

多值子查询可以通过在每个 select-many 表达式后推送谓词来优化JOIN表达式,而不是在子句中的所有WHERE交叉联接之后推送谓词。

请考虑下列查询:

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%"

对于此查询,索引与具有key标记的任何项匹配,fabric其值大于 *3material至少一个大小order,以及至少一种颜色作为gray子字符串。 此处的JOIN表达式执行每个匹配项的交叉乘积,以及colors每个匹配项tagssizes的数组,然后再应用任何筛选器。

然后,该 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 子句)的值。 可以在标量表达式有效的许多位置使用标量子查询表达式。 例如,可以在和WHERE子句中的任何SELECT表达式中使用标量子查询。

使用标量子查询并不总是有助于优化查询。 例如,将标量子查询作为参数传递给系统或用户定义的函数在减少资源单元(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

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

EXISTS如果子查询返回单个值undefinedEXISTS则计算结果为 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"
      }
    ]
  }
]

现在,请考虑以下查询,该查询基于 type 每个项中的数组中的属性进行 quantityOnHand 筛选。

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"
  }
]