Sdílet prostřednictvím


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