Recherche…


Syntaxe

  • WITH QueryName [(ColumnName, ...)] AS (
    SELECT ...
    )
    SELECT ... FROM QueryName ...;

  • WITH RECURSIVE QueryName [(ColumnName, ...)] AS (
    SELECT ...
    UNION [TOUS]
    SELECT ... FROM QueryName ...
    )
    SELECT ... FROM QueryName ...;

Remarques

Documentation officielle: clause WITH

Une expression de table commune est un jeu de résultats temporaire pouvant résulter d'une requête complexe. Il est défini à l'aide de la clause WITH. CTE améliore la lisibilité et est créé dans la mémoire plutôt que dans la base de données TempDB où la variable Table temporaire et Table est créée.

Concepts clés des expressions de table communes:

  • Peut être utilisé pour casser des requêtes complexes, en particulier des jointures complexes et des sous-requêtes.
  • Est un moyen d'encapsuler une définition de requête.
  • Ne persister que jusqu'à l'exécution de la requête suivante.
  • Une utilisation correcte peut entraîner des améliorations de la qualité / maintenabilité et de la rapidité du code.
  • Peut être utilisé pour référencer la table résultante plusieurs fois dans la même instruction (élimine la duplication dans SQL).
  • Peut remplacer une vue lorsque l'utilisation générale d'une vue n'est pas requise; en d'autres termes, vous n'avez pas besoin de stocker la définition dans les métadonnées.
  • Sera exécuté lorsqu'il est appelé, pas lorsqu'il est défini. Si le CTE est utilisé plusieurs fois dans une requête, il sera exécuté plusieurs fois (éventuellement avec des résultats différents).

Requête temporaire

Celles-ci se comportent de la même manière que les sous-requêtes imbriquées mais avec une syntaxe différente.

WITH ReadyCars AS (
  SELECT *
  FROM Cars
  WHERE Status = 'READY'
)
SELECT ID, Model, TotalCost
FROM ReadyCars
ORDER BY TotalCost;
ID Modèle Coût total
1 Ford F-150 200
2 Ford F-150 230

Syntaxe de sous-requête équivalente

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

récursivement monter dans un arbre

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;
Id FName LName Numéro de téléphone ManagerId DépartementId
4 Johnathon Forgeron 1212121212 2 1
2 John Johnson 2468101214 1 1
1 James Forgeron 1234567890 NUL 1

générer des valeurs

La plupart des bases de données ne permettent pas de générer une série de nombres pour une utilisation ponctuelle. Cependant, les expressions de table communes peuvent être utilisées avec la récursivité pour émuler ce type de fonction.

L'exemple suivant génère une expression de table commune appelée Numbers avec une colonne i comportant une ligne pour les numéros 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;
je
1
2
3
4
5

Cette méthode peut être utilisée avec n'importe quel intervalle numérique, ainsi que d'autres types de données.

énumérer récursivement un sous-arbre

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;
Niveau ID FName LName
1 1 James Forgeron
2 2 John Johnson
3 4 Johnathon Forgeron
2 3 Michael Williams

Fonctionnalité Oracle CONNECT BY avec les CTE récursifs

La fonctionnalité CONNECT BY d'Oracle fournit de nombreuses fonctionnalités utiles et non triviales qui ne sont pas intégrées lors de l'utilisation de CTE récursifs standard SQL. Cet exemple réplique ces fonctionnalités (avec quelques ajouts par souci d'exhaustivité), en utilisant la syntaxe SQL Server. Il est très utile pour les développeurs Oracle de trouver de nombreuses fonctionnalités manquantes dans leurs requêtes hiérarchiques sur d'autres bases de données, mais il sert également à présenter ce qui peut être fait avec une requête hiérarchique en général.

  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

Fonctionnalités CONNECT BY démontrées ci-dessus, avec des explications:

  • Clauses
    • CONNECT BY: Spécifie la relation qui définit la hiérarchie.
    • START WITH: Spécifie les nœuds racine.
    • ORDER SIBLINGS BY: Résultats des commandes correctement.
  • Paramètres
    • NOCYCLE: Arrête le traitement d'une branche lorsqu'une boucle est détectée. Les hiérarchies valides sont des graphes acycliques dirigés et les références circulaires violent cette construction.
  • Les opérateurs
    • PRIOR: Obtient les données du parent du noeud.
    • CONNECT_BY_ROOT: Obtient les données de la racine du noeud.
  • Pseudocolonnes
    • NIVEAU: Indique la distance entre le nœud et sa racine.
    • CONNECT_BY_ISLEAF: Indique un nœud sans enfants.
    • CONNECT_BY_ISCYCLE: Indique un noeud avec une référence circulaire.
  • Les fonctions
    • SYS_CONNECT_BY_PATH: Retourne une représentation aplatie / concaténée du chemin d'accès au noeud depuis sa racine.

Génération récursive de dates, étendue pour inclure la liste des équipes comme exemple

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

Résultat

Ie Pour TeamA est sur R & R, TeamB est sur Day Shift et TeamC est sur Night Shift.

entrer la description de l'image ici

Refactoring d'une requête pour utiliser les expressions de table communes

Supposons que nous voulons obtenir toutes les catégories de produits dont les ventes totales sont supérieures à 20.

Voici une requête sans expressions de table communes:

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

Et une requête équivalente utilisant les expressions de table communes:

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

Exemple d'un SQL complexe avec une expression de table commune

Supposons que nous voulons interroger les "produits les moins chers" des "catégories supérieures".

Voici un exemple de requête utilisant les expressions de table communes

-- 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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow