Поделиться через


Вложенные запросы в 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 запрос возвращает одно значение, undefinedEXISTS значение равно 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"
  }
]