適用于 NoSQL 的 Azure Cosmos DB 中的子查詢

適用於:NoSQL

子查詢是巢狀于 Azure Cosmos DB for NoSQL 內另一個查詢內的查詢。 子查詢也稱為內部查詢內部 SELECT。 包含子查詢的語句通常稱為 外部查詢

子查詢的類型

子查詢有兩種主要類型:

  • 相互關聯:子查詢參考外部查詢的值。 針對外部查詢處理的每個資料列,子查詢都經過一次評估。
  • 非相互關聯:子查詢與外部查詢無關。 可獨立執行,不依賴外部查詢。

注意

Azure Cosmos DB 僅支援相互關聯的子查詢。

根據傳回的資料列和資料行數目,子查詢可進一步分類。 有三種類型:

  • 資料表:傳回多個資料列和多個資料行。
  • 多重值:傳回多個資料列和單一資料行。
  • 純量:傳回單一資料列和單一資料行。

適用于 NoSQL 的 Azure Cosmos DB 查詢一律會傳回單一資料行, (簡單值或複雜專案) 。 因此,只適用多重值和純量子查詢。 您只能在 子句中使用 FROM 多重值子查詢做為關聯運算式。 您可以使用純量子查詢作為 或 WHERE 子句中的 SELECT 純量運算式,或是 子句中的 FROM 關聯式運算式。

多重值子查詢

多重值子查詢會傳回一組專案,而且一律用於 子句內 FROM 。 用途包括:

  • 優化 JOIN (自我聯結) 運算式。
  • 耗費資源的運算式只評估一次,然後參考多次。

優化自我聯結運算式

多重值子查詢可以藉由在每個select-many運算式之後推送述詞,而不是在 子句中的所有 WHERE交叉聯結之後,來優化 JOIN 運算式。

請考慮下列查詢:

SELECT VALUE
    COUNT(1)
FROM
    products p
JOIN 
    t in p.tags
JOIN 
    q in p.onHandQuantities
JOIN 
    s in p.warehouseStock
WHERE 
    t.name IN ("winter", "fall") AND
    (q.quantity BETWEEN 0 AND 10) AND
    NOT s.backstock

針對此查詢,索引會比對任何具有標籤且具有 name「winter」「fall」的專案、至少一個介於之間的專案,以及至少一個 quantity 倉儲,其中 backstockfalse 。 此處的 JOIN 運算式會在套用任何篩選之前,針對每個相符專案 tags 執行 、 onHandQuantitieswarehouseStock 陣列的交叉乘積。

WHERE 句接著會在每個 <c, t, n, s> Tuple 上套用篩選述詞。 例如,如果相符的專案在三個數組中各有 個專案,則會展開為 1 x 10 x 10 x 10 (,也就是 1,000) Tuple。 在這裡使用子查詢有助於在聯結下一個運算式之前,先篩選掉聯結的陣列項目。

此查詢相當於上述其中一項,但是使用的是子查詢:

SELECT VALUE
    COUNT(1)
FROM
    products p
JOIN 
    (SELECT VALUE t FROM t IN p.tags WHERE t.name IN ("winter", "fall"))
JOIN 
    (SELECT VALUE q FROM q IN p.onHandQuantities WHERE q.quantity BETWEEN 0 AND 10)
JOIN 
    (SELECT VALUE s FROM s IN p.warehouseStock WHERE NOT s.backstock)

假設 tags 陣列中只有一個項目符合篩選條件,而 nutrients 和 servings 陣列都有五個項目。 運算式 JOIN 接著會展開至 1 x 1 x 5 x 5 (25 個) 專案,而不是第一個查詢中的 1,000 個專案。

評估一次並參考多次

如果查詢中的運算式耗費資源,例如使用者定義函數 (UDF)、複雜字串或算術運算式,子查詢有助於將查詢最佳化。 您可以使用子查詢和 JOIN 運算式來評估運算式一次,但多次參考它。

假設您已定義下列 UDF (getTotalWithTax) 。

function getTotalWithTax(subTotal){
  return subTotal * 1.25;
}

下列查詢會多次執行 UDF getTotalWithTax

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

以下同樣的查詢只執行 UDF 一次:

SELECT VALUE {
    subtotal: p.price,
    total: totalPrice
}
FROM
    products p
JOIN
    (SELECT VALUE udf.getTotalWithTax(p.price)) totalPrice
WHERE
    totalPrice < 22.25

提示

請記住運算式的 JOIN 跨產品行為。 如果 UDF 運算式可以評估為 undefined ,您應該確定 JOIN 運算式一律會藉由從子查詢傳回物件,而不是直接產生值來產生單一資料列。

以外部參考資料來模擬聯結

您可能需要參考很少變更的靜態資料,例如 度量單位。 最好不要複製查詢中每個專案的靜態資料。 避免此重複專案可節省儲存空間,並藉由讓個別專案大小較小來改善寫入效能。 您可以使用子查詢來模擬內部聯結語意與靜態參考資料的集合。

例如,請考慮這組度量:

Name 乘數 基礎單位
ng Nanogram 1.00E-09 Gram
µg Microgram 1.00E-06 Gram
mg Milligram 1.00E-03 Gram
g Gram 1.00E+00 Gram
kg Kilogram 1.00E+03 Gram
Mg Megagram 1.00E+06 Gram
Gg Gigagram 1.00E+09 Gram

下列查詢以這組資料來模擬聯結,讓您將單位名稱加入至輸出:

SELECT
    s.id,
    (s.weight.quantity * m.multiplier) AS calculatedWeight,
    m.unit AS unitOfWeight
FROM
    shipments s
JOIN m IN (
    SELECT VALUE [
        {unit: 'ng', name: 'nanogram', multiplier: 0.000000001, baseUnit: 'gram'},
        {unit: 'µg', name: 'microgram', multiplier: 0.000001, baseUnit: 'gram'},
        {unit: 'mg', name: 'milligram', multiplier: 0.001, baseUnit: 'gram'},
        {unit: 'g', name: 'gram', multiplier: 1, baseUnit: 'gram'},
        {unit: 'kg', name: 'kilogram', multiplier: 1000, baseUnit: 'gram'},
        {unit: 'Mg', name: 'megagram', multiplier: 1000000, baseUnit: 'gram'},
        {unit: 'Gg', name: 'gigagram', multiplier: 1000000000, baseUnit: 'gram'}
    ]
)
WHERE
    s.weight.units = m.unit

純量子查詢

純量子查詢運算式是評估為單一值的子查詢。 純量子查詢運算式的值是子查詢) 投影 (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, "glove")).name
FROM
    products p
[
  {
    "id": "03230",
    "name": "Winter glove"
  },
  {
    "id": "03238"
  },
  {
    "id": "03229"
  }
]

彙總純量子查詢

彙總純量子查詢的投影或篩選條件中,有一個彙總函式評估為單一值。

作為第一個範例,請考慮具有下欄欄位的專案。

{
  "name": "Snow coat",
  "inventory": [
    {
      "location": "Redmond, WA",
      "quantity": 50
    },
    {
      "location": "Seattle, WA",
      "quantity": 30
    },
    {
      "location": "Washington, DC",
      "quantity": 25
    }
  ]
}

以下是其投影中具有單一彙總函式運算式的子查詢。 此查詢會計算每個專案的所有標記。

SELECT
    p.name,
    (SELECT VALUE COUNT(1) FROM i IN p.inventory) AS locationCount
FROM
    products p
[
  {
    "name": "Snow coat",
    "locationCount": 3
  }
]

以下是具有篩選準則的相同子查詢。

SELECT
    p.name,
    (SELECT VALUE COUNT(1) FROM i IN p.inventory WHERE ENDSWITH(i.location, "WA")) AS washingtonLocationCount
FROM
    products p
[
  {
    "name": "Snow coat",
    "washingtonLocationCount": 2
  }
]

以下是具有多個彙總函式運算式的另一個子查詢:

SELECT
    p.name,
    (SELECT
        COUNT(1) AS locationCount,
        SUM(i.quantity) AS totalQuantity
    FROM i IN p.inventory) AS inventoryData
FROM
    products p
[
  {
    "name": "Snow coat",
    "inventoryData": {
      "locationCount": 2,
      "totalQuantity": 75
    }
  }
]

最後,以下是在投影和篩選中具有匯總子查詢的查詢:

SELECT
    p.name,
    (SELECT VALUE AVG(q.quantity) FROM q IN p.inventory WHERE q.quantity > 10) AS averageInventory
FROM
    products p
WHERE
    (SELECT VALUE COUNT(1) FROM i IN p.inventory WHERE i.quantity > 10) >= 1
[
  {
    "name": "Snow coat",
    "averageInventory": 35
  }
]

撰寫此查詢更好的方式是在子查詢上聯結,然後在 SELECT 和 WHERE 子句中參考子查詢別名。 此查詢更有效率,因為您只需要在 join 陳述式內執行子查詢,而不需要在投影和篩選條件中都執行子查詢。

SELECT
    p.name,
    inventoryData.inventoryAverage
FROM
    products p
JOIN
    (SELECT 
        COUNT(1) AS inventoryCount, 
        AVG(i.quantity) as inventoryAverage 
    FROM i IN p.inventory 
    WHERE i.quantity > 10) AS inventoryData
WHERE
    inventoryData.inventoryCount >= 1

EXISTS 運算式

適用于 NoSQL 的 Azure Cosmos DB 查詢引擎支援 EXISTS 運算式。 此運算式是內建于適用于 NoSQL 的 Azure Cosmos DB 中的匯總純量子查詢。 EXISTS 會採用子查詢運算式,並在子查詢傳回任何資料列時傳回 true 。 否則會傳回 false

因為查詢引擎不會區分布林運算式和任何其他純量運算式,所以您可以在 和 WHERE 子句中使用。 EXISTSSELECT 此行為不同于 T-SQL,其中布林運算式僅限於篩選準則。

EXISTS如果子查詢傳回單一值, undefinedEXISTS 則評估為 false。 例如,請考慮下列不會傳回任何專案的查詢。

SELECT VALUE
    undefined

如果您使用 EXISTS 運算式和上述查詢做為子查詢,運算式會傳 false 回 。

SELECT
    EXISTS (SELECT VALUE undefined)
[
  {
    "$1": false
  }
]

如果省略上述子查詢中的 VALUE 關鍵字,子查詢會評估為具有單一空白物件的陣列。

SELECT
    undefined
[
  {}
]

此時,運算式會評估為 trueEXISTS 因為物件 ({}) 技術上結束。

SELECT 
    EXISTS (SELECT undefined) 
[
  {
    "$1": true
  }
]

的常見使用案例 ARRAY_CONTAINS 是藉由陣列中的專案存在來篩選項目。 在此情況下,我們會檢查陣列是否 tags 包含名為 「outerwear」 的專案。

SELECT
    p.name,
    p.tags
FROM
    products p
WHERE
    ARRAY_CONTAINS(p.tags, "outerwear")

相同的查詢可以做 EXISTS 為替代選項使用。

SELECT
    p.name,
    p.tags
FROM
    products p
WHERE
    EXISTS (SELECT VALUE t FROM t IN p.tags WHERE t = "outerwear")

此外, ARRAY_CONTAINS 只能檢查值是否等於陣列中的任何專案。 如果您需要對陣列屬性進行更複雜的篩選,請改用 JOIN

請考慮集合中的這個範例專案,其中每個專案都包含一個 accessories 陣列。

{
  "name": "Unobtani road bike",
  "accessories": [
    {
      "name": "Front/rear tire",
      "type": "tire",
      "quantityOnHand": 5
    },
    {
      "name": "9-speed chain",
      "type": "chains",
      "quantityOnHand": 25
    },
    {
      "name": "Clip-in pedals",
      "type": "pedals",
      "quantityOnHand": 15
    }
  ]
}

現在,請考慮下列查詢,根據每個專案內陣列中的 和 quantityOnHand 屬性進行篩選 type

SELECT
    p.name,
    a.name AS accessoryName
FROM
    products p
JOIN
    a IN p.accessories
WHERE
    a.type = "chains" AND
    a.quantityOnHand >= 10
[
  {
    "name": "Unobtani road bike",
    "accessoryName": "9-speed chain"
  }
]

針對集合中的每個專案,會使用其陣列元素執行交叉乘積。 這項 JOIN 作業可讓您篩選陣列內的屬性。 不過,此查詢的 RU 耗用量相當重要。 例如,如果 1,000 個專案在每個陣列中有 100 個專案,它會展開至 1,000 x 100 (也就是 100,000 個) 元組。

使用 EXISTS 有助於避免這種昂貴的跨產品。 在下一個範例中,查詢會篩選子查詢內的 EXISTS 陣列元素。 如果陣列元素符合篩選準則,則您投影它並 EXISTS 評估為 true。

SELECT VALUE
    p.name
FROM
    products p
WHERE
    EXISTS (SELECT VALUE 
        a 
    FROM 
        a IN p.accessories
    WHERE
        a.type = "chains" AND
        a.quantityOnHand >= 10)
[
  "Unobtani road bike"
]

查詢也可以別名 EXISTS 並參考投影中的別名:

SELECT
    p.name,
    EXISTS (SELECT VALUE
        a 
    FROM 
        a IN p.accessories
    WHERE
        a.type = "chains" AND
        a.quantityOnHand >= 10) AS chainAccessoryAvailable
FROM
    products p
[
  {
    "name": "Unobtani road bike",
    "chainAccessoryAvailable": true
  }
]

ARRAY 運算式

您可以使用 ARRAY 運算式,將查詢的結果投影為數組。 您只能在查詢的 子句內 SELECT 使用此運算式。

針對這些範例,假設至少有一個容器具有這個專案。

{
  "name": "Radimer mountain bike",
  "tags": [
    {
      "name": "road"
    },
    {
      "name": "bike"
    },
    {
      "name": "competitive"
    }
  ]
}

在此第一個範例中,運算式會在 子句中使用 SELECT

SELECT
    p.name,
    ARRAY (SELECT VALUE t.name FROM t in p.tags) AS tagNames
FROM
    products p
[
  {
    "name": "Radimer mountain bike",
    "tagNames": [
      "road",
      "bike",
      "competitive"
    ]
  }
]

與其他子查詢一樣,可以篩選運算式 ARRAY

SELECT
    p.name,
    ARRAY (SELECT VALUE t.name FROM t in p.tags) AS tagNames,
    ARRAY (SELECT VALUE t.name FROM t in p.tags WHERE CONTAINS(t.name, "bike")) AS bikeTagNames
FROM
    products p
[
  {
    "name": "Radimer mountain bike",
    "tagNames": [
      "road",
      "bike",
      "competitive"
    ],
    "bikeTagNames": [
      "bike"
    ]
  }
]

陣列運算式也可以在子查詢中的 子句之後 FROM

SELECT
    p.name,
    n.t.name AS nonBikeTagName
FROM
    products p
JOIN
    n IN (SELECT VALUE ARRAY(SELECT t FROM t in p.tags WHERE t.name NOT LIKE "%bike%"))
[
  {
    "name": "Radimer mountain bike",
    "nonBikeTagName": "road"
  },
  {
    "name": "Radimer mountain bike",
    "nonBikeTagName": "competitive"
  }
]