Subconsultas no Azure Cosmos DB para NoSQL

APLICA-SE A: NoSQL

Uma subconsulta é uma consulta aninhada noutra consulta no Azure Cosmos DB para NoSQL. Uma subconsulta também é denominada consulta interna ou interna SELECT. Normalmente, a instrução que contém uma subconsulta é denominada consulta externa.

Tipos de subconsultas

Existem dois tipos principais de subconsultas:

  • Correlacionado: uma subconsulta que referencia valores da consulta externa. A subconsulta é avaliada uma vez para cada linha que a consulta externa processa.
  • Não correlacionado: uma subconsulta independente da consulta externa. Pode ser executado por si só sem depender da consulta externa.

Nota

O Azure Cosmos DB suporta apenas subconsultas correlacionadas.

As subconsultas podem ser classificadas com base no número de linhas e colunas que devolvem. Existem três tipos:

  • Tabela: devolve várias linhas e múltiplas colunas.
  • Valor múltiplo: devolve várias linhas e uma única coluna.
  • Escalar: devolve uma única linha e uma única coluna.

As consultas no Azure Cosmos DB para NoSQL devolvem sempre uma única coluna (um valor simples ou um item complexo). Por conseguinte, apenas são aplicáveis subconsultas de múltiplos valores e escalares. Pode utilizar uma subconsulta de múltiplos valores apenas na FROM cláusula como uma expressão relacional. Pode utilizar uma subconsulta escalar como uma expressão escalar na SELECT cláusula ou WHERE ou como uma expressão relacional na FROM cláusula.

Subconsultas de valores múltiplos

As subconsultas de valores múltiplos devolvem um conjunto de itens e são sempre utilizadas dentro da FROM cláusula. São utilizados para:

  • Otimizar expressões (associação JOIN autónoma).
  • Avaliar expressões dispendiosas uma vez e referenciar várias vezes.

Otimizar expressões de associação autónoma

As subconsultas de valores múltiplos podem otimizar JOIN expressões ao emitir predicados após cada expressão select-many em vez de depois de todas as associações cruzadas na WHERE cláusula.

Considere a consulta seguinte:

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

Para esta consulta, o índice corresponde a qualquer item que tenha uma etiqueta com um name de "inverno" ou "queda", pelo menos um quantity entre zero e dez e, pelo menos, um armazém em que o backstock é false. A JOIN expressão aqui executa o produto cruzado de todos os itens de tags, onHandQuantitiese warehouseStock matrizes para cada item correspondente antes de qualquer filtro ser aplicado.

Em WHERE seguida, a cláusula aplica o predicado de filtro em cada <c, t, n, s> cadeia de identificação. Por exemplo, se um item correspondente tiver dez itens em cada uma das três matrizes, este expande para 1 x 10 x 10 x 10 (ou seja, 1000) cadeias de identificação. A utilização de subconsultas aqui pode ajudar a filtrar itens de matriz associados antes de se associar à expressão seguinte.

Esta consulta é equivalente à anterior, mas utiliza subconsultas:

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)

Suponha que apenas um item na matriz de etiquetas corresponde ao filtro e que existem cinco itens para matrizes de nutrientes e porções. Em JOIN seguida, as expressões expandem para 1 x 1 x 5 x 5(25) itens, em oposição a 1000 itens na primeira consulta.

Avaliar uma vez e referenciar muitas vezes

As subconsultas podem ajudar a otimizar as consultas com expressões dispendiosas, como funções definidas pelo utilizador (UDFs), cadeias complexas ou expressões aritméticas. Pode utilizar uma subconsulta juntamente com uma JOIN expressão para avaliar a expressão uma vez, mas referencia-a muitas vezes.

Vamos supor que tem o seguinte UDF (getTotalWithTax) definido.

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

A seguinte consulta executa o UDF getTotalWithTax várias vezes:

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

Eis uma consulta equivalente que executa o UDF apenas uma vez:

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

Dica

Tenha em atenção o comportamento entre produtos das JOIN expressões. Se a expressão UDF puder ser avaliada como undefined, deve garantir que a JOIN expressão produz sempre uma única linha ao devolver um objeto da subconsulta em vez do valor diretamente.

Imitar a associação com dados de referência externa

Muitas vezes, poderá ter de referenciar dados estáticos que raramente mudam, como unidades de medição. É ideal não duplicar dados estáticos para cada item numa consulta. Evitar esta duplicação poupa no armazenamento e melhora o desempenho de escrita ao manter o tamanho do item individual mais pequeno. Pode utilizar uma subconsulta para imitar a semântica de associação interna com uma coleção de dados de referência estáticos.

Por exemplo, considere este conjunto de medidas:

Nome Multiplicador Unidade base
ng Nanograma 1.00E-09 Grama
µg Micrograma 1.00E-06 Grama
mg Miligrama 1.00E-03 Grama
g Grama 1.00E+00 Grama
kg Quilograma 1.00E+03 Grama
Mg Megagrama 1.00E+06 Grama
Gg Gigagram 1.00E+09 Grama

A seguinte consulta imita a associação a estes dados para que adicione o nome da unidade à saída:

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

Subconsultas escalares

Uma expressão de subconsulta escalar é uma subconsulta que é avaliada como um único valor. O valor da expressão de subconsulta escalar é o valor da projeção (SELECT cláusula) da subconsulta. Pode utilizar uma expressão de subconsulta escalar em vários locais onde uma expressão escalar é válida. Por exemplo, pode utilizar uma subconsulta escalar em qualquer expressão nas SELECT cláusulas e WHERE .

A utilização de uma subconsulta escalar nem sempre ajuda a otimizar a consulta. Por exemplo, transmitir uma subconsulta escalar como um argumento para um sistema ou funções definidas pelo utilizador não oferece qualquer benefício na redução do consumo ou latência de unidades de recursos (RU).

As subconsultas escalares podem ser classificadas como:

  • Subconsultas escalares de expressão simples
  • Agregar subconsultas escalares

Subconsultas escalares de expressão simples

Uma subconsulta escalar de expressão simples é uma subconsulta correlacionada que tem uma SELECT cláusula que não contém expressões agregadas. Estas subconsultas não proporcionam benefícios de otimização porque o compilador as converte numa expressão simples maior. Não existe nenhum contexto correlacionado entre as consultas internas e externas.

Como primeiro exemplo, considere esta consulta trivial.

SELECT
    1 AS a,
    2 AS b

Pode reescrever esta consulta com uma subconsulta escalar de expressão simples.

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

Ambas as consultas produzem o mesmo resultado.

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

Esta consulta de exemplo seguinte concatena o identificador exclusivo com um prefixo como uma subconsulta escalar de expressão simples.

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

Este exemplo utiliza uma subconsulta escalar de expressão simples para devolver apenas os campos relevantes para cada item. A consulta produz algo para cada item, mas só inclui o campo projetado se cumprir o filtro na subconsulta.

SELECT
    p.id,
    (SELECT p.name WHERE CONTAINS(p.name, "glove")).name
FROM
    products p
[
  {
    "id": "03230",
    "name": "Winter glove"
  },
  {
    "id": "03238"
  },
  {
    "id": "03229"
  }
]

Agregar subconsultas escalares

Uma subconsulta escalar agregada é uma subconsulta que tem uma função de agregação na sua projeção ou filtro que é avaliada como um único valor.

Como primeiro exemplo, considere um item com os seguintes campos.

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

Segue-se uma subconsulta com uma única expressão de função de agregação na projeção. Esta consulta conta todas as etiquetas para cada item.

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

Eis a mesma subconsulta com um 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
  }
]

Eis outra subconsulta com múltiplas expressões de função de agregação:

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

Por fim, eis uma consulta com uma subconsulta agregada na projeção e no 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
  }
]

Uma forma mais ideal de escrever esta consulta é associar-se à subconsulta e referenciar o alias de subconsulta nas cláusulas SELECT e WHERE. Esta consulta é mais eficiente porque só precisa de executar a subconsulta na instrução de associação e não na projeção e no 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

Expressão EXISTS

O motor de consulta do Azure Cosmos DB para NoSQL suporta EXISTS expressões. Esta expressão é uma subconsulta escalar agregada incorporada no Azure Cosmos DB para NoSQL. EXISTS utiliza uma expressão de subconsulta e devolve true se a subconsulta devolver linhas. Caso contrário, devolve false.

Uma vez que o motor de consulta não diferencia entre expressões booleanas e quaisquer outras expressões escalares, pode utilizar EXISTS em ambas as SELECT cláusulas e WHERE . Este comportamento é diferente do T-SQL, em que uma expressão booleana está restrita a apenas filtros.

Se a EXISTS subconsulta devolver um único valor, undefinedé avaliado EXISTS como falso. Por exemplo, considere a seguinte consulta que não devolve nada.

SELECT VALUE
    undefined

Se utilizar a EXISTS expressão e a consulta anterior como uma subconsulta, a expressão devolve false.

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

Se a palavra-chave VALOR na subconsulta anterior for omitida, a subconsulta é avaliada como uma matriz com um único objeto vazio.

SELECT
    undefined
[
  {}
]

Nessa altura, a EXISTS expressão é avaliada como uma true vez que o objeto ({}) sai tecnicamente.

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

Um caso de utilização comum de ARRAY_CONTAINS é filtrar um item pela existência de um item numa matriz. Neste caso, estamos a verificar se a tags matriz contém um item chamado "outerwear".

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

A mesma consulta pode ser utilizada EXISTS como uma opção alternativa.

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

Além disso, ARRAY_CONTAINS só pode verificar se um valor é igual a qualquer elemento dentro de uma matriz. Se precisar de filtros mais complexos nas propriedades da matriz, utilize JOIN em alternativa.

Considere este item de exemplo num conjunto com vários itens que contêm uma accessories matriz.

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

Agora, considere a seguinte consulta que filtra com base nas type propriedades e quantityOnHand na matriz dentro de cada 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"
  }
]

Para cada um dos itens na coleção, é efetuado um produto cruzado com os respetivos elementos de matriz. Esta JOIN operação permite filtrar as propriedades na matriz. No entanto, o consumo de RU desta consulta é significativo. Por exemplo, se 1000 itens tinham 100 itens em cada matriz, expande-se para 1,000 x 100 (ou seja, 100 000) cadeias de identificação.

A utilização EXISTS pode ajudar a evitar este produto cruzado dispendioso. Neste exemplo seguinte, a consulta filtra os elementos de matriz na subconsulta EXISTS . Se um elemento de matriz corresponder ao filtro, projetará-o e EXISTS avaliará como verdadeiro.

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

As consultas também podem alias EXISTS e referenciar o alias na projeção:

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

Expressão MATRIZ

Pode utilizar a ARRAY expressão para projetar os resultados de uma consulta como uma matriz. Só pode utilizar esta expressão na SELECT cláusula da consulta.

Para estes exemplos, vamos supor que existe um contentor com, pelo menos, este item.

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

Neste primeiro exemplo, a expressão é utilizada dentro da SELECT cláusula.

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

Tal como acontece com outras subconsultas, os filtros com a ARRAY expressão são possíveis.

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

As expressões de matriz também podem ser apresentadas após a FROM cláusula em subconsultas.

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