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í SELECT
dotaz . 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
, onHandQuantities
a 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 SELECT
WHERE
. 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í undefined
se 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"
}
]