Ricerca…


Sintassi

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

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

Osservazioni

Documentazione ufficiale: clausola WITH

Un'espressione tabella comune è un set di risultati temporaneo e può essere il risultato di una subquery complessa. È definito utilizzando la clausola WITH. CTE migliora la leggibilità e viene creato in memoria anziché nel database TempDB in cui viene creata la variabile Tabella temporanea e Tabella.

Concetti chiave delle espressioni di tabella comuni:

  • Può essere utilizzato per suddividere query complesse, in particolare join e sottoquery complessi.
  • È un modo per incapsulare una definizione di query.
  • Persistere solo fino all'esecuzione della query successiva.
  • L'uso corretto può portare a miglioramenti in termini di qualità del codice / manutenibilità e velocità.
  • Può essere utilizzato per fare riferimento alla tabella risultante più volte nella stessa istruzione (eliminare la duplicazione in SQL).
  • Può essere un sostituto di una vista quando non è richiesto l'uso generale di una vista; cioè, non è necessario memorizzare la definizione nei metadati.
  • Verrà eseguito quando chiamato, non quando definito. Se il CTE viene utilizzato più volte in una query, verrà eseguito più volte (probabilmente con risultati diversi).

Query temporanea

Si comportano allo stesso modo delle subquery nidificate ma con una sintassi diversa.

WITH ReadyCars AS (
  SELECT *
  FROM Cars
  WHERE Status = 'READY'
)
SELECT ID, Model, TotalCost
FROM ReadyCars
ORDER BY TotalCost;
ID Modello Costo totale
1 Ford F-150 200
2 Ford F-150 230

Sintassi di subquery equivalente

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

salendo ricorsivamente su un albero

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 Numero di telefono ManagerID DepartmentID
4 Johnathon fabbro 1212121212 2 1
2 John Johnson 2468101214 1 1
1 Giacomo fabbro 1234567890 NULLO 1

generare valori

La maggior parte dei database non ha un modo nativo di generare una serie di numeri per l'uso ad-hoc; tuttavia, le espressioni di tabella comuni possono essere utilizzate con la ricorsione per emulare tale tipo di funzione.

L'esempio seguente genera un'espressione di tabella comune denominata Numbers con una colonna i che ha una riga per i numeri 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;
io
1
2
3
4
5

Questo metodo può essere utilizzato con qualsiasi intervallo di numeri, nonché con altri tipi di dati.

enumerare ricorsivamente una sottostruttura

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;
Livello ID FName LName
1 1 Giacomo fabbro
2 2 John Johnson
3 4 Johnathon fabbro
2 3 Michael Williams

Funzionalità Oracle CONNECT BY con CTE ricorsive

La funzionalità CONNECT BY di Oracle offre molte funzionalità utili e non banali che non sono integrate quando si utilizzano CTE ricorsive standard SQL. Questo esempio replica queste funzioni (con alcune aggiunte per motivi di completezza), utilizzando la sintassi di SQL Server. È molto utile per gli sviluppatori Oracle che trovano molte funzionalità mancanti nelle loro query gerarchiche su altri database, ma serve anche a mostrare cosa si può fare con una query gerarchica in generale.

  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 caratteristiche illustrate sopra, con spiegazioni:

  • clausole
    • CONNECT BY: specifica la relazione che definisce la gerarchia.
    • INIZIA CON: specifica i nodi radice.
    • ORDINA I SEGNI DI: Ordini risultati correttamente.
  • parametri
    • NOCYCLE: interrompe l'elaborazione di un ramo quando viene rilevato un loop. Le gerarchie valide sono i grafici aciclici diretti e i riferimenti circolari violano questo costrutto.
  • operatori
    • PRIOR: ottiene i dati dal genitore del nodo.
    • CONNECT_BY_ROOT: ottiene i dati dalla radice del nodo.
  • pseudocolonne
    • LIVELLO: indica la distanza del nodo dalla sua radice.
    • CONNECT_BY_ISLEAF: indica un nodo senza figli.
    • CONNECT_BY_ISCYCLE: indica un nodo con un riferimento circolare.
  • funzioni
    • SYS_CONNECT_BY_PATH: restituisce una rappresentazione appiattita / concatenata del percorso del nodo dalla sua radice.

Generare in modo ricorsivo date, estese per includere la generazione di squadre come esempio

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

Risultato

Cioè per la settimana 1 TeamA è su R & R, TeamB su Day Shift e TeamC su Night Shift.

inserisci la descrizione dell'immagine qui

Rifattorizzare una query per utilizzare le espressioni di tabella comuni

Supponiamo di voler ottenere tutte le categorie di prodotti con vendite totali superiori a 20.

Ecco una query senza Common Table Expressions:

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

E una query equivalente utilizzando Common Table Expressions:

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

Esempio di un SQL complesso con Common Table Expression

Supponiamo di voler interrogare i "prodotti più economici" dalle "categorie principali".

Ecco un esempio di query utilizzando Common Table Expressions

-- 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow