Поиск…


Синтаксис

  • WITH QueryName [(ColumnName, ...)] AS (
    ВЫБРАТЬ ...
    )
    SELECT ... FROM QueryName ...;

  • С RECURSIVE QueryName [(ColumnName, ...)] AS (
    ВЫБРАТЬ ...
    СОЮЗ [ВСЕ]
    SELECT ... FROM QueryName ...
    )
    SELECT ... FROM QueryName ...;

замечания

Официальная документация: статья WITH

Выражение общей таблицы - это временный набор результатов, и это может быть результатом сложного суб-запроса. Он определяется с помощью предложения WITH. CTE улучшает читаемость и создается в памяти, а не в базе данных TempDB, где создается переменная Temp Table и Table.

Ключевые понятия общих табличных выражений:

  • Может использоваться для разбивки сложных запросов, особенно сложных объединений и подзапросов.
  • Является способом инкапсуляции определения запроса.
  • Сохранять только до тех пор, пока не будет запущен следующий запрос.
  • Правильное использование может привести к улучшению как качества кода, так и поддерживаемости и скорости.
  • Может использоваться для ссылки на результирующую таблицу несколько раз в одном выражении (исключить дублирование в SQL).
  • Может быть заменой для представления, когда общее использование представления не требуется; то есть вам не нужно сохранять определение в метаданных.
  • Будет выполняться при вызове, а не при определении. Если CTE используется несколько раз в запросе, он будет запускаться несколько раз (возможно, с разными результатами).

Временный запрос

Они ведут себя так же, как вложенные подзапросы, но с другим синтаксисом.

WITH ReadyCars AS (
  SELECT *
  FROM Cars
  WHERE Status = 'READY'
)
SELECT ID, Model, TotalCost
FROM ReadyCars
ORDER BY TotalCost;
Я БЫ модель Общая стоимость
1 Ford F-150 200
2 Ford F-150 230

Эквивалентный синтаксис подзапроса

SELECT ID, Model, TotalCost
FROM (
  SELECT *
  FROM Cars
  WHERE Status = 'READY'
) AS ReadyCars
ORDER BY TotalCost

рекурсивно поднимающийся в дерево

WITH RECURSIVE ManagersOfJonathon AS (
    -- start with this row
    SELECT *
    FROM Employees
    WHERE ID = 4

    UNION ALL

    -- get manager(s) of all previously selected rows
    SELECT Employees.*
    FROM Employees
    JOIN ManagersOfJonathon
        ON Employees.ID = ManagersOfJonathon.ManagerID
)
SELECT * FROM ManagersOfJonathon;
Я бы FName LName Номер телефона ManagerID DepartmentID
4 Джонатон кузнец 1212121212 2 1
2 Джон Джонсон 2468101214 1 1
1 Джеймс кузнец 1234567890 НОЛЬ 1

генерирующие значения

Большинство баз данных не имеют собственного способа генерации серии чисел для ad-hoc-использования; однако общие выражения таблицы могут использоваться с рекурсией для эмуляции этого типа функции.

В следующем примере генерируется общее табличное выражение, называемое Numbers с столбцом i которое имеет строку для чисел 1-5:

--Give a table name `Numbers" and a column `i` to hold the numbers
WITH Numbers(i) AS (
    --Starting number/index
    SELECT 1
    --Top-level UNION ALL operator required for recursion
    UNION ALL
    --Iteration expression:
    SELECT i + 1
    --Table expression we first declared used as source for recursion
    FROM Numbers
    --Clause to define the end of the recursion
    WHERE i < 5
)
--Use the generated table expression like a regular table
SELECT i FROM Numbers;
я
1
2
3
4
5

Этот метод можно использовать с любым интервалом числа, а также с другими типами данных.

рекурсивно перечисляя поддерево

WITH RECURSIVE ManagedByJames(Level, ID, FName, LName) AS (
    -- start with this row
    SELECT 1, ID, FName, LName
    FROM Employees
    WHERE ID = 1

    UNION ALL

    -- get employees that have any of the previously selected rows as manager
    SELECT ManagedByJames.Level + 1,
           Employees.ID,
           Employees.FName,
           Employees.LName
    FROM Employees
    JOIN ManagedByJames
        ON Employees.ManagerID = ManagedByJames.ID

    ORDER BY 1 DESC   -- depth-first search
)
SELECT * FROM ManagedByJames;
уровень Я БЫ FName LName
1 1 Джеймс кузнец
2 2 Джон Джонсон
3 4 Джонатон кузнец
2 3 Майкл Williams

Функциональность Oracle CONNECT BY с рекурсивными CTE

Функциональность CONNECT BY от Oracle обеспечивает множество полезных и нетривиальных функций, которые не встроены при использовании стандартных рекурсивных CTE SQL. Этот пример реплицирует эти функции (с несколькими дополнениями для полноты), используя синтаксис SQL Server. Для разработчиков Oracle очень полезно найти многие функции, отсутствующие в их иерархических запросах в других базах данных, но также служит для демонстрации того, что можно сделать с помощью иерархического запроса в целом.

  WITH tbl AS (
       SELECT id, name, parent_id
         FROM mytable)
     , tbl_hierarchy AS (
       /* Anchor */
       SELECT 1 AS "LEVEL"
            --, 1 AS CONNECT_BY_ISROOT
            --, 0 AS CONNECT_BY_ISBRANCH
            , CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
            , 0 AS CONNECT_BY_ISCYCLE
            , '/' + CAST(t.id   AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
            , '/' + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
            , t.id AS root_id
            , t.*
         FROM tbl t
        WHERE t.parent_id IS NULL                            -- START WITH parent_id IS NULL
       UNION ALL
       /* Recursive */
       SELECT th."LEVEL" + 1 AS "LEVEL"
            --, 0 AS CONNECT_BY_ISROOT
            --, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 1 ELSE 0 END AS CONNECT_BY_ISBRANCH
            , CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
            , CASE WHEN th.SYS_CONNECT_BY_PATH_id LIKE '%/' + CAST(t.id AS VARCHAR(MAX)) + '/%' THEN 1 ELSE 0 END AS CONNECT_BY_ISCYCLE
            , th.SYS_CONNECT_BY_PATH_id   + CAST(t.id   AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
            , th.SYS_CONNECT_BY_PATH_name + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
            , th.root_id
            , t.*
         FROM tbl t
              JOIN tbl_hierarchy th ON (th.id = t.parent_id) -- CONNECT BY PRIOR id = parent_id
        WHERE th.CONNECT_BY_ISCYCLE = 0)                     -- NOCYCLE
SELECT th.*
     --, REPLICATE(' ', (th."LEVEL" - 1) * 3) + th.name AS tbl_hierarchy
  FROM tbl_hierarchy th
       JOIN tbl CONNECT_BY_ROOT ON (CONNECT_BY_ROOT.id = th.root_id)
 ORDER BY th.SYS_CONNECT_BY_PATH_name;                       -- ORDER SIBLINGS BY name

CONNECT BY, показанные выше, с пояснениями:

  • Статьи
    • CONNECT BY: Определяет взаимосвязь, определяющую иерархию.
    • START WITH: указывает корневые узлы.
    • ORDER SIBLINGS BY: Заказывает результат правильно.
  • параметры
    • NOCYCLE: останавливает обработку ветки при обнаружении цикла. Допустимые иерархии - это ациклические графики, а круговые ссылки нарушают эту конструкцию.
  • операторы
    • ПРИОР: Получает данные от родителя узла.
    • CONNECT_BY_ROOT: Получает данные из корня узла.
  • псевдостолбцы
    • LEVEL: указывает расстояние до узла от его корня.
    • CONNECT_BY_ISLEAF: указывает узел без детей.
    • CONNECT_BY_ISCYCLE: указывает узел с круглой ссылкой.
  • функции
    • SYS_CONNECT_BY_PATH: Возвращает сжатое / конкатенированное представление пути к узлу из его корня.

Рекурсивно генерировать даты, расширенные для включения в список команд в качестве примера

DECLARE @DateFrom DATETIME = '2016-06-01 06:00'
DECLARE @DateTo DATETIME = '2016-07-01 06:00'
DECLARE @IntervalDays INT = 7

-- Transition Sequence = Rest & Relax into Day Shift into Night Shift
-- RR (Rest & Relax) = 1
-- DS (Day Shift) = 2
-- NS (Night Shift) = 3

;WITH roster AS
(
   SELECT @DateFrom AS RosterStart, 1 AS TeamA, 2 AS TeamB, 3 AS TeamC
   UNION ALL
   SELECT DATEADD(d, @IntervalDays, RosterStart),
          CASE TeamA WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamA,
          CASE TeamB WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamB,
          CASE TeamC WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamC
   FROM roster WHERE RosterStart < DATEADD(d, -@IntervalDays, @DateTo)
)

SELECT RosterStart,
       ISNULL(LEAD(RosterStart) OVER (ORDER BY RosterStart), RosterStart + @IntervalDays) AS RosterEnd,
       CASE TeamA WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamA,
       CASE TeamB WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamB,
       CASE TeamC WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamC
FROM roster

Результат

Т.е. за неделю 1 TeamA находится на R & R, TeamB находится в режиме Day Shift, а TeamC - в ночном режиме.

введите описание изображения здесь

Рефакторинг запроса для использования общих выражений таблицы

Предположим, мы хотим получить все категории продуктов с общим объемом продаж более 20.

Вот запрос без общих выражений таблицы:

SELECT category.description, sum(product.price) as total_sales
FROM sale
LEFT JOIN product on sale.product_id = product.id
LEFT JOIN category on product.category_id = category.id
GROUP BY category.id, category.description
HAVING sum(product.price) > 20

И эквивалентный запрос с использованием общих табличных выражений:

WITH all_sales AS (
  SELECT product.price, category.id as category_id, category.description as category_description
  FROM sale
  LEFT JOIN product on sale.product_id = product.id
  LEFT JOIN category on product.category_id = category.id
)
, sales_by_category AS (
  SELECT category_description, sum(price) as total_sales
  FROM all_sales
  GROUP BY category_id, category_description
)
SELECT * from sales_by_category WHERE total_sales > 20

Пример сложного SQL с общим выражением таблицы

Предположим, мы хотим запросить «самые дешевые продукты» из «лучших категорий».

Ниже приведен пример запроса с использованием общих табличных выражений

-- all_sales: just a simple SELECT with all the needed JOINS
WITH all_sales AS (
  SELECT
  product.price as product_price,
  category.id as category_id,
  category.description as category_description
  FROM sale
  LEFT JOIN product on sale.product_id = product.id
  LEFT JOIN category on product.category_id = category.id
)
-- Group by category
, sales_by_category AS (
  SELECT category_id, category_description,
  sum(product_price) as total_sales
  FROM all_sales
  GROUP BY category_id, category_description
)
-- Filtering total_sales > 20
, top_categories AS (
  SELECT * from sales_by_category WHERE total_sales > 20
)
-- all_products: just a simple SELECT with all the needed JOINS
, all_products AS (
  SELECT
  product.id as product_id,
  product.description as product_description,
  product.price as product_price,
  category.id as category_id,
  category.description as category_description
  FROM product
  LEFT JOIN category on product.category_id = category.id
)
-- Order by product price
, cheapest_products AS (
  SELECT * from all_products
  ORDER by product_price ASC
)
-- Simple inner join 
, cheapest_products_from_top_categories AS (
  SELECT product_description, product_price
  FROM cheapest_products
  INNER JOIN top_categories ON cheapest_products.category_id = top_categories.category_id
)
--The main SELECT
SELECT * from cheapest_products_from_top_categories


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow