×
Мы обрабатываем cookies, чтобы сделать наш сайт удобнее и персонализированнее для вас. Подробнее: политика использования «cookies» и «политики конфиденциальности».

Для самостоятельной настройки ознакомьтесь с инструкцией

Дополнительные настройки cookies в браузерах

Файлы cookie автоматически загружаются в ваш браузер при посещении веб-сайта. У вас есть возможность управлять этими файлами. Если Вы не согласны с использованием файлов cookies, запретите их сохранение на своём устройстве, удалите уже имеющиеся файлы cookies через настройки браузера или прекратите использование сайта.

При отключении обработки cookie наш сайт продолжит функционировать, однако будут использоваться исключительно необходимые технические файлы, без которых работа ресурса невозможна.

Инструкция по отключению cookies
Принять
Настроить
Отклонить

ДОКУМЕНТАЦИЯ

Выберите версию, форк и язык для СУБД Digital Q.DataBase, чтобы прочитать или скачать всю документацию.
Техподдержка
Документация
Диасофт
Авторские права © 2016–2025 ООО "Диасофт Экосистема"
Скачать всю документацию:

2.4.8. Запросы WITH (общие табличные выражения)

2.4.8.1. SELECT в WITH
2.4.8.2. Рекурсивные запросы
2.4.8.3. Материализация обобщенных табличных выражений
2.4.8.4. Инструкции по изменению данных в WITH

WITH позволяет создавать вспомогательные инструкции для использования в основном запросе. Эти инструкции часто называют общими табличными выражениями или CTEможно рассматривать как определение временных таблиц, существующих только в рамках одного запроса. Каждая вспомогательная инструкция в WITH предложении может быть SELECT, INSERT, UPDATE, DELETE, или MERGE; а само WITH предложение присоединяется к основной инструкции, которая также может быть SELECT, INSERT, UPDATE, DELETE, или MERGE.

2.4.8.1. SELECT в WITH #

Основное назначение SELECT в WITH заключается в том, чтобы разбить сложные запросы на более простые части. Пример:

WITH regional_sales AS (
    SELECT region, SUM(amount) AS total_sales
    FROM orders
    GROUP BY region
), top_regions AS (
    SELECT region
    FROM regional_sales
    WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT region,
       product,
       SUM(quantity) AS product_units,
       SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;

которая отображает итоги продаж по каждому продукту только в регионах с наибольшими объемами продаж. Инструкция WITH предложение определяет две вспомогательные инструкции с именами regional_sales и top_regions, где результат regional_sales используется в top_regions а результат top_regions используется в основном SELECT запросе. Этот пример можно было бы написать без использования WITH, но тогда потребовалось бы два уровня вложенных под-SELECTзапросов. В таком виде логику отследить несколько проще.

2.4.8.2. Рекурсивные запросы #

Необязательные RECURSIVE модификатор превращает WITH из простого синтаксического удобства в функционал, позволяющий решать задачи, которые иначе невозможно реализовать в стандартном SQL. Используя RECURSIVE, WITH запрос может обращаться к своему собственному результату. Простой пример такого запроса для вычисления суммы целых чисел от 1 до 100:

WITH RECURSIVE t(n) AS (
    VALUES (1)
  UNION ALL
    SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;

Общая форма рекурсивного WITH запроса всегда представляет собой нерекурсивный терм, затем UNION (или UNION ALL), а затем рекурсивный терм, где только рекурсивный терм может содержать ссылку на собственный результат запроса. Такой запрос выполняется следующим образом:

Вычисление рекурсивного запроса

  1. Вычислите нерекурсивный терм. Для UNION (но не для UNION ALL), исключите дублирующиеся строки. Включите все оставшиеся строки в результат рекурсивного запроса, а также поместите их во временную рабочую таблицу.

  2. Пока рабочая таблица не пуста, повторяйте следующие шаги:

    1. Вычислите рекурсивный терм, подставляя текущее содержимое рабочей таблицы вместо рекурсивной ссылки на саму себя. Для UNION (но не UNION ALL), исключите дублирующиеся строки и строки, которые дублируют любую из предыдущих строк результата. Включите все остальные строки в результат рекурсивного запроса и также поместите их во временную промежуточную таблицу.

    2. Замените содержимое рабочей таблицы содержимым промежуточной таблицы, а затем очистите промежуточную таблицу.

Примечание

Хотя RECURSIVE позволяет определять запросы рекурсивно, внутри такие запросы выполняются итеративно.

В приведенном выше примере рабочая таблица на каждом шаге содержит всего одну строку и принимает значения от 1 до 100 на последующих шагах. На 100-м шаге вывод отсутствует из-за WHERE предложения, и поэтому выполнение запроса завершается.

Рекурсивные запросы обычно используются для работы с иерархическими данными или данными со структурой дерево. Полезным примером является данный запрос для поиска всех прямых и косвенных подкомпонентов изделия при наличии только таблицы, содержащей сведения о непосредственных вложениях:

WITH RECURSIVE included_parts(sub_part, part, quantity) AS (
    SELECT sub_part, part, quantity FROM parts WHERE part = 'our_product'
  UNION ALL
    SELECT p.sub_part, p.part, p.quantity * pr.quantity
    FROM included_parts pr, parts p
    WHERE p.part = pr.sub_part
)
SELECT sub_part, SUM(quantity) as total_quantity
FROM included_parts
GROUP BY sub_part

2.4.8.2.2. Обнаружение циклов #

При работе с рекурсивными запросами важно быть уверенным в том, что рекурсивная часть запроса в конечном итоге не вернет ни одного кортежа, иначе команда будет зацикливаться бесконечно. Иногда использование UNION вместо UNION ALL позволяет добиться этого путем исключения строк, дублирующих предыдущие выходные строки. Однако зачастую цикл не ограничивается полностью дублирующимися выходными строками: для определения того, была ли достигнута та же точка ранее, может потребоваться проверка только одного или нескольких полей. Стандартный метод обработки таких ситуаций заключается в формировании массива уже посещенных значений. Например, рассмотрим еще раз следующий запрос, выполняющий поиск в таблице граф с использованием ссылка поля:

WITH RECURSIVE search_graph(id, link, data, depth) AS (
    SELECT g.id, g.link, g.data, 0
    FROM graph g
  UNION ALL
    SELECT g.id, g.link, g.data, sg.depth + 1
    FROM graph g, search_graph sg
    WHERE g.id = sg.link
)
SELECT * FROM search_graph;

Данный запрос зациклится, если ссылка связи содержат циклы. Поскольку требуется получить «depth» вывод, простая замена UNION ALL на UNION не устранит зацикливание. Вместо этого необходимо определить, встретилась ли та же строка снова при прохождении по определённому пути ссылок. Мы добавим два столбца is_cycle и путь в запрос, подверженный зацикливанию:

WITH RECURSIVE search_graph(id, link, data, depth, is_cycle, path) AS (
    SELECT g.id, g.link, g.data, 0,
      false,
      ARRAY[g.id]
    из предложения FROM graph g
  UNION ALL
    команда SELECT g.id, g.link, g.data, sg.depth + 1,
      g.id = ANY(path),
      path || g.id
    FROM graph g, search_graph sg
    WHERE g.id = sg.link AND NOT is_cycle
)
команда SELECT * FROM search_graph;

Помимо предотвращения циклов, само значение массива часто бывает полезно и само по себе для представления «путь» пути, пройденного для достижения каждой конкретной строки.

В общем случае, когда для распознавания цикла необходимо проверить несколько полей, используйте массив строк. Например, если бы нам потребовалось сравнить поля f1 и f2:

WITH RECURSIVE search_graph(id, link, data, depth, is_cycle, path) AS (
    SELECT g.id, g.link, g.data, 0,
      false,
      ARRAY[ROW(g.f1, g.f2)]
    из предложения FROM graph g
  UNION ALL
    команда SELECT g.id, g.link, g.data, sg.depth + 1,
      ROW(g.f1, g.f2) = ANY(path),
      path || ROW(g.f1, g.f2)
    FROM graph g, search_graph sg
    WHERE g.id = sg.link AND NOT is_cycle
)
команда SELECT * FROM search_graph;

Подсказка

Опустите ROW() синтаксис в обычном случае, когда для распознавания цикла достаточно проверить только одно поле. Это позволяет использовать обычный массив вместо массива составного типа, что повышает эффективность.

Для упрощения обнаружения циклов предусмотрен встроенный синтаксис. Приведенный выше запрос также можно написать следующим образом:

WITH RECURSIVE search_graph(id, link, data, depth) AS (
    SELECT g.id, g.link, g.data, 1
    FROM graph g
  UNION ALL
    SELECT g.id, g.link, g.data, sg.depth + 1
    FROM graph g, search_graph sg
    WHERE g.id = sg.link
) CYCLE id SET is_cycle USING path
SELECT * FROM search_graph;

и он будет автоматически преобразован во внутреннее представление, приведенное выше. Предложение CYCLE предложение сначала определяет список столбцов, которые необходимо отслеживать для обнаружения циклов, затем имя столбца, содержащего признак обнаружения цикла, и, наконец, имя еще одного столбца, в котором будет сохраняться путь. Столбцы cycle и path будут неявно добавлены в выходные строки обобщенного табличного выражения (CTE).

Подсказка

Столбец пути цикла вычисляется так же, как и столбец упорядочивания при поиске в глубину, описанный в предыдущем разделе. Запрос может содержать одновременно и SEARCH и CYCLE предложение, но совместное использование спецификации поиска в глубину и спецификации обнаружения циклов приведет к избыточным вычислениям, поэтому эффективнее использовать только CYCLE предложение и выполнять сортировку по столбцу path. Если же требуется упорядочивание при поиске в ширину, то указание обоих предложений SEARCH и CYCLE может быть полезным.

Полезный прием для тестирования запросов, когда вы не уверены, не зациклятся ли они, заключается в добавлении LIMIT в основной запрос. Например, выполнение этого запроса приведет к бесконечному циклу без LIMIT:

WITH RECURSIVE t(n) AS (
    SELECT 1
  UNION ALL
    SELECT n+1 FROM t
)
SELECT n FROM t LIMIT 100;

Это работает, потому что Digital Q.DataBaseреализация вычисляет ровно столько строк WITH запроса, сколько фактически извлекается родительским запросом. Использование этого приема в эксплуатационных средах не рекомендуется, поскольку другие системы могут работать иначе. Кроме того, это обычно не работает, если во внешнем запросе выполняется сортировка результатов рекурсивного запроса или их соединение с какой-либо другой таблицей, так как в этих случаях внешний запрос обычно пытается извлечь все WITH результаты запроса в любом случае.

2.4.8.3. Материализация обобщенных табличных выражений #

Полезным свойством WITH запросов является то, что они обычно вычисляются только один раз за время выполнения родительского запроса, даже если на них несколько раз ссылается родительский запрос или соседние WITH запросов. Таким образом, ресурсоемкие вычисления, необходимые в нескольких местах, можно поместить в WITH запрос во избежание дублирования работы. Еще одним возможным применением является предотвращение нежелательных многократных вычислений функций с побочными эффектами. Однако обратной стороной является то, что оптимизатор не может перенести ограничения из родительского запроса в многократно используемое WITH запрос, так как это может повлиять на все случаи использования WITH результатов запроса, когда это должно затрагивать только один из них. Запрос с многократными ссылками WITH будет выполнен в исходном виде, без исключения строк, которые основной запрос мог бы отбросить впоследствии. (Однако, как упоминалось выше, выполнение может быть прекращено раньше, если ссылки на запрос требуют лишь ограниченное число строк.)

Тем не менее, если WITH запрос является нерекурсивным и не имеющим побочных эффектов (то есть это SELECT не содержащий изменчивых функций), то он может быть встроен в основной запрос, что позволяет выполнять совместную оптимизацию обоих уровней запроса. По умолчанию это происходит, если основной запрос ссылается на WITH запрос всего один раз, но не в том случае, если он ссылается на WITH запрос более одного раза. Вы можете переопределить это решение, указав MATERIALIZED для принудительного отдельного вычисления этого WITH запроса или указав NOT MATERIALIZED чтобы принудительно объединить его с родительским запросом. Последний вариант сопряжен с риском дублирования вычислений WITH запроса, но он все же может обеспечить чистую экономию, если при каждом использовании WITH запросу требуется лишь небольшая часть WITH полного набора данных запроса.

Простым примером этих правил является

WITH w AS (
    SELECT * FROM big_table
)
SELECT * FROM w WHERE key = 123;

Данный WITH запрос будет преобразован, формируя тот же план выполнения, что и

SELECT * FROM big_table WHERE key = 123;

В частности, если имеется индекс по столбцу key, он, скорее всего, будет использован для получения только тех строк, где key = 123. С другой стороны, в команде

WITH w AS (
    SELECT * FROM big_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref
WHERE w2.key = 123;

WITH запрос будет материализован с созданием временной копии big_table которая затем соединяется сама с собой — без использования каких-либо индексов. Этот запрос будет выполнен гораздо эффективнее, если написать его так:

WITH w AS NOT MATERIALIZED (
    SELECT * FROM big_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref
WHERE w2.key = 123;

чтобы ограничения основного запроса могли быть применены непосредственно к сканированию big_table.

Пример, в котором использование NOT MATERIALIZED может быть нежелательным:

WITH w AS (
    SELECT key, very_expensive_function(val) as f FROM some_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.f = w2.f;

В данном случае материализация WITH запроса гарантирует, что very_expensive_function вычисляется только один раз для каждой строки таблицы, а не дважды.

В примерах выше показано только использование WITH совместно с SELECT, но его можно аналогичным образом присоединить и к INSERT, UPDATE, DELETE, или MERGE. В каждом из этих случаев фактически создаются временные таблицы, к которым можно обращаться в основной команде.

2.4.8.4. Инструкции по изменению данных в WITH #

Вы можете использовать инструкции, изменяющие данные (INSERT, UPDATE, DELETE, или MERGE) в WITH. Это позволяет выполнять несколько различных операций в рамках одного запроса. Пример:

WITH moved_rows AS (
    DELETE FROM products
    WHERE
        "date" >= '2010-10-01' AND
        "date" < '2010-11-01'
    RETURNING *
)
INSERT INTO products_log
SELECT * FROM moved_rows;

Данный запрос фактически перемещает строки из таблицы products в products_log. Инструкция DELETE в WITH удаляет указанные строки из products, возвращая их содержимое посредством своего RETURNING предложении; и затем основной запрос считывает этот результат и вставляет его в products_log.

Тонкий момент приведенного выше примера заключается в том, что WITH предложение присоединяется к INSERT, а не к под-SELECT внутри команды INSERT. Это необходимо, так как команды изменения данных разрешены только в WITH предложениях, которые присоединены к инструкции верхнего уровня. Однако применяются обычные WITH правила видимости, поэтому можно ссылаться на WITH результат команды из под-SELECT.

Команды изменения данных в WITH обычно содержат RETURNING предложения (см. Раздел 2.3.4), как показано в примере выше. Именно вывод предложения RETURNING предложение, не целевая таблица команды изменения данных, которая формирует временную таблицу, на которую может ссылаться остальная часть запроса. Если в команде изменения данных в WITH отсутствует RETURNING предложение, то оно не формирует временную таблицу и на него нельзя ссылаться в остальной части запроса. Такая инструкция всё равно будет выполнена. Пример, не имеющий особой практической пользы:

WITH t AS (
    DELETE FROM foo
)
DELETE FROM bar;

В данном примере будут удалены все строки из таблиц foo и bar. Число затронутых строк, сообщаемое клиенту, будет включать только строки, удалённые из bar.

Рекурсивные ссылки на самих себя в инструкциях изменения данных не допускаются. В некоторых случаях это ограничение можно обойти, обратившись к результату рекурсивной WITH, например:

WITH RECURSIVE included_parts(sub_part, part) AS (
    SELECT sub_part, part FROM parts WHERE part = 'our_product'
  UNION ALL
    SELECT p.sub_part, p.part
    FROM included_parts pr, parts p
    WHERE p.part = pr.sub_part
)
DELETE FROM parts
  WHERE part IN (SELECT part FROM included_parts);

Данный запрос удалит все прямые и косвенные составные части продукта.

Команды изменения данных в WITH выполняются ровно один раз и всегда до завершения, независимо от того, считывает ли основной запрос все их выходные данные (или не считывает их вовсе). Обратите внимание, что это отличается от правила для SELECT в WITH: как было сказано в предыдущем разделе, выполнение SELECT осуществляется лишь в той степени, в которой основной запрос требует их вывода.

Вложенные инструкции в WITH выполняются параллельно друг с другом и с основным запросом. Таким образом, при использовании изменяющих данные инструкций в WITH, порядок фактического выполнения указанных обновлений является непредсказуемым. Все инструкции выполняются в рамках одного и того же snapshot (см. Глава 2.10), поэтому они не могут «видеть» результаты изменений, внесенных друг другом в целевые таблицы. Это сглаживает последствия непредсказуемости фактического порядка обновления строк и означает, что RETURNING передача данных является единственным способом сообщения об изменениях между различными WITH вложенными инструкциями и основным запросом. В качестве примера можно привести ситуацию, когда в

WITH t AS (
    UPDATE products SET price = price * 1.05
    RETURNING *
)
SELECT * FROM products;

внешняя команда SELECT вернет исходные цены, существовавшие до выполнения действия UPDATE, тогда как в

WITH t AS (
    UPDATE products SET price = price * 1.05
    RETURNING *
)
SELECT * FROM t;

внешняя команда SELECT будут возвращены обновленные данные.

Попытка дважды обновить одну и ту же строку в рамках одной инструкции не поддерживается. Применяется только одно из изменений, при этом надежно предсказать, какое именно, затруднительно (а иногда и невозможно). Это также справедливо для удаления строки, которая уже была обновлена в той же инструкции: будет выполнено только обновление. Поэтому в целом следует избегать попыток дважды изменить одну и ту же строку в рамках одной инструкции. В частности, следует избегать написания WITH вложенных инструкций, которые могут затронуть те же строки, измененные основной инструкцией или параллельной вложенной инструкцией. Результаты такой инструкции будут непредсказуемыми.

В настоящее время любая таблица, используемая в качестве целевой для инструкции изменения данных в WITH не должна иметь ни условного правила, ни ALSO правила, ни INSTEAD правила, которое разворачивается в несколько инструкций.

Наверх
свяжитесь
с нами
контакты
Для прямой связи с нами вы можете использовать контакты ниже, либо оставить заявку через форму обратной связи, и мы обязательно свяжемся с вами

*поля обязательные к заполнению