Subconsultas en Azure Cosmos DB for NoSQL
SE APLICA A: NoSQL
Una subconsulta es una consulta anidada dentro de otra consulta dentro de Azure Cosmos DB for NoSQL. Una subconsulta también se denomina consulta interna o SELECT
interna. La instrucción que contiene una subconsulta normalmente se llama consulta externa.
Tipos de subconsultas
Existen dos tipos principales de subconsultas:
- Correlacionado: una subconsulta que hace referencia a valores de la consulta externa. La subconsulta se evalúa una vez para cada fila que procesa la consulta externa.
- Sin correlacionar: una subconsulta que es independiente de la consulta externa. Se puede ejecutar por sí misma sin depender de la consulta externa.
Nota:
Azure Cosmos DB admite solo subconsultas correlacionadas.
Las subconsultas se pueden clasificar aún más en función del número de filas y columnas que devuelven. Existen tres tipos:
- Tabla: devuelve varias filas y varias columnas.
- Valores múltiples: devuelve varias filas y una sola columna.
- Escalar: devuelve una sola fila y una sola columna.
Las consultas en Azure Cosmos DB for NoSQL siempre devuelven una única columna (ya sea un valor simple o un elemento complejo). Por lo tanto, solo las subconsultas escalares y multivalor se aplican. Puede usar una subconsulta multivalor solo en la cláusula FROM
como expresión relacional. Puede usar una subconsulta escalar como expresión escalar en la cláusula SELECT
o WHERE
, o como expresión relacional en la cláusula FROM
.
Subconsultas multivalor
Las subconsultas multivalor devuelven un conjunto de elementos y siempre se usan dentro de la cláusula FROM
. Se usan para:
- Optimización de expresiones
JOIN
(autocombinación). - Evaluar expresiones costosas una vez y hacer referencia varias veces.
Optimización de expresiones de autocombinación
Las subconsultas multivalor pueden optimizar las expresiones JOIN
mediante la inserción de predicados después de cada expresión select-many, en lugar de hacerlo después de todas las combinaciones cruzadas en la cláusula WHERE
.
Considere la siguiente consulta:
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, el índice coincide con cualquier elemento que tenga una etiqueta con un name
de "winter" (invierno) o "fall" (otoño), al menos un quantity
entre zero (cero) y ten (diez), y al menos un almacén donde backstock
es false
. La expresión JOIN
aquí realiza el producto cruzado de todos los elementos de las matrices tags
, onHandQuantities
y warehouseStock
para cada elemento coincidente antes de aplicar cualquier filtro.
Luego, la cláusula WHERE
aplica el predicado de filtro en cada tupla <c, t, n, s>
. Por ejemplo, si un elemento coincidente tenía diez elementos en cada una de las tres matrices, se expande a 1 x 10 x 10 x 10
(es decir, 1 000) tuplas. Usar subconsultas aquí puede ayudar a filtrar los elementos de matrices combinadas antes de unirse a la siguiente expresión.
Esta consulta es equivalente a la anterior, pero 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)
Supongamos que solo un elemento de la matriz de etiquetas coincide con el filtro y hay cinco elementos para las matrices de cantidades y existencias. Después, las expresiones JOIN
se amplían a 1 x 1 x 5 x 5
(25) elementos, frente a los 1 000 elementos de la primera consulta.
Evaluar una vez y hacer referencia varias veces
Las subconsultas pueden ayudar a optimizar las consultas con expresiones costosas, como funciones definidas por el usuario (UDF), cadenas complejas o expresiones aritméticas. Puede usar una subconsulta junto con una expresión JOIN
para evaluar la expresión una vez, pero hacer referencia a ella varias veces.
Supongamos que tiene definida la siguiente función definida por el usuario (getTotalWithTax
).
function getTotalWithTax(subTotal){
return subTotal * 1.25;
}
La siguiente consulta ejecuta la UDF getTotalWithTax
varias veces:
SELECT VALUE {
subtotal: p.price,
total: udf.getTotalWithTax(p.price)
}
FROM
products p
WHERE
udf.getTotalWithTax(p.price) < 22.25
Esta es una consulta equivalente que ejecuta la UDF solo una vez:
SELECT VALUE {
subtotal: p.price,
total: totalPrice
}
FROM
products p
JOIN
(SELECT VALUE udf.getTotalWithTax(p.price)) totalPrice
WHERE
totalPrice < 22.25
Sugerencia
Tenga en cuenta el comportamiento del producto cruzado de las expresiones JOIN
. Si la expresión UDF se puede evaluar como undefined
, debe asegurarse de que la expresión JOIN
siempre produzca una sola fila mediante la devolución de un objeto de la subconsulta en lugar del valor directamente.
Imitar la combinación con datos de referencia externos
A menudo, es posible que deba hacer referencia a datos estáticos que no suelen cambiar, por ejemplo, unidades de medida. Es ideal no duplicar datos estáticos para cada elemento de una consulta. Al evitar esta duplicación, ahorra en almacenamiento y mejora el rendimiento de escritura, ya que mantiene un tamaño del elemento individual más pequeño. El uso de una subconsulta permite imitar la semántica de las uniones internas con una colección de datos de referencia estáticos.
Por ejemplo, considere este conjunto de medidas:
Nombre | Multiplicador | Unidad base | |
---|---|---|---|
ng |
Nanogramo | 1.00E-09 |
Gramo |
µg |
Microgramo | 1.00E-06 |
Gramo |
mg |
Miligramo | 1.00E-03 |
Gramo |
g |
Gramo | 1.00E+00 |
Gramo |
kg |
Kilogramo | 1.00E+03 |
Gramo |
Mg |
Megagramo | 1.00E+06 |
Gramo |
Gg |
Gigagramo | 1.00E+09 |
Gramo |
La siguiente consulta imita la combinación con estos datos para que agregue el nombre de la unidad a la salida:
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
Una expresión de subconsulta escalar es una subconsulta que se evalúa como valor único. El valor de la expresión de subconsulta escalar es el valor de la proyección (cláusula SELECT
) de la subconsulta. Puede usar una expresión de subconsulta escalar en muchos lugares donde sea válida una expresión escalar. Por ejemplo, puede usar una subconsulta escalar en cualquier expresión en las cláusulas SELECT
y WHERE
.
El uso de una subconsulta escalar no siempre ayuda a optimizar la consulta. Por ejemplo, pasar una subconsulta escalar como argumento a un sistema o a funciones definidas por el usuario no brinda ninguna ventaja en la reducción del consumo de recursos de unidad (RU) ni en la latencia.
Las subconsultas escalares se pueden clasificar, además, de esta manera:
- Subconsultas escalares de expresión simple
- Subconsultas escalares agregadas
Subconsultas escalares de expresión simple
Una subconsulta escalar de expresión simple es una subconsulta correlacionada que tienen una cláusula SELECT
que no contiene ninguna expresión agregada. Estas subconsultas no brindan ninguna ventaja de optimización porque el compilador las convierte en una expresión simple mayor. No existe ningún contexto correlacionado entre las consultas internas y externas.
Como primer ejemplo, considere esta consulta trivial.
SELECT
1 AS a,
2 AS b
Puede reescribir esta consulta con una subconsulta escalar de expresión simple.
SELECT
(SELECT VALUE 1) AS a,
(SELECT VALUE 2) AS b
Ambas consultas generan la misma salida.
[
{
"a": 1,
"b": 2
}
]
En este siguiente ejemplo, la consulta concatena el identificador único con un prefijo como subconsulta escalar de expresión simple.
SELECT
(SELECT VALUE Concat('ID-', p.id)) AS internalId
FROM
products p
El ejemplo usa una subconsulta escalar de expresión simple para devolver solo los campos relevantes de cada elemento. La consulta genera algo para cada elemento, pero solo incluye el campo proyectado si cumple el filtro dentro de la 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"
}
]
Subconsultas escalares agregadas
Una subconsulta escalar agregada es una subconsulta que tiene una función agregada en su proyección o filtro que se evalúa como valor único.
Como primer ejemplo, considere un elemento con los campos siguientes.
{
"name": "Snow coat",
"inventory": [
{
"location": "Redmond, WA",
"quantity": 50
},
{
"location": "Seattle, WA",
"quantity": 30
},
{
"location": "Washington, DC",
"quantity": 25
}
]
}
Esta es una subconsulta con una única expresión de función agregada en su proyección. Esta consulta cuenta todas las etiquetas de cada elemento.
SELECT
p.name,
(SELECT VALUE COUNT(1) FROM i IN p.inventory) AS locationCount
FROM
products p
[
{
"name": "Snow coat",
"locationCount": 3
}
]
Esta es la misma subconsulta 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
}
]
Esta es otra subconsulta con varias expresiones de función agregada:
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 último, esta es una consulta con una subconsulta agregada tanto en la proyección como en el 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
}
]
Una manera más eficaz de escribir esta consulta es combinar en la subconsulta y hacer referencia al alias de la subconsulta tanto en la cláusula SELECT como en la WHERE. Esta consulta es más eficaz porque tiene que ejecutar la subconsulta solo dentro de la instrucción de combinación, y no en la proyección y en el 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
Expresión EXISTS
El motor de consultas de Azure Cosmos DB for NoSQL admite expresiones EXISTS
. Esta expresión es una subconsulta escalar agregada compilada en Azure Cosmos DB for NoSQL. EXISTS
toma una expresión de subconsulta y devuelve true
si la subconsulta devuelve alguna fila. De lo contrario, devuelve false
.
Dado que el motor de consulta no distingue entre expresiones booleanas y cualquier otra expresión escalar, puede usar EXISTS
tanto en cláusulas SELECT
como WHERE
. Este comportamiento es diferente de T-SQL, donde una expresión booleana está restringida solo a filtros.
Si la subconsulta EXISTS
devuelve un único valor que es undefined
, EXISTS
se evalúa como falso. Por ejemplo, considere la siguiente consulta que no devuelve nada.
SELECT VALUE
undefined
Si usa la expresión EXISTS
y la consulta anterior como subconsulta, la expresión devuelve false
.
SELECT
EXISTS (SELECT VALUE undefined)
[
{
"$1": false
}
]
Si se omite la palabra clave VALUE en la subconsulta anterior, la subconsulta se evalúa a una matriz con un único objeto vacío.
SELECT
undefined
[
{}
]
En ese momento, la expresión EXISTS
se evalúa a true
ya que el objeto ({}
) técnicamente se cierra.
SELECT
EXISTS (SELECT undefined)
[
{
"$1": true
}
]
Un caso de uso común de ARRAY_CONTAINS
es filtrar un elemento por la existencia de un elemento en una matriz. En este caso, estamos consultando si la matriz tags
contiene un elemento llamado "outerwear" (ropa exterior).
SELECT
p.name,
p.tags
FROM
products p
WHERE
ARRAY_CONTAINS(p.tags, "outerwear")
La misma consulta puede usar EXISTS
como opción alternativa.
SELECT
p.name,
p.tags
FROM
products p
WHERE
EXISTS (SELECT VALUE t FROM t IN p.tags WHERE t = "outerwear")
Además, ARRAY_CONTAINS
solo puede comprobar si un valor es igual a cualquier elemento dentro de una matriz. Si necesita filtros más complejos en las propiedades de la matriz, use JOIN
en su lugar.
Considere este elemento de ejemplo en un conjunto con varios elementos, cada uno de los cuales contiene una 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
}
]
}
Ahora, considere la siguiente consulta que filtra basándose en las propiedades type
y quantityOnHand
de la matriz dentro de cada 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"
}
]
Para cada uno de los elementos de la colección, se realiza un producto cruzado con los elementos de la matriz. Esta operación JOIN
permite filtrar según las propiedades dentro de la matriz. Sin embargo, el consumo de RU de esta consulta es significativo. Por ejemplo, si 1 000 elementos tuvieran 100 elementos en cada matriz, se expande a 1,000 x 100
(es decir, 100 000) tuplas.
Usar EXISTS
puede ayudar a evitar este costoso producto cruzado. En el siguiente ejemplo, la consulta filtra los elementos de la matriz dentro de la subconsulta EXISTS
. Si un elemento de la matriz coincide con el filtro, lo proyecta y EXISTS
se evalúa 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"
]
Las consultas también pueden establecer alias EXISTS
y hacer referencia al alias en la proyección:
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
}
]
Expresión ARRAY
Puede usar la expresión ARRAY
para proyectar los resultados de una consulta como matriz. Puede usar esta expresión solo dentro de la cláusula SELECT
de la consulta.
En estos ejemplos, supongamos que hay un contenedor con al menos este elemento.
{
"name": "Radimer mountain bike",
"tags": [
{
"name": "road"
},
{
"name": "bike"
},
{
"name": "competitive"
}
]
}
En este primer ejemplo, la expresión se usa dentro de la 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"
]
}
]
Al igual que con las demás subconsultas, son posibles los filtros con la expresión 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"
]
}
]
Las expresiones de la matriz también pueden ir después de la cláusula FROM
en las 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"
}
]