Poddotazování ve službě Azure Cosmos DB for NoSQL

PLATÍ PRO: NoSQL

Poddotaz je dotaz vnořený do jiného dotazu ve službě Azure Cosmos DB for NoSQL. Poddotaz se také nazývá vnitřní dotaz nebo vnitřní SELECTdotaz . Příkaz, který obsahuje poddotaz, se obvykle nazývá vnější dotaz.

Typy poddotazů

Existují dva hlavní typy poddotazů:

  • Korelace: Poddotaz, který odkazuje na hodnoty z vnějšího dotazu. Poddotaz se vyhodnocuje jednou pro každý řádek, který vnější dotaz zpracuje.
  • Nekorelovaný: Poddotaz, který je nezávislý na vnějším dotazu. Můžete ho spustit samostatně, aniž byste museli spoléhat na vnější dotaz.

Poznámka

Azure Cosmos DB podporuje pouze korelované poddotazy.

Poddotaz lze dále klasifikovat na základě počtu řádků a sloupců, které vrací. Existují tři typy:

  • Tabulka: Vrátí více řádků a více sloupců.
  • Více hodnot: Vrátí více řádků a jeden sloupec.
  • Skalární: Vrátí jeden řádek a jeden sloupec.

Dotazy ve službě Azure Cosmos DB for NoSQL vždy vrací jeden sloupec (jednoduchou hodnotu nebo složitou položku). Proto lze použít pouze poddotaz s více hodnotami a skalární poddotaz. Poddotaz s více hodnotami můžete použít pouze v klauzuli FROM jako relační výraz. Skalární poddotaz můžete použít jako skalární výraz v klauzuli SELECT nebo WHERE jako relační výraz v klauzuli FROM .

Poddotaz s více hodnotami

Poddotaz s více hodnotami vrací sadu položek a vždy se používají v rámci klauzule FROM . Používají se k:

  • Optimalizace JOIN výrazů (self-join)
  • Vyhodnocení nákladných výrazů jednou a vícenásobné odkazování

Optimalizace výrazů automatického spojení

Poddotazy s více hodnotami můžou optimalizovat JOIN výrazy tím, že za každý výraz select-many nasdílí predikáty, nikoli po všech křížových spojeních v klauzuli WHERE .

Zamyslete se nad následujícím dotazem:

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

U tohoto dotazu index odpovídá každé položce, která má značku se značkou name"winter" nebo "fall", alespoň jednu quantity mezi nulou a deseti a alespoň jedním skladem, kde backstock je false. Výraz JOIN zde provádí křížový součin všech polí tags, onHandQuantitiesa warehouseStock pro každou odpovídající položku před použití jakéhokoli filtru.

Klauzule WHERE pak použije predikát filtru na každou <c, t, n, s> kolekci členů. Pokud například odpovídající položka měla v každé ze tří polí deset položek, rozbalí se na 1 x 10 x 10 x 10 (tj. 1 000) řazených kolekcí členů. Použití poddotazů může pomoct při filtrování spojených položek pole před spojením s dalším výrazem.

Tento dotaz odpovídá předchozímu dotazu, ale používá poddotaz:

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)

Předpokládejme, že filtr odpovídá pouze jedné položce v poli značek a existuje pět položek pro pole živin a obsluhy. Výrazy JOIN se pak rozbalí na 1 x 1 x 5 x 5 (25) položek namísto 1 000 položek v prvním dotazu.

Vyhodnocení jednou a mnohokrát odkazování

Poddotazy můžou pomoct optimalizovat dotazy pomocí nákladných výrazů, jako jsou uživatelem definované funkce (UDF), komplexní řetězce nebo aritmetické výrazy. Poddotaz můžete použít spolu s výrazem JOIN k vyhodnocení výrazu jednou, ale mnohokrát na něj odkazovat.

Předpokládejme, že máte definovanou následující UDF (getTotalWithTax).

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

Následující dotaz spustí UDF getTotalWithTax několikrát:

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

Tady je ekvivalentní dotaz, který spustí UDF jenom jednou:

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

Tip

Mějte na paměti chování výrazů JOIN mezi produkty. Pokud se výraz UDF může vyhodnotit jako undefined, měli byste zajistit, aby výraz vždy vytvořil jeden řádek tím, že JOIN vrátí objekt z poddotazu místo hodnoty přímo.

Mimické spojení s externími referenčními daty

Často můžete potřebovat odkazovat na statická data, která se mění jen zřídka, například měrné jednotky. Ideální je ne duplikovat statická data pro každou položku v dotazu. Díky tomu, že se této duplicitě zabráníte, ušetříte úložiště a zvýšíte výkon zápisu tím, že velikost jednotlivých položek bude menší. Poddotaz můžete použít k napodobení sémantiky vnitřního spojení s kolekcí statických referenčních dat.

Zvažte například tuto sadu měření:

Název Multiplikátor Základní jednotka
ng Nanogram 1.00E-09 Gram
µg Mikrogramů 1.00E-06 Gram
mg Miligram 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

Následující dotaz napodobuje spojení s těmito daty, takže do výstupu přidáte název jednotky:

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

Skalární poddotazy

Skalární výraz poddotaz je poddotaz, který se vyhodnotí jako jedna hodnota. Hodnota skalárního poddotazového výrazu je hodnota projekce (SELECT klauzule) poddotaz. Skalární poddotazový výraz můžete použít na mnoha místech, kde je skalární výraz platný. Skalární poddotaz můžete například použít v libovolném výrazu SELECT v klauzulích a WHERE .

Použití skalárního poddotazu nemusí vždy pomoct optimalizovat dotaz. Například předání skalárního poddotazu jako argumentu do systémových nebo uživatelem definovaných funkcí neposkytuje žádnou výhodu při snížení spotřeby jednotek prostředků nebo latence.

Skalární poddotaz lze dále klasifikovat jako:

  • Skalární poddotaz s jednoduchým výrazem
  • Agregace skalárních poddotazů

Skalární poddotaz s jednoduchým výrazem

Skalární poddotaz jednoduchých výrazů je korelovaný poddotaz, který má SELECT klauzuli, která neobsahuje žádné agregační výrazy. Tyto poddotazy neposkytují žádné výhody optimalizace, protože kompilátor je převede na jeden větší jednoduchý výraz. Mezi vnitřními a vnějšími dotazy není žádný korelovaný kontext.

Jako první příklad si představte tento triviální dotaz.

SELECT
    1 AS a,
    2 AS b

Tento dotaz můžete přepsat pomocí skalárního poddotazu s jednoduchým výrazem.

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

Oba dotazy vygenerují stejný výstup.

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

Tento další ukázkový dotaz zřetězí jedinečný identifikátor s předponou jako skalární poddotaz s jednoduchým výrazem.

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

Tento příklad používá skalární poddotaz simple-expression k vrácení pouze relevantních polí pro každou položku. Dotaz vypíše něco pro každou položku, ale obsahuje pouze předpokládané pole, pokud splňuje filtr v rámci poddotazu.

SELECT
    p.id,
    (SELECT p.name WHERE CONTAINS(p.name, "glove")).name
FROM
    products p
[
  {
    "id": "03230",
    "name": "Winter glove"
  },
  {
    "id": "03238"
  },
  {
    "id": "03229"
  }
]

Agregace skalárních poddotazů

Agregovaný skalární poddotaz je poddotaz, který má v projekci nebo filtru agregační funkci, která se vyhodnotí jako jedna hodnota.

Jako první příklad si představte položku s následujícími poli.

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

Tady je poddotaz s jedním výrazem agregační funkce v projekci. Tento dotaz spočítá všechny značky pro každou položku.

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

Tady je stejný poddotaz s filtrem.

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

Tady je další poddotaz s více výrazy agregační funkce:

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

Nakonec tady je dotaz s agregačním poddotazem v projekci i ve filtru:

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

Optimální způsob, jak tento dotaz napsat, je spojit se na poddotaz a odkazovat na alias poddotaz v klauzuli SELECT i WHERE. Tento dotaz je efektivnější, protože poddotaz musíte spustit pouze v rámci příkazu join, a ne v projekci a filtru.

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

Výraz EXISTS

Dotazovací modul služby Azure Cosmos DB for NoSQL podporuje EXISTS výrazy. Tento výraz je agregovaný skalární poddotaz integrovaný do služby Azure Cosmos DB for NoSQL. EXISTS vezme výraz poddotaz a vrátí true , pokud poddotaz vrátí nějaké řádky. V opačném případě vrátí false.

Vzhledem k tomu, že dotazovací modul nerozlišuje mezi logickými výrazy a žádnými jinými skalárními výrazy, můžete použít EXISTS v klauzulích i SELECTWHERE . Toto chování se liší od jazyka T-SQL, kde je logický výraz omezen pouze na filtry.

EXISTS Pokud poddotaz vrátí jednu hodnotu , EXISTS vyhodnotí undefinedse jako nepravda. Představte si například následující dotaz, který nevrací nic.

SELECT VALUE
    undefined

Pokud jako poddotaz použijete EXISTS výraz a předchozí dotaz, výraz vrátí false.

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

Pokud je klíčové slovo VALUE v předchozím poddotazu vynecháno, vyhodnotí se poddotaz jako pole s jedním prázdným objektem.

SELECT
    undefined
[
  {}
]

V tomto okamžiku EXISTS se výraz vyhodnotí jako true , protože objekt ({}) technicky opustí.

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

Běžným případem ARRAY_CONTAINS použití je filtrování položky podle existence položky v poli. V tomto případě kontrolujeme, jestli tags pole obsahuje položku s názvem "outerwear".

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

Stejný dotaz můžete použít EXISTS jako alternativní možnost.

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

Kromě toho lze pouze zkontrolovat, ARRAY_CONTAINS zda je hodnota rovna libovolnému prvku v rámci pole. Pokud potřebujete složitější filtry pro vlastnosti pole, použijte JOIN místo toho .

Podívejte se na tuto ukázkovou položku v sadě s více položkami, z nichž každá obsahuje pole 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
    }
  ]
}

Teď se podívejte na následující dotaz, který filtruje na type základě vlastností a quantityOnHand v poli v rámci každé položky.

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

Pro každou položku v kolekci se provede křížový součin s prvky pole. Tato JOIN operace umožňuje filtrovat vlastnosti v rámci pole. Spotřeba RU tohoto dotazu je však významná. Pokud například 1 000 položek obsahovalo v každé matici 100 položek, rozbalí se 1,000 x 100 (tj. 100 000) řazených kolekcí členů.

Použití EXISTS může pomoct vyhnout se tomuto drahému produktu. V tomto dalším příkladu dotaz filtruje prvky pole v rámci poddotaz EXISTS . Pokud element pole odpovídá filtru, promítnete ho a EXISTS vyhodnotíte ho jako 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"
]

Dotazy můžou aliasy v projekci také aliasy EXISTS aliasy a odkazovat na ho:

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

Výraz ARRAY

Výraz můžete použít ARRAY k promítaní výsledků dotazu jako pole. Tento výraz můžete použít pouze v klauzuli SELECT dotazu.

V těchto příkladech předpokládejme, že existuje kontejner s alespoň touto položkou.

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

V tomto prvním příkladu se v klauzuli SELECT používá výraz .

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

Stejně jako u jiných poddotazů jsou filtry s výrazem ARRAY možné.

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

Maticové výrazy můžou být také za klauzulí FROM v poddotazech.

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