Poddotazy 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í SELECT
. 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ý zpracovává vnější dotaz.
- Nekoelované: 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.
Poddotazy je možné 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 platí pouze vícehodnotové a skalární poddotazy. Poddotaz s více hodnotami můžete použít pouze v FROM
klauzuli jako relační výraz. Skalární poddotaz můžete použít jako skalární výraz v SELECT
klauzuli nebo WHERE
jako relační výraz v klauzuli FROM
.
Poddotazy s více hodnotami
Poddotazy s více hodnotami vrací sadu položek a vždy se používají v rámci FROM
klauzule. Používají se pro:
- Optimalizace
JOIN
výrazů (self-join) - Vyhodnocení drahých výrazů jednou a odkazování na vícekrát
Optimalizace výrazů s vlastním spojením
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 místo za všemi křížovými spojeními v WHERE
klauzuli.
Představte si následující dotaz:
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
Pro tento dotaz index odpovídá libovolné položce, která má značku s "zimou" nebo "podzimem", alespoň jednou quantity
mezi nulou a deseti a alespoň jedním skladem, kde backstock
je false
. name
Výraz JOIN
zde provede křížový součin všech položek tags
, onHandQuantities
a warehouseStock
pole 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>
řazenou kolekci členů. Pokud například odpovídající položka obsahovala v každé ze tří polí deset položek, rozbalí se na 1 x 10 x 10 x 10
řazené kolekce členů (tj . 1 000). Použití poddotazů zde může pomoct při filtrování připojených položek pole před spojením s dalším výrazem.
Tento dotaz je ekvivalentní předchozímu dotazu, ale používá poddotazy:
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 pro množství i pole akcií existuje pět položek. Výrazy JOIN
se pak rozbalí na 1 x 1 x 5 x 5
(25) položek, nikoli na 1 000 položek v prvním dotazu.
Vyhodnocení jednou a mnohokrát odkazování
Poddotazy můžou pomoct optimalizovat dotazy s nákladnými výrazy, 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í definovanou uživatelem definovanougetTotalWithTax
.
function getTotalWithTax(subTotal){
return subTotal * 1.25;
}
Následující dotaz spustí uživatelem getTotalWithTax
definovanou uživatelem 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ý spouští UDF pouze 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ů napříč JOIN
produkty. Pokud se výraz UDF může vyhodnotit jako undefined
, měli byste zajistit, aby výraz vždy vytvořil jeden řádek tak, že JOIN
vrátí objekt z poddotazu, a ne přímo hodnotu.
Napodobení spojení s externími referenčními daty
Často budete muset odkazovat na statická data, která se zřídka mění, například měrné jednotky. Je ideální ne duplikovat statická data pro každou položku v dotazu. Vyhnete se tomu, že se tato duplicita šetří na úložišti a zlepšuje 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.
Představte si 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í poddotaz je poddotaz, který se vyhodnotí jako jedna hodnota. Hodnota skalárního poddotazového výrazu je hodnota projekce (SELECT
klauzule) poddotazu. 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 i WHERE
v klauzulích.
Použití skalárního poddotazu vždy nepomůže optimalizovat dotaz. Například předání skalárního poddotazu jako argumentu funkcím definovanému systémem nebo uživatelem neposkytuje žádnou výhodu při snížení spotřeby nebo latence jednotky prostředků .RU.
Skalární poddotazy lze dále klasifikovat jako:
- Skalární poddotazy jednoduchého výrazu
- Agregace skalárních poddotazů
Skalární poddotazy jednoduchého výrazu
Skalární poddotaz jednoduchých výrazů je korelovaný poddotaz, který obsahuje 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 neexistuje žádný korelovaný kontext.
Jako první příklad zvažte tento triviální dotaz.
SELECT
1 AS a,
2 AS b
Tento dotaz můžete přepsat pomocí skalárního poddotazu jednoduchého výrazu.
SELECT
(SELECT VALUE 1) AS a,
(SELECT VALUE 2) AS b
Oba dotazy vytvářejí stejný výstup.
[
{
"a": 1,
"b": 2
}
]
Tento další příklad dotazu zřetězí jedinečný identifikátor s předponou jako skalární poddotaz jednoduchých výrazů.
SELECT
(SELECT VALUE Concat('ID-', p.id)) AS internalId
FROM
products p
Tento příklad používá skalární poddotaz jednoduchých výrazů k vrácení relevantních polí pouze 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": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
"name": "Winter glove"
},
{
"id": "bbbbbbbb-1111-2222-3333-cccccccccccc"
},
{
"id": "cccccccc-2222-3333-4444-dddddddddddd"
}
]
Agregace skalárních poddotazů
Agregační skalární poddotaz je poddotaz, který má v projekci nebo filtru agregační funkci, která se vyhodnotí jako jedna hodnota.
Jako první příklad zvažte 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 agregačním výrazem 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 agregačními výrazy funkcí:
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 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
}
]
Vhodnější způsob, jak tento dotaz napsat, je spojit se s poddotazem a odkazovat na alias poddotazů v klauzulích SELECT i WHERE. Tento dotaz je efektivnější, protože potřebujete poddotaz spustit pouze v rámci příkazu join, a ne v projekci i 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 Azure Cosmos DB for NoSQL podporuje EXISTS
výrazy. Tento výraz je agregační 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í všechny řádky. V opačném případě se vrátí false
.
Vzhledem k tomu, že dotazovací modul nerozlišuje mezi logickými výrazy a žádnými dalšími skalárními výrazy, můžete je použít EXISTS
v SELECT
obou klauzulích i WHERE
v klauzulích. Toto chování se liší od jazyka T-SQL, kdy je logický výraz omezen pouze na filtry.
EXISTS
Pokud poddotaz vrátí jednu hodnotu, která je undefined
, EXISTS
vyhodnotí se jako false. Představte si například následující dotaz, který nevrací nic.
SELECT VALUE
undefined
Pokud použijete EXISTS
výraz a předchozí dotaz jako poddotaz, výraz vrátí false
.
SELECT
EXISTS (SELECT VALUE undefined)
[
{
"$1": false
}
]
Pokud je klíčové slovo HODNOTA v předchozím poddotazu vynecháno, poddotaz se vyhodnotí jako pole s jedním prázdným objektem.
SELECT
undefined
[
{}
]
V tomto okamžiku se EXISTS
výraz vyhodnotí jako true
od technicky ukončeného objektu ({}
).
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 "vnější oděvy".
SELECT
p.name,
p.tags
FROM
products p
WHERE
ARRAY_CONTAINS(p.tags, "outerwear")
Stejný dotaz může 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 může zkontrolovat, ARRAY_CONTAINS
zda je hodnota rovna libovolnému prvku v rámci pole. Pokud potřebujete složitější filtry vlastností pole, použijte JOIN
místo toho.
Představte si tuto ukázkovou položku v sadě s více položkami, které obsahují accessories
pole.
{
"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ď zvažte následující dotaz, který filtruje na type
quantityOnHand
základě vlastností pole 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 provádí křížový součin se svými prvky pole. Tato JOIN
operace umožňuje filtrovat vlastnosti v rámci pole. Spotřeba RU tohoto dotazu je ale významná. Pokud například 1 000 položek obsahovalo v každé matici 100 položek, rozšíří se na 1,000 x 100
100 000 řazených kolekcí členů.
Použití EXISTS
vám pomůže vyhnout se tomuto nákladnému křížovému produktu. V tomto dalším příkladu filtruje dotaz na prvky pole v poddotadu EXISTS
. Pokud prvek pole odpovídá filtru, pak ho promítnete a EXISTS
vyhodnotíte 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 mohou také aliasy EXISTS
a odkazovat na alias v projekci:
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
Pomocí výrazu ARRAY
můžete promítnout výsledky dotazu jako pole. Tento výraz můžete použít pouze v rámci SELECT
klauzule 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 SELECT
se výraz použije v rámci klauzule.
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 možné filtry s výrazem 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"
]
}
]
Výrazy pole můžou pocházet také za FROM
klauzulí 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"
}
]