Subquery's in Azure Cosmos DB for NoSQL
VAN TOEPASSING OP: NoSQL
Een subquery is een query die is genest binnen een andere query in Azure Cosmos DB voor NoSQL. Een subquery wordt ook wel een binnenste query of binnenste SELECT
query genoemd. De instructie die een subquery bevat, wordt meestal een buitenste query genoemd.
Typen subquery's
Er zijn twee hoofdtypen subquery's:
- Gecorreleerd: een subquery die verwijst naar waarden uit de buitenste query. De subquery wordt eenmaal geëvalueerd voor elke rij die door de buitenste query wordt verwerkt.
- Niet-gecorreleerd: een subquery die onafhankelijk is van de buitenste query. Deze kan zelfstandig worden uitgevoerd zonder dat deze afhankelijk is van de buitenste query.
Notitie
Azure Cosmos DB ondersteunt alleen gecorreleerde subquery's.
Subquery's kunnen verder worden geclassificeerd op basis van het aantal rijen en kolommen dat ze retourneren. Er zijn drie typen:
- Tabel: Retourneert meerdere rijen en meerdere kolommen.
- Meerdere waarden: retourneert meerdere rijen en één kolom.
- Scalar: retourneert één rij en één kolom.
Query's in Azure Cosmos DB for NoSQL retourneren altijd één kolom (een eenvoudige waarde of een complex item). Daarom zijn alleen subquery's met meerdere waarden en scalaire subquery's van toepassing. U kunt een subquery met meerdere waarden alleen in de FROM
component gebruiken als een relationele expressie. U kunt een scalaire subquery gebruiken als een scalaire expressie in de SELECT
of WHERE
component, of als een relationele expressie in de FROM
component.
Subquery's met meerdere waarden
Subquery's met meerdere waarden retourneren een set items en worden altijd binnen de FROM
component gebruikt. Ze worden gebruikt voor:
JOIN
Expressies (self-join) optimaliseren.- Dure expressies één keer evalueren en meerdere keren verwijzen.
Self-join-expressies optimaliseren
Subquery's met meerdere waarden kunnen expressies optimaliseren JOIN
door predicaten na elke select-many-expressie te pushen in plaats van na alle cross-joins in de WHERE
component.
Houd rekening met de volgende query:
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
Voor deze query komt de index overeen met elk item met een tag met een van de name
'winter' of 'herfst', ten minste één quantity
tussen nul en tien, en ten minste één magazijn waar het backstock
isfalse
. De JOIN
expressie hier voert het kruisproduct uit van alle items van tags
, onHandQuantities
en warehouseStock
matrices voor elk overeenkomend item voordat een filter wordt toegepast.
De WHERE
component past vervolgens het filterpredicaat toe op elke <c, t, n, s>
tuple. Als een overeenkomend item bijvoorbeeld tien items in elk van de drie matrices bevat, wordt het uitgebreid tot 1 x 10 x 10 x 10
(dat wil gezegd 1000) tuples. Het gebruik van subquery's hier kan helpen bij het filteren van gekoppelde matrixitems voordat u met de volgende expressie gaat samenvoegen.
Deze query is gelijk aan de voorgaande, maar gebruikt subquery's:
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)
Stel dat slechts één item in de tagsmatrix overeenkomt met het filter en dat er vijf items zijn voor zowel hoeveelheid als voorraadmatrices. De JOIN
expressies worden vervolgens uitgebreid naar 1 x 1 x 5 x 5
(25) items, in plaats van 1000 items in de eerste query.
Eenmaal evalueren en vaak verwijzen
Met subquery's kunt u query's optimaliseren met dure expressies, zoals door de gebruiker gedefinieerde functies (UDF's), complexe tekenreeksen of rekenkundige expressies. U kunt een subquery samen met een JOIN
expressie gebruiken om de expressie eenmaal te evalueren, maar vaak ernaar te verwijzen.
Stel dat u de volgende UDF (getTotalWithTax
) hebt gedefinieerd.
function getTotalWithTax(subTotal){
return subTotal * 1.25;
}
Met de volgende query wordt de UDF getTotalWithTax
meerdere keren uitgevoerd:
SELECT VALUE {
subtotal: p.price,
total: udf.getTotalWithTax(p.price)
}
FROM
products p
WHERE
udf.getTotalWithTax(p.price) < 22.25
Hier volgt een equivalente query die de UDF slechts één keer uitvoert:
SELECT VALUE {
subtotal: p.price,
total: totalPrice
}
FROM
products p
JOIN
(SELECT VALUE udf.getTotalWithTax(p.price)) totalPrice
WHERE
totalPrice < 22.25
Tip
Houd rekening met het gedrag van JOIN
expressies voor meerdere producten. Als de UDF-expressie kan evalueren undefined
, moet u ervoor zorgen dat de JOIN
expressie altijd één rij produceert door een object uit de subquery te retourneren in plaats van de waarde rechtstreeks.
Samenvoegen met externe referentiegegevens nabootsen
Mogelijk moet u vaak verwijzen naar statische gegevens die zelden veranderen, zoals maateenheden. Het is ideaal om statische gegevens niet te dupliceren voor elk item in een query. Als u deze duplicatie vermijdt, bespaart u opslag en verbetert u de schrijfprestaties door de afzonderlijke itemgrootte kleiner te houden. U kunt een subquery gebruiken om semantiek van inner-joins na te bootsen met een verzameling statische referentiegegevens.
Denk bijvoorbeeld aan deze set metingen:
Naam | Vermenigvuldigingsfactor | Basiseenheid | |
---|---|---|---|
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 |
Met de volgende query wordt het samenvoegen van deze gegevens nagebootst, zodat u de naam van de eenheid toevoegt aan de uitvoer:
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
Scalaire subquery's
Een scalaire subquery-expressie is een subquery die één waarde oplevert. De waarde van de scalaire subquery-expressie is de waarde van de projectie (SELECT
component) van de subquery. U kunt een scalaire subquery-expressie gebruiken op veel plaatsen waar een scalaire expressie geldig is. U kunt bijvoorbeeld een scalaire subquery gebruiken in elke expressie in zowel de SELECT
als WHERE
de component.
Het gebruik van een scalaire subquery helpt niet altijd om uw query te optimaliseren. Het doorgeven van een scalaire subquery als argument aan een systeem of door de gebruiker gedefinieerde functies biedt bijvoorbeeld geen voordeel bij het verminderen van ru-verbruik (Resource Unit) of latentie.
Scalaire subquery's kunnen verder worden geclassificeerd als:
- Scalaire scalaire subquery's voor eenvoudige expressies
- Scalaire subquery's aggregeren
Scalaire scalaire subquery's voor eenvoudige expressies
Een scalaire scalaire subquery voor eenvoudige expressies is een gecorreleerde subquery met een SELECT
component die geen statistische expressies bevat. Deze subquery's bieden geen optimalisatievoordelen omdat de compiler deze converteert naar een grotere eenvoudige expressie. Er is geen gecorreleerde context tussen de binnenste en buitenste query's.
Bekijk deze triviale query als eerste voorbeeld.
SELECT
1 AS a,
2 AS b
U kunt deze query opnieuw schrijven met behulp van een scalaire scalaire subquery voor eenvoudige expressies.
SELECT
(SELECT VALUE 1) AS a,
(SELECT VALUE 2) AS b
Beide query's produceren dezelfde uitvoer.
[
{
"a": 1,
"b": 2
}
]
Met deze volgende voorbeeldquery wordt de unieke id samengevoegd met een voorvoegsel als scalaire scalaire subquery voor eenvoudige expressies.
SELECT
(SELECT VALUE Concat('ID-', p.id)) AS internalId
FROM
products p
In dit voorbeeld wordt een scalaire scalaire scalaire subquery gebruikt om alleen de relevante velden voor elk item te retourneren. De query voert iets uit voor elk item, maar bevat alleen het verwachte veld als het voldoet aan het filter in de subquery.
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"
}
]
Scalaire subquery's aggregeren
Een geaggregeerde scalaire subquery is een subquery met een statistische functie in de projectie of het filter die resulteert in één waarde.
Bekijk als eerste voorbeeld een item met de volgende velden.
{
"name": "Snow coat",
"inventory": [
{
"location": "Redmond, WA",
"quantity": 50
},
{
"location": "Seattle, WA",
"quantity": 30
},
{
"location": "Washington, DC",
"quantity": 25
}
]
}
Hier volgt een subquery met één statistische functie-expressie in de projectie. Met deze query worden alle tags voor elk item geteld.
SELECT
p.name,
(SELECT VALUE COUNT(1) FROM i IN p.inventory) AS locationCount
FROM
products p
[
{
"name": "Snow coat",
"locationCount": 3
}
]
Hier volgt dezelfde subquery met een filter.
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
}
]
Hier volgt een andere subquery met meerdere expressies voor statistische functies:
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
}
}
]
Ten slotte ziet u hier een query met een geaggregeerde subquery in zowel de projectie als het filter:
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
}
]
Een optimale manier om deze query te schrijven, is door deel te nemen aan de subquery en te verwijzen naar de subqueryalias in zowel de SELECT- als WHERE-componenten. Deze query is efficiënter omdat u de subquery alleen binnen de join-instructie moet uitvoeren en niet in zowel de projectie als het filter.
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-expressie
De query-engine van Azure Cosmos DB for NoSQL ondersteunt EXISTS
expressies. Deze expressie is een geaggregeerde scalaire subquery die is ingebouwd in Azure Cosmos DB for NoSQL. EXISTS
gebruikt een subquery-expressie en retourneert true
als de subquery rijen retourneert. Anders wordt het geretourneerd false
.
Omdat de query-engine geen onderscheid maakt tussen booleaanse expressies en andere scalaire expressies, kunt u in beide SELECT
en WHERE
componenten gebruikenEXISTS
. Dit gedrag is in tegenstelling tot T-SQL, waarbij een Boole-expressie wordt beperkt tot alleen filters.
Als de EXISTS
subquery één waarde undefined
retourneert, EXISTS
resulteert dit in onwaar. Denk bijvoorbeeld aan de volgende query die niets retourneert.
SELECT VALUE
undefined
Als u de EXISTS
expressie en de voorgaande query als subquery gebruikt, retourneert false
de expressie.
SELECT
EXISTS (SELECT VALUE undefined)
[
{
"$1": false
}
]
Als het trefwoord VALUE in de voorgaande subquery wordt weggelaten, resulteert de subquery in een matrix met één leeg object.
SELECT
undefined
[
{}
]
Op dat moment wordt de EXISTS
expressie geëvalueerd true
omdat het object ({}
) technisch wordt afgesloten.
SELECT
EXISTS (SELECT undefined)
[
{
"$1": true
}
]
Een veelvoorkomend gebruiksvoorbeeld is het filteren van ARRAY_CONTAINS
een item op basis van het bestaan van een item in een matrix. In dit geval controleren we of de matrix een item bevat met de tags
naam 'outerwear'.
SELECT
p.name,
p.tags
FROM
products p
WHERE
ARRAY_CONTAINS(p.tags, "outerwear")
Dezelfde query kan worden gebruikt EXISTS
als een alternatieve optie.
SELECT
p.name,
p.tags
FROM
products p
WHERE
EXISTS (SELECT VALUE t FROM t IN p.tags WHERE t = "outerwear")
ARRAY_CONTAINS
Daarnaast kan alleen worden gecontroleerd of een waarde gelijk is aan een element in een matrix. Als u complexere filters voor matrixeigenschappen nodig hebt, gebruikt JOIN
u in plaats daarvan.
Bekijk dit voorbeelditem in een set met meerdere items die elk een accessories
matrix bevatten.
{
"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
}
]
}
Bekijk nu de volgende query die filtert op basis van de type
en quantityOnHand
eigenschappen in de matrix binnen elk item.
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"
}
]
Voor elk van de items in de verzameling wordt een kruisproduct uitgevoerd met de bijbehorende matrixelementen. Met deze JOIN
bewerking kunt u filteren op eigenschappen binnen de matrix. Het RU-verbruik van deze query is echter aanzienlijk. Als er bijvoorbeeld 1000 items in elke matrix 100 items bevatten, wordt deze uitgebreid tot 1,000 x 100
(dat wil gezegd 100.000) tuples.
Het gebruik EXISTS
kan helpen om dit dure cross-product te voorkomen. In dit volgende voorbeeld filtert de query op matrixelementen in de EXISTS
subquery. Als een matrixelement overeenkomt met het filter, projecteert u het en EXISTS
resulteert u in waar.
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"
]
Query's kunnen ook EXISTS
aliasen en verwijzen naar de alias in de projectie:
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
}
]
MATRIX-expressie
U kunt de ARRAY
expressie gebruiken om de resultaten van een query als matrix te projecteren. U kunt deze expressie alleen gebruiken binnen de SELECT
component van de query.
Voor deze voorbeelden gaan we ervan uit dat er een container is met ten minste dit item.
{
"name": "Radimer mountain bike",
"tags": [
{
"name": "road"
},
{
"name": "bike"
},
{
"name": "competitive"
}
]
}
In dit eerste voorbeeld wordt de expressie in de SELECT
component gebruikt.
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"
]
}
]
Net als bij andere subquery's zijn filters met de ARRAY
expressie mogelijk.
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"
]
}
]
Matrixexpressies kunnen ook na de FROM
component in subquery's komen.
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"
}
]