Subconsultas no Azure Cosmos DB for NoSQL
APLICA-SE A: NoSQL
Uma subconsulta é uma consulta aninhada em outra consulta no Azure Cosmos DB for NoSQL. Uma subconsulta também é chamada de consulta interna ou interna SELECT
. A instrução que contém uma subconsulta normalmente é chamada de consulta externa.
Tipos de subconsultas
Há dois principais tipos de subconsultas:
- Correlacionado: uma subconsulta que faz referência a valores da consulta externa. A subconsulta é avaliada uma vez para cada linha que a consulta externa processa.
- Não correlacionado: uma subconsulta que é independente da consulta externa. Ele pode ser executado sozinho sem depender da consulta externa.
Observação
Azure Cosmos DB dá suporte apenas a Subconsultas correlacionadas.
As subconsultas podem ser classificadas com base no número de linhas e colunas que retornam. Há três tipos:
- Tabela: retorna várias linhas e várias colunas.
- Vários valores: retorna várias linhas e uma única coluna.
- Escalar: retorna uma única linha e uma única coluna.
As consultas no Azure Cosmos DB for NoSQL sempre retornam uma única coluna (um valor simples ou um item complexo). Portanto, somente subconsultas de valores múltiplos e escalares são aplicáveis. Você pode usar uma subconsulta de vários valores somente na cláusula FROM
como uma expressão relacional. Você pode usar uma subconsulta escalar como uma expressão escalar na cláusula SELECT
ou WHERE
, ou como uma expressão relacional na cláusula FROM
.
Subconsultas de vários valores
Subconsultas de vários valores retornam um conjunto de itens e são sempre usadas dentro da cláusula FROM
. Elas são utilizadas para:
- Otimizando expressões
JOIN
(autojunção). - Avaliar expressões caras uma vez e fazer referência várias vezes.
Otimizar expressões de autojunção
Subconsultas de vários valores podem otimizar expressões JOIN
enviando predicados por push após cada expressão select-many em vez de todas as junções cross-joins na cláusula WHERE
.
Considere a consulta a seguir:
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 essa consulta, o índice corresponde a qualquer item que tenha uma marca com um name
de "winter" ou "fall", pelo menos um quantity
entre zero e dez e pelo menos um warehouse em que o backstock
é false
. A expressão JOIN
aqui executará o produto cruzado de todos os itens de matrizes de tags
, onHandQuantities
e warehouseStock
para cada item correspondente antes de qualquer filtro ser aplicado.
A cláusula WHERE
aplicará o predicado de filtro em cada tupla de <c, t, n, s>
. Por exemplo, se o item correspondente tem dez itens em cada uma das três matrizes, ele expande para tuplas 1 x 10 x 10 x 10
(ou seja, 1.000. O uso de subconsultas aqui pode ajudar a filtrar itens de matriz unidos antes de se juntar à próxima expressão.
Essa consulta é equivalente à anterior, mas usa 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 tags corresponda ao filtro e que haja cinco itens para as matrizes de quantidade e estoque. As expressões JOIN
se expandem para itens 1 x 1 x 5 x 5
(25), em oposição a 1.000 itens na primeira consulta.
Avaliar uma vez e fazer referência várias vezes
As subconsultas podem ajudar a otimizar consultas com expressões caras, como UDFs (funções definidas pelo usuário), cadeias de caracteres complexas ou expressões aritméticas. Você pode usar uma subconsulta juntamente com uma expressão JOIN
para avaliar a expressão uma vez, mas fazer referência a ela muitas vezes.
Vamos supor que você tenha o UDF (getTotalWithTax
) a seguir definido.
function getTotalWithTax(subTotal){
return subTotal * 1.25;
}
A consulta a seguir 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
Aqui está uma consulta equivalente que executa o UDF somente 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 mente o comportamento entre produtos de expressões JOIN
. Se a expressão UDF puder ser avaliada como undefined
, você deve garantir que a expressão JOIN
sempre produza uma única linha retornando um objeto da subconsulta em vez do valor diretamente.
Imitar junção com dados de referência externa
Geralmente, talvez seja necessário fazer referência a dados estáticos que raramente mudam, como unidades de medida. É ideal não duplicar dados estáticos para cada item em uma consulta. Evitar essa duplicação economiza armazenamento e melhorará o desempenho de gravação mantendo o tamanho do item individual menor. Você pode usar uma subconsulta para imitar semântica de junção interna com uma coleção de dados de referência estático.
Por exemplo, considere esse 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 |
Gigagrama | 1.00E+09 |
Grama |
A consulta a seguir imita a junção com esses dados para que você 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 (cláusula SELECT
) da subconsulta. Você pode usar uma expressão de subconsulta escalar em muitos locais em que uma expressão escalar é válida. Por exemplo, você pode usar uma subconsulta escalar em qualquer expressão nas cláusulas SELECT
e WHERE
.
O uso de uma subconsulta escalar nem sempre ajuda a otimizar sua consulta. Por exemplo, passar uma subconsulta escalar como um argumento para um sistema ou funções definidas pelo usuário não fornece nenhum benefício em reduzir o consumo ou na latência de RU (unidade de recurso).
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 cláusula SELECT
que não contém nenhuma expressão de agregação. Essas subconsultas não fornecem benefícios de otimização porque o compilador as converte em uma expressão simples maior. Não há nenhum contexto correlacionado entre as consultas internas e externas.
Como primeiro exemplo, considere essa consulta trivial.
SELECT
1 AS a,
2 AS b
Você pode reescrever essa consulta usando uma subconsulta escalar de expressão simples.
SELECT
(SELECT VALUE 1) AS a,
(SELECT VALUE 2) AS b
Ambas as consultas produzem a mesma saída.
[
{
"a": 1,
"b": 2
}
]
O próximo exemplo de consulta 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
Esse exemplo usa uma subconsulta escalar de expressão simples para retornar apenas os campos relevantes para cada item. A consulta gera algo para cada item, mas só inclui o campo projetado se ele atender ao filtro dentro da subconsulta.
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"
}
]
Agregar subconsultas escalares
Uma subconsulta escalar agregada é uma subconsulta que tem uma função de agregação em sua projeção ou filtro que é avaliada como um único valor.
Como primeiro exemplo, considere um item com os campos a seguir.
{
"name": "Snow coat",
"inventory": [
{
"location": "Redmond, WA",
"quantity": 50
},
{
"location": "Seattle, WA",
"quantity": 30
},
{
"location": "Washington, DC",
"quantity": 25
}
]
}
Aqui está uma subconsulta com uma única expressão de função de agregação em sua projeção. Essa consulta conta todas as marcas 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
}
]
Aqui está 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
}
]
Aqui está outra subconsulta com várias 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
}
}
]
Finalmente aqui está 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 maneira mais ideal de escrever essa consulta é ingressar na subconsulta e fazer referência ao alias de subconsulta nas cláusulas SELECT e WHERE. Essa consulta é mais eficiente porque você precisa executar a subconsulta somente dentro da instrução de junçã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 mecanismo de consulta do Azure Cosmos DB for NoSQL dá suporte a expressões EXISTS
. Essa expressão é uma subconsulta escalar agregada incorporada ao Azure Cosmos DB for NoSQL. EXISTS
usa uma expressão de subconsulta e retorna true
se a subconsulta retornar qualquer linha. Caso contrário, ele retornará false
.
Como o mecanismo de consulta não diferencia as expressões boolianas e quaisquer outras expressões escalares, você pode usar EXISTS
nas cláusulas SELECT
e WHERE
. Esse comportamento é diferente do T-SQL, em que uma expressão booliana é restrita apenas a filtros.
A subconsulta EXISTS
retorna um único valor que undefined
, EXISTS
avalia como false. Por exemplo, considere a consulta a seguir que não retorna nada.
SELECT VALUE
undefined
Se você usar a expressão EXISTS
e a consulta anterior como uma subconsulta, a expressão retornará false
.
SELECT
EXISTS (SELECT VALUE undefined)
[
{
"$1": false
}
]
Se o VALUE da palavra-chave na subconsulta anterior for omitido, a subconsulta será avaliada como uma matriz com um único objeto vazio.
SELECT
undefined
[
{}
]
Nesse ponto, a expressão EXISTS
é avaliada true
como desde que o objeto ({}
) sai tecnicamente.
SELECT
EXISTS (SELECT undefined)
[
{
"$1": true
}
]
Um caso de uso comum de ARRAY_CONTAINS
é filtrar um documento pela existência de um item em uma matriz. Nesse caso, estamos verificando se a matriz de tags
contém um item chamado "outerwear."
SELECT
p.name,
p.tags
FROM
products p
WHERE
ARRAY_CONTAINS(p.tags, "outerwear")
A mesma consulta pode usar 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 você precisar de filtros mais complexos em Propriedades de matriz, use JOIN
.
Considere esse item de exemplo em um conjunto com vários itens cada um contendo uma matriz accessories
.
{
"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 propriedades type
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, um produto cruzado é executado com seus elementos de matriz. Essa operação JOIN
torna possível filtrar as propriedades dentro da matriz. No entanto, o consumo de RU dessa consulta é significativo. Por exemplo, se os documentos 1.000 tivessem 100 itens em cada matriz, eles serão expandidos para tuplas 1,000 x 100
(ou seja, 100.000).
O uso de EXISTS
pode ajudar a evitar este caro produto cruzado. Neste próximo exemplo, a consulta filtra os elementos de matriz dentro da subconsulta EXISTS
. Se um elemento de matriz corresponder ao filtro, você o projetará e EXISTS
avalia como 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"
]
As consultas também podem criar o 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 ARRAY
Você pode usar a expressão ARRAY
para projetar os resultados de uma consulta como uma matriz. Você pode usar essa expressão somente dentro da cláusula SELECT
da consulta.
Para esses exemplos, vamos supor que haja um contêiner com pelo menos esse item.
{
"name": "Radimer mountain bike",
"tags": [
{
"name": "road"
},
{
"name": "bike"
},
{
"name": "competitive"
}
]
}
Nesse primeiro exemplo, a expressão é usada dentro da cláusula SELECT
.
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"
]
}
]
Assim como ocorre com outras subconsultas, são possíveis filtros com a expressão 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"
]
}
]
As expressões de matriz também podem vir após a cláusula FROM
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"
}
]