Underfrågor i Azure Cosmos DB för NoSQL
GÄLLER FÖR: NoSQL
En underfråga är en fråga kapslad i en annan fråga i Azure Cosmos DB för NoSQL. En underfråga kallas även för en inre fråga eller en inre SELECT
. Instruktionen som innehåller en underfråga kallas vanligtvis för en yttre fråga.
Typer av underfrågor
Det finns två huvudtyper av underfrågor:
- Korrelerad: En underfråga som refererar till värden från den yttre frågan. Underfrågan utvärderas en gång för varje rad som den yttre frågan bearbetar.
- Icke-korrelerad: En underfråga som är oberoende av den yttre frågan. Den kan köras på egen hand utan att förlita sig på den yttre frågan.
Kommentar
Azure Cosmos DB stöder endast korrelerade underfrågor.
Underfrågor kan klassificeras ytterligare baserat på antalet rader och kolumner som de returnerar. Det finns tre typer:
- Tabell: Returnerar flera rader och flera kolumner.
- Flera värden: Returnerar flera rader och en enda kolumn.
- Skalär: Returnerar en enskild rad och en enda kolumn.
Frågor i Azure Cosmos DB för NoSQL returnerar alltid en enda kolumn (antingen ett enkelt värde eller ett komplext objekt). Därför gäller endast underfrågor med flera värden och skalär. Du kan endast använda en underfråga med flera värden i FROM
-satsen som ett relationsuttryck. Du kan använda en skalär underfråga som ett skalärt uttryck i -instruktionen SELECT
eller WHERE
som ett relationsuttryck i FROM
-satsen.
Underfrågor med flera värden
Underfrågor med flera värden returnerar en uppsättning objekt och används alltid i FROM
-satsen. De används för:
JOIN
Optimera (självkoppling) uttryck.- Utvärdera dyra uttryck en gång och referera till flera gånger.
Optimera självkopplingsuttryck
Underfrågor med flera värden kan optimera JOIN
uttryck genom att push-överföra predikat efter varje select-many-uttryck i stället för efter alla korskopplingar i WHERE
-satsen.
Överväg följande fråga:
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
För den här frågan matchar indexet alla objekt som har en tagg med antingen name
"vinter" eller "fall", minst en quantity
mellan noll och tio och minst ett lager där backstock
är false
. Uttrycket JOIN
här utför korsprodukten av alla objekt tags
i , onHandQuantities
och warehouseStock
matriser för varje matchande objekt innan något filter tillämpas.
WHERE
Satsen tillämpar sedan filterpredikatet på varje <c, t, n, s>
tupplar. Om ett matchande objekt till exempel hade tio objekt i var och en av de tre matriserna expanderas det till 1 x 10 x 10 x 10
(dvs . 1 000) tupplar. Om du använder underfrågor här kan du filtrera bort kopplade matrisobjekt innan du ansluter till nästa uttryck.
Den här frågan motsvarar den föregående, men använder underfrågor:
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)
Anta att endast ett objekt i taggarnas matris matchar filtret och att det finns fem objekt för både kvantitets- och lagermatriser. Uttrycken JOIN
expanderar sedan till 1 x 1 x 5 x 5
(25) objekt, till skillnad från 1 000 objekt i den första frågan.
Utvärdera en gång och referens många gånger
Underfrågor kan hjälpa till att optimera frågor med dyra uttryck som användardefinierade funktioner (UDF), komplexa strängar eller aritmetiska uttryck. Du kan använda en underfråga tillsammans med ett JOIN
uttryck för att utvärdera uttrycket en gång men referera till det många gånger.
Anta att du har definierat följande UDF (getTotalWithTax
).
function getTotalWithTax(subTotal){
return subTotal * 1.25;
}
Följande fråga kör UDF getTotalWithTax
flera gånger:
SELECT VALUE {
subtotal: p.price,
total: udf.getTotalWithTax(p.price)
}
FROM
products p
WHERE
udf.getTotalWithTax(p.price) < 22.25
Här är en motsvarande fråga som bara kör UDF en gång:
SELECT VALUE {
subtotal: p.price,
total: totalPrice
}
FROM
products p
JOIN
(SELECT VALUE udf.getTotalWithTax(p.price)) totalPrice
WHERE
totalPrice < 22.25
Dricks
Tänk på uttryckens beteende JOIN
mellan produkter. Om UDF-uttrycket kan utvärderas till bör du se till undefined
att JOIN
uttrycket alltid skapar en enskild rad genom att returnera ett objekt från underfrågan i stället för värdet direkt.
Efterlikna koppling med externa referensdata
Du kan ofta behöva referera till statiska data som sällan ändras, till exempel måttenheter. Det är idealiskt att inte duplicera statiska data för varje objekt i en fråga. Om du undviker den här dupliceringen sparas lagring och skrivprestanda förbättras genom att den enskilda objektstorleken hålls mindre. Du kan använda en underfråga för att efterlikna inre kopplingssemantik med en samling statiska referensdata.
Tänk till exempel på den här uppsättningen mått:
Namn | Multiplikator | Basenhet | |
---|---|---|---|
ng |
Nanogram | 1.00E-09 |
Gram |
µg |
Mikrogram | 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 |
Följande fråga efterliknar sammanfogning med dessa data så att du lägger till namnet på enheten i utdata:
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ära underfrågor
Ett skalärt underfrågasuttryck är en underfråga som utvärderas till ett enda värde. Värdet för det skalära underquery-uttrycket är värdet för underfrågans projektion (SELECT
-sats). Du kan använda ett skalärt underfrågasuttryck på många platser där ett skalärt uttryck är giltigt. Du kan till exempel använda en skalär underfråga i valfritt uttryck i både SELECT
- och-satserna WHERE
.
Att använda en skalär underfråga hjälper inte alltid till att optimera frågan. Att till exempel skicka en skalär underfråga som ett argument till antingen ett system eller användardefinierade funktioner ger ingen fördel när det gäller att minska förbrukningen av resursenheter (RU) eller svarstiden.
Skalära underfrågor kan klassificeras ytterligare som:
- Skalära underfrågor med enkla uttryck
- Aggregera skalära underfrågor
Skalära underfrågor med enkla uttryck
En skalär underfråga med enkla uttryck är en korrelerad underfråga som har en SELECT
sats som inte innehåller några aggregerade uttryck. Dessa underfrågor ger inga optimeringsfördelar eftersom kompilatorn konverterar dem till ett större enkelt uttryck. Det finns ingen korrelerad kontext mellan de inre och yttre frågorna.
Som ett första exempel bör du överväga den här triviala frågan.
SELECT
1 AS a,
2 AS b
Du kan skriva om den här frågan med hjälp av en skalär underfråga med enkla uttryck.
SELECT
(SELECT VALUE 1) AS a,
(SELECT VALUE 2) AS b
Båda frågorna genererar samma utdata.
[
{
"a": 1,
"b": 2
}
]
I nästa exempelfråga sammanfogas den unika identifieraren med ett prefix som en skalär underfråga med enkla uttryck.
SELECT
(SELECT VALUE Concat('ID-', p.id)) AS internalId
FROM
products p
I det här exemplet används en skalär underfråga med enkla uttryck för att endast returnera relevanta fält för varje objekt. Frågan matar ut något för varje objekt, men den innehåller bara det projicerade fältet om det uppfyller filtret i underfrågan.
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"
}
]
Aggregera skalära underfrågor
En aggregerad skalär underfråga är en underfråga som har en aggregeringsfunktion i projektionen eller filtret som utvärderas till ett enda värde.
Som ett första exempel bör du överväga ett objekt med följande fält.
{
"name": "Snow coat",
"inventory": [
{
"location": "Redmond, WA",
"quantity": 50
},
{
"location": "Seattle, WA",
"quantity": 30
},
{
"location": "Washington, DC",
"quantity": 25
}
]
}
Här är en underfråga med ett enda mängdfunktionsuttryck i projektionen. Den här frågan räknar alla taggar för varje objekt.
SELECT
p.name,
(SELECT VALUE COUNT(1) FROM i IN p.inventory) AS locationCount
FROM
products p
[
{
"name": "Snow coat",
"locationCount": 3
}
]
Här är samma underfråga med ett 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
}
]
Här är en annan underfråga med flera mängdfunktionsuttryck:
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
}
}
]
Slutligen är här en fråga med en aggregerad underfråga i både projektionen och filtret:
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
}
]
Ett mer optimalt sätt att skriva den här frågan är att ansluta till underfrågan och referera till underfrågans alias i både SELECT- och WHERE-satserna. Den här frågan är effektivare eftersom du bara behöver köra underfrågan i kopplingsinstruktionen och inte i både projektionen och filtret.
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-uttryck
Azure Cosmos DB för NoSQL:s frågemotor stöder EXISTS
uttryck. Det här uttrycket är en aggregerad skalär underfråga som är inbyggd i Azure Cosmos DB för NoSQL. EXISTS
tar ett underfrågasuttryck och returnerar true
om underfrågan returnerar några rader. Annars returneras false
.
Eftersom frågemotorn inte skiljer mellan booleska uttryck och andra skalära uttryck kan du använda EXISTS
i både SELECT
- och WHERE
-satser. Det här beteendet skiljer sig från T-SQL, där ett booleskt uttryck begränsas till endast filter.
EXISTS
Om underfrågan returnerar ett enda värde som är undefined
, EXISTS
utvärderas till false. Tänk till exempel på följande fråga som inte returnerar något.
SELECT VALUE
undefined
Om du använder EXISTS
uttrycket och föregående fråga som en underfråga returnerar false
uttrycket .
SELECT
EXISTS (SELECT VALUE undefined)
[
{
"$1": false
}
]
Om nyckelordet VALUE i föregående underfråga utelämnas utvärderas underfrågan till en matris med ett enda tomt objekt.
SELECT
undefined
[
{}
]
EXISTS
Då utvärderas uttrycket till true
eftersom objektet ({}
) tekniskt avslutas.
SELECT
EXISTS (SELECT undefined)
[
{
"$1": true
}
]
Ett vanligt användningsfall ARRAY_CONTAINS
är att filtrera ett objekt efter förekomsten av ett objekt i en matris. I det här fallet kontrollerar vi om matrisen tags
innehåller ett objekt med namnet "outerwear".
SELECT
p.name,
p.tags
FROM
products p
WHERE
ARRAY_CONTAINS(p.tags, "outerwear")
Samma fråga kan användas EXISTS
som ett alternativt alternativ.
SELECT
p.name,
p.tags
FROM
products p
WHERE
EXISTS (SELECT VALUE t FROM t IN p.tags WHERE t = "outerwear")
Kan dessutom ARRAY_CONTAINS
bara kontrollera om ett värde är lika med alla element i en matris. Om du behöver mer komplexa filter för matrisegenskaper använder du JOIN
i stället.
Överväg det här exempelobjektet i en uppsättning med flera objekt som var och en innehåller en accessories
matris.
{
"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
}
]
}
Tänk dig nu följande fråga som filtrerar baserat på type
egenskaperna och quantityOnHand
i matrisen i varje objekt.
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"
}
]
För varje objekt i samlingen utförs en korsprodukt med dess matriselement. Den här JOIN
åtgärden gör det möjligt att filtrera efter egenskaper i matrisen. Den här frågans RU-förbrukning är dock betydande. Om till exempel 1 000 objekt hade 100 objekt i varje matris expanderas det till 1,000 x 100
(dvs . 100 000) tupplar.
Att använda EXISTS
kan hjälpa till att undvika denna dyra korsprodukt. I nästa exempel filtrerar frågan på matriselement i EXISTS
underfrågan. Om ett matriselement matchar filtret projicerar du det och EXISTS
utvärderar det till sant.
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"
]
Frågor kan också alias EXISTS
och referera till aliaset i projektionen:
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-uttryck
Du kan använda ARRAY
uttrycket för att projicera resultatet av en fråga som en matris. Du kan bara använda det här uttrycket i SELECT
frågans sats.
I de här exemplen antar vi att det finns en container med minst det här objektet.
{
"name": "Radimer mountain bike",
"tags": [
{
"name": "road"
},
{
"name": "bike"
},
{
"name": "competitive"
}
]
}
I det första exemplet används uttrycket i SELECT
-satsen.
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"
]
}
]
Precis som med andra underfrågor är filter med ARRAY
uttrycket möjliga.
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"
]
}
]
Matrisuttryck kan också komma efter FROM
-satsen i underfrågor.
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"
}
]