Sottoquery in Azure Cosmos DB per NoSQL

SI APPLICA A: NoSQL

Una sottoquery è una query annidata all'interno di un'altra query all'interno di Azure Cosmos DB per NoSQL. Una sottoquery è detta anche query interna o interna SELECT. L'istruzione che contiene una sottoquery viene in genere chiamata query esterna.

Tipologie di sottoquery

Esistono due tipi principali di sottoquery:

  • Correlata: sottoquery che fa riferimento ai valori della query esterna. La sottoquery viene valutata una volta per ciascuna riga elaborata dalla query esterna.
  • Non correlata: sottoquery indipendente dalla query esterna. Può essere eseguita autonomamente senza basarsi sulla query esterna.

Nota

Azure Cosmos DB supporta solo sottoquery correlate.

Le sottoquery possono essere ulteriormente classificate in base al numero di righe e colonne restituite. Sono disponibili tre tipi:

  • Tabella: restituisce più righe e più colonne.
  • Multivalore: restituisce più righe e una singola colonna.
  • Scalare: restituisce una singola riga e una singola colonna.

Le query in Azure Cosmos DB per NoSQL restituiscono sempre una singola colonna (un valore semplice o un elemento complesso). Pertanto, sono applicabili solo sottoquery multivalore e scalare. È possibile usare una sottoquery multivalore solo nella FROM clausola come espressione relazionale. È possibile usare una sottoquery scalare come espressione scalare nella SELECT clausola o WHERE o come espressione relazionale nella FROM clausola .

Sottoquery multivalore

Le sottoquery multivalore restituiscono un set di elementi e vengono sempre usate all'interno della FROM clausola . Vengono utilizzate per:

  • Ottimizzazione delle JOIN espressioni (self-join).
  • Valutazione di espressioni costose una sola volta e referenziamento multiplo.

Ottimizzare le espressioni self-join

Le sottoquery multivalore possono ottimizzare JOIN le espressioni eseguendo il push dei predicati dopo ogni espressione select-many anziché dopo tutti i cross join nella WHERE clausola .

Si consideri la query seguente:

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

Per questa query, l'indice corrisponde a qualsiasi elemento con un tag con un name valore "winter" o "fall", almeno uno quantity compreso tra zero e dieci e almeno un magazzino in cui backstock è false. L'espressione JOIN seguente esegue il prodotto incrociato di tutti gli elementi di tags, onHandQuantitiese warehouseStock matrici per ogni elemento corrispondente prima dell'applicazione di qualsiasi filtro.

La WHERE clausola applica quindi il predicato di filtro in ogni <c, t, n, s> tupla. Ad esempio, se un elemento corrispondente ha dieci elementi in ognuna delle tre matrici, si espande in 1 x 10 x 10 x 10 ( ovvero 1.000) tuple. L'uso di sottoquery in questo caso consente di filtrare gli elementi della matrice aggiunti prima dell'unione con l'espressione successiva.

Questa query è equivalente alla precedente, ma usa le sottoquery:

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)

Si supponga che solo un elemento nella matrice di tag corrisponda al filtro e che siano presenti cinque elementi sia per la quantità che per le matrici di scorte. Le JOIN espressioni si espandono quindi a 1 x 1 x 5 x 5 (25) elementi, anziché a 1.000 elementi nella prima query.

Valutare una volta e referenziare più volte

Le sottoquery consentono di ottimizzare le query con espressioni costose, ad esempio funzioni definite dall'utente (UDF), stringhe complesse o espressioni aritmetiche. È possibile usare una sottoquery insieme a un'espressione JOIN per valutare l'espressione una sola volta, ma farvi riferimento più volte.

Si supponga di avere definito la funzione definita dall'utente (getTotalWithTax) seguente.

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

La query seguente esegue più volte la funzione definita dall'utente getTotalWithTax :

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

Ecco una query equivalente che esegue la funzione definita dall'utente una sola volta:

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

Suggerimento

Tenere presente il comportamento tra prodotti delle JOIN espressioni. Se l'espressione definita dall'utente può restituire undefined, è necessario assicurarsi che l'espressione JOIN produci sempre una singola riga restituendo un oggetto dalla sottoquery anziché direttamente dal valore.

Simulare il join con dati di riferimento esterni

Spesso potrebbe essere necessario fare riferimento a dati statici che raramente cambiano, ad esempio unità di misura. È consigliabile non duplicare i dati statici per ogni elemento in una query. Evitare questa duplicazione consente di risparmiare spazio di archiviazione e migliorare le prestazioni di scrittura mantenendo le dimensioni dei singoli elementi più piccoli. È possibile usare una sottoquery per simulare la semantica di inner join con una raccolta di dati di riferimento statici.

Si consideri, ad esempio, questo set di misurazioni:

Nome Moltiplicatore Unità di base
ng Nanogrammo 1.00E-09 Grammo
µg Microgrammo 1.00E-06 Gram
mg Milligram 1.00E-03 Grammo
g Grammo 1.00E+00 Grammo
kg Kilogram 1.00E+03 Gram
Mg Megagrammo 1.00E+06 Grammo
Gg Gigagrammo 1.00E+09 Grammo

La query seguente simula l'unione con questi dati in modo da aggiungere il nome dell'unità all'output:

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

Sottoquery scalari

Un'espressione di sottoquery scalare è una sottoquery che restituisce un singolo valore. Il valore dell'espressione della sottoquery scalare è il valore della proiezione (SELECT clausola) della sottoquery. È possibile usare un'espressione di sottoquery scalare in molte posizioni in cui un'espressione scalare è valida. Ad esempio, è possibile usare una sottoquery scalare in qualsiasi espressione nelle SELECT clausole e WHERE .

L'uso di una sottoquery scalare non consente sempre di ottimizzare la query. Ad esempio, il passaggio di una sottoquery scalare come argomento a un sistema o a funzioni definite dall'utente non offre alcun vantaggio nella riduzione del consumo o della latenza delle unità di risorse.

Le sottoquery scalari possono essere ulteriormente classificate come:

  • Sottoquery scalari con espressione semplice
  • Sottoquery scalari aggregate

Sottoquery scalari con espressione semplice

Una sottoquery scalare con espressione semplice è una sottoquery correlata con una SELECT clausola che non contiene espressioni di aggregazione. Queste sottoquery non offrono vantaggi di ottimizzazione perché il compilatore le converte in un'unica espressione semplice più grande. Non esiste alcun contesto correlato tra le query interne e quelle esterne.

Come primo esempio, si consideri questa query semplice.

SELECT
    1 AS a,
    2 AS b

È possibile riscrivere questa query usando una sottoquery scalare con espressione semplice.

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

Entrambe le query producono lo stesso output.

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

In questo esempio di query seguente viene concatenato l'identificatore univoco con un prefisso come sottoquery scalare con espressione semplice.

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

In questo esempio viene utilizzata una sottoquery scalare con espressione semplice per restituire solo i campi pertinenti per ogni elemento. La query restituisce un elemento per ogni elemento, ma include solo il campo proiettato se soddisfa il filtro all'interno della sottoquery.

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

Sottoquery scalari aggregate

Una sottoquery scalare aggregata è una sottoquery con una funzione di aggregazione nella proiezione o nel filtro che restituisce un singolo valore.

Come primo esempio, si consideri un elemento con i campi seguenti.

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

Ecco una sottoquery con una singola espressione di funzione di aggregazione nella relativa proiezione. Questa query conta tutti i tag per ogni elemento.

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

Ecco la stessa sottoquery con un filtro.

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

Ecco un'altra sottoquery con più espressioni di funzione di aggregazione:

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

Infine, ecco una query con una sottoquery di aggregazione sia nella proiezione che nel filtro:

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

Un modo migliore per scrivere questa query consiste nel creare un join nella sottoquery e fare riferimento all'alias della sottoquery nelle clausole SELECT e WHERE. Questa query è più efficiente perché è necessario eseguire la sottoquery solo all'interno dell'istruzione join e non nella proiezione e nel filtro.

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

Espressione EXISTS

Il motore di query di Azure Cosmos DB per NoSQL supporta EXISTS le espressioni. Questa espressione è una sottoquery scalare aggregata incorporata in Azure Cosmos DB per NoSQL. EXISTS accetta un'espressione di sottoquery e restituisce true se la sottoquery restituisce righe. In caso contrario, viene restituito false.

Poiché il motore di query non distingue tra espressioni booleane e altre espressioni scalari, è possibile usare EXISTS in entrambe SELECT le clausole e WHERE . Questo comportamento è diverso da T-SQL, in cui un'espressione booleana è limitata solo ai filtri.

Se la EXISTS sottoquery restituisce un singolo valore undefined, EXISTS restituisce false. Si consideri, ad esempio, la query seguente che non restituisce nulla.

SELECT VALUE
    undefined

Se si usa l'espressione EXISTS e la query precedente come sottoquery, l'espressione restituisce false.

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

Se la parola chiave VALUE nella sottoquery precedente viene omessa, la sottoquery restituisce una matrice con un singolo oggetto vuoto.

SELECT
    undefined
[
  {}
]

A questo punto, l'espressione EXISTS restituisce true poiché l'oggetto ({}) esce tecnicamente.

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

Un caso d'uso comune di ARRAY_CONTAINS consiste nel filtrare un elemento in base all'esistenza di un elemento in una matrice. In questo caso, viene verificato se la tags matrice contiene un elemento denominato "outerwear".

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

La stessa query può essere usata EXISTS come opzione alternativa.

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

Inoltre, ARRAY_CONTAINS può controllare solo se un valore è uguale a qualsiasi elemento all'interno di una matrice. Se sono necessari filtri più complessi sulle proprietà della matrice, usare JOIN invece .

Si consideri questo elemento di esempio in un set con più elementi ognuno contenente una accessories matrice.

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

Si consideri ora la query seguente che filtra in base alle type proprietà e quantityOnHand nella matrice all'interno di ogni elemento.

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

Per ogni elemento della raccolta, un prodotto incrociato viene eseguito con i relativi elementi di matrice. Questa JOIN operazione consente di filtrare le proprietà all'interno della matrice. Tuttavia, il consumo di UR di questa query è significativo. Ad esempio, se 1.000 elementi hanno 100 elementi in ogni matrice, si espande in 1,000 x 100 (ovvero 100.000) tuple.

L'uso EXISTS di può aiutare a evitare questo costoso cross-product. In questo esempio successivo la query filtra gli elementi della matrice all'interno della EXISTS sottoquery. Se un elemento di matrice corrisponde al filtro, lo si proietta e EXISTS restituisce 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"
]

Le query possono anche eseguire l'alias EXISTS e fare riferimento all'alias nella proiezione:

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

Espressione ARRAY

È possibile usare l'espressione ARRAY per proiettare i risultati di una query come matrice. È possibile usare questa espressione solo all'interno della SELECT clausola della query.

Per questi esempi, si supponga che sia presente un contenitore con almeno questo elemento.

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

In questo primo esempio, l'espressione viene usata all'interno della SELECT clausola .

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

Come per altre sottoquery, i filtri con l'espressione ARRAY sono possibili.

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

Le espressioni di matrice possono anche venire dopo la FROM clausola nelle sottoquery.

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