Вложенные запросы в Azure Cosmos DB для NoSQL
ОБЛАСТЬ ПРИМЕНЕНИЯ: NoSQL
Вложенный запрос — это запрос, вложенный в другой запрос в Azure Cosmos DB для NoSQL. Вложенный запрос также называется внутренним запросом или внутренним SELECT
запросом. Оператор, содержащий вложенный запрос, обычно называется внешним запросом.
Типы вложенных запросов
Существует два основных типа вложенных запросов:
- Коррелированный. Вложенный запрос, который ссылается на значения из внешнего запроса. Вложенный запрос вычисляется один раз для каждой строки, обрабатываемой внешним запросом.
- Некоррелированный. Вложенный запрос, который не зависит от внешнего запроса. Его можно выполнять самостоятельно, не полагаясь на внешний запрос.
Примечание.
Azure Cosmos DB поддерживает только коррелированные вложенные запросы.
Вложенные запросы можно дополнительно классифицировать на основе числа возвращаемых записей и столбцов. Существует три типа:
- Таблица. Возвращает несколько записей и несколько столбцов.
- Несколько значений. Возвращает несколько записей и один столбец.
- Скаляр. Возвращает одну запись и один столбец.
Запросы в Azure Cosmos DB для NoSQL всегда возвращают один столбец (простое значение или сложный элемент). Поэтому применимы только многозначные и скалярные вложенные запросы. Вложенный запрос с несколькими значениями можно использовать только в предложении в FROM
качестве реляционного выражения. Скалярный вложенный запрос можно использовать в качестве скалярного выражения в SELECT
предложении или WHERE
в качестве реляционного FROM
выражения.
Вложенные запросы с несколькими значениями
Вложенные запросы с несколькими значениями возвращают набор элементов и всегда используются в предложении FROM
. Они используются, когда требуется:
- Оптимизация выражений (самосоединение
JOIN
). - Однократная оценка дорогостоящих выражений и многократная ссылка на них.
Оптимизация выражений самосоединения
Вложенные запросы с несколькими значениями могут оптимизировать JOIN
выражения путем отправки предикатов после каждого выражения select-many , а не после всех перекрестных соединений в предложении WHERE
.
Обратите внимание на следующий запрос:
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
Для этого запроса индекс соответствует любому элементу, который имеет тег с name
тегом "зима" или "падение", по крайней мере один от нуля до десяти, и по крайней мере один quantity
склад, где находитсяfalse
.backstock
Выражение здесь JOIN
выполняет перекрестное произведение всех элементов и warehouseStock
onHandQuantities
массивов для каждого соответствующего tags
элемента перед применением любого фильтра.
Затем предложение WHERE
применяет предикат фильтра для каждого <c, t, n, s>
кортежа. Например, если соответствующий элемент содержит десять элементов в каждом из трех массивов, он расширяется до 1 x 10 x 10 x 10
(то есть 1000) кортежей. Использование вложенных запросов здесь может помочь отфильтровать присоединенные элементы массива перед присоединением к следующему выражению.
Этот запрос эквивалентен предыдущему, но использует вложенные запросы:
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)
Предположим, что только один элемент в массиве тегов соответствует фильтру, и есть пять элементов как для количества, так и для массивов запасов. Затем JOIN
выражения разворачиваются до 1 x 1 x 5 x 5
(25) элементов, а не 1000 элементов в первом запросе.
Однократное вычисление и многократная ссылка
Вложенные запросы могут помочь оптимизировать запросы с дорогостоящими выражениями, такими как определяемые пользователем функции (UDF), сложные строки или арифметические выражения. Вы можете использовать вложенный JOIN
запрос вместе с выражением, чтобы оценить выражение один раз, но ссылаться на него много раз.
Предположим, что у вас есть следующие определяемые UDF (getTotalWithTax
).
function getTotalWithTax(subTotal){
return subTotal * 1.25;
}
Следующий запрос выполняет UDF getTotalWithTax
несколько раз:
SELECT VALUE {
subtotal: p.price,
total: udf.getTotalWithTax(p.price)
}
FROM
products p
WHERE
udf.getTotalWithTax(p.price) < 22.25
Ниже приведен эквивалентный запрос, запускающий функцию UDF только один раз:
SELECT VALUE {
subtotal: p.price,
total: totalPrice
}
FROM
products p
JOIN
(SELECT VALUE udf.getTotalWithTax(p.price)) totalPrice
WHERE
totalPrice < 22.25
Совет
Помните о кросс-продукте поведения выражений JOIN
. Если выражение UDF может оцениваться undefined
, следует убедиться, что JOIN
выражение всегда создает одну строку, возвращая объект из подзапроса, а не значение напрямую.
Имитация соединения с внешними эталонными данными
Часто может потребоваться ссылаться на статические данные, которые редко изменяются, например единицы измерения. Идеально подходит для того, чтобы не дублировать статические данные для каждого элемента в запросе. Избегая этого дублирования, экономия на хранилище и повышение производительности записи путем уменьшения размера отдельного элемента. Вы можете использовать вложенный запрос для имитации семантики внутреннего соединения с коллекцией статических ссылочных данных.
Например, рассмотрим этот набор измерений:
Имя | Коэффициент | Базовая единица измерения | |
---|---|---|---|
ng |
Нанограмм | 1.00E-09 |
Грамм |
µg |
Микрограмм | 1.00E-06 |
Грамм |
mg |
Миллиграмм | 1.00E-03 |
Грамм |
g |
Грамм | 1.00E+00 |
Грамм |
kg |
Килограмм | 1.00E+03 |
Грамм |
Mg |
Мегаграмм | 1.00E+06 |
Грамм |
Gg |
Гигаграмм | 1.00E+09 |
Грамм |
Следующий запрос имитирует соединение с этими данными, чтобы добавить в выходные данные название единицы измерения:
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
скалярные вложенные запросы;
Скалярное выражение вложенного запроса — это вложенный запрос, результатом которого является единственное значение. Значение скалярного вложенного запроса — это значение проекции (SELECT
предложения) вложенного запроса. Можно использовать скалярное выражение вложенного запроса во многих случаях, когда допустимо использование скалярного выражения. Например, можно использовать скалярный вложенный запрос в любом выражении в обоих SELECT
предложениях и WHERE
предложениях.
Использование скалярного вложенного запроса не всегда помогает оптимизировать запрос. Например, передача скалярного вложенного запроса в качестве аргумента в системные или определяемые пользователем функции не обеспечивает преимущества снижения потребления единиц ресурсов (ЕЗ) или задержки.
Скалярные вложенные запросы можно дополнительно классифицировать следующим образом:
- Скалярные вложенные запросы в простых выражениях
- Агрегирование скалярных вложенных запросов
Скалярные вложенные запросы в простых выражениях
Скалярный запрос простого выражения — это коррелированный вложенный запрос, содержащий SELECT
предложение, которое не содержит статистических выражений. Эти вложенные запросы не дают никаких преимуществ оптимизации, поскольку компилятор преобразует их в одно более крупное простое выражение. Не просматривается корреляция контекста между внутренними и внешними запросами.
В первом примере рассмотрим этот тривиальный запрос.
SELECT
1 AS a,
2 AS b
Этот запрос можно переписать с помощью скалярного запроса простого выражения.
SELECT
(SELECT VALUE 1) AS a,
(SELECT VALUE 2) AS b
Оба запроса создают одинаковые выходные данные.
[
{
"a": 1,
"b": 2
}
]
Следующий пример запроса объединяет уникальный идентификатор с префиксом в виде скалярного запроса простого выражения.
SELECT
(SELECT VALUE Concat('ID-', p.id)) AS internalId
FROM
products p
В этом примере используется скалярный запрос простого выражения, чтобы возвращать только соответствующие поля для каждого элемента. Запрос выводит что-то для каждого элемента, но он включает только проецируемого поля, если он соответствует фильтру в подзапросе.
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"
}
]
Агрегирование скалярных вложенных запросов
Статистический скалярный вложенный запрос — это вложенный запрос, имеющий агрегатную функцию в проекции или фильтре, результатом которой является единственное значение.
В первом примере рассмотрим элемент со следующими полями.
{
"name": "Snow coat",
"inventory": [
{
"location": "Redmond, WA",
"quantity": 50
},
{
"location": "Seattle, WA",
"quantity": 30
},
{
"location": "Washington, DC",
"quantity": 25
}
]
}
Ниже приведен вложенный запрос с одним статистическим выражением функции в проекции. Этот запрос подсчитывает все теги для каждого элемента.
SELECT
p.name,
(SELECT VALUE COUNT(1) FROM i IN p.inventory) AS locationCount
FROM
products p
[
{
"name": "Snow coat",
"locationCount": 3
}
]
Ниже приведен тот же вложенный запрос с фильтром.
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
}
]
Ниже приведены другие вложенные запросы с несколькими статистическими выражениями функций:
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
}
}
]
Наконец, вот запрос со статистическим запросом как в проекции, так и в фильтре:
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
}
]
Более оптимальный способ написания этого запроса — соединение во вложенном запросе и ссылка на псевдоним вложенного запроса в предложениях SELECT и WHERE. Этот запрос более эффективен, поскольку необходимо выполнить вложенный запрос только внутри инструкции объединения, а не в проекции и фильтре.
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
Выражение EXISTS
Модуль запросов Azure Cosmos DB для NoSQL поддерживает EXISTS
выражения. Это выражение является скалярным вложенным запросом, встроенным в Azure Cosmos DB для NoSQL. EXISTS
принимает выражение вложенного запроса и возвращает true
, если вложенный запрос возвращает все строки. В противном случае возвращается значение false
.
Так как обработчик запросов не отличает логические выражения и другие скалярные выражения, их можно использовать EXISTS
как в предложениях, так и WHERE
в SELECT
предложениях. Это поведение в отличие от T-SQL, где логическое выражение ограничено только фильтрами.
Если вложенный EXISTS
запрос возвращает одно значение, undefined
EXISTS
значение равно false. Например, рассмотрим следующий запрос, возвращающий ничего.
SELECT VALUE
undefined
Если вы используете выражение и предыдущий EXISTS
запрос в качестве вложенных запросов, выражение возвращается false
.
SELECT
EXISTS (SELECT VALUE undefined)
[
{
"$1": false
}
]
Если ключевое слово VALUE в предыдущем вложенных запросах опущено, вложенный запрос вычисляет массив с одним пустым объектом.
SELECT
undefined
[
{}
]
На этом этапе EXISTS
выражение оценивается true
так, как объект ({}
) технически завершает работу.
SELECT
EXISTS (SELECT undefined)
[
{
"$1": true
}
]
Распространенным вариантом ARRAY_CONTAINS
использования является фильтрация элемента по существованию элемента в массиве. В этом случае мы проверяем, содержит ли tags
массив элемент с именем "внешней одежды".
SELECT
p.name,
p.tags
FROM
products p
WHERE
ARRAY_CONTAINS(p.tags, "outerwear")
Тот же запрос может использоваться EXISTS
в качестве альтернативного варианта.
SELECT
p.name,
p.tags
FROM
products p
WHERE
EXISTS (SELECT VALUE t FROM t IN p.tags WHERE t = "outerwear")
Кроме того, ARRAY_CONTAINS
можно проверить, равно ли значение любому элементу в массиве. Если вам нужны более сложные фильтры для свойств массива, используйте JOIN
вместо этого.
Рассмотрим этот пример элемента в наборе с несколькими элементами, содержащими 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
}
]
}
Теперь рассмотрим следующий запрос, который фильтрует на type
основе и quantityOnHand
свойств массива в каждом элементе.
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"
}
]
Для каждого элемента в коллекции кросс-продукт выполняется со своими элементами массива. Эта JOIN
операция позволяет фильтровать свойства в массиве. Однако потребление ЕЗ этого запроса имеет значительное значение. Например, если 1000 элементов имеют 100 элементов в каждом массиве, он расширяется до 1,000 x 100
(то есть 100 000) кортежей.
Использование EXISTS
может помочь избежать этого дорогого кросс-продукта. В следующем примере запрос фильтрует элементы массива в подзапросе EXISTS
. Если элемент массива соответствует фильтру, проектируйте его и EXISTS
оцениваете как 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"
]
Запросы также могут псевдонимы EXISTS
и ссылаться на псевдоним в проекции:
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
}
]
Выражение ARRAY
Выражение можно использовать ARRAY
для проецировать результаты запроса в виде массива. Это выражение можно использовать только в SELECT
предложении запроса.
В этих примерах предположим, что есть контейнер с по крайней мере этим элементом.
{
"name": "Radimer mountain bike",
"tags": [
{
"name": "road"
},
{
"name": "bike"
},
{
"name": "competitive"
}
]
}
В этом первом примере выражение используется в предложении 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"
]
}
]
Как и в других вложенных запросах, возможны фильтры с выражением 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"
]
}
]
Выражения массива также могут поступать после FROM
предложения в вложенных запросах.
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"
}
]