2012-01-04 14 views
13

Capisco come utilizzare la clausola WITH per le query ricorsive (!!), ma sto riscontrando dei problemi a comprenderne l'uso generale/la potenza.Guida all'utilizzo della clausola WITH in SQL

Per esempio, la seguente query aggiorna un record il cui ID è determinato utilizzando una subquery ritorno l'id del primo record da timestamp:

update global.prospect psp 
set status=status||'*' 
where psp.psp_id=(
      select p2.psp_id 
      from global.prospect p2 
      where p2.status='new' or p2.status='reset' 
      order by p2.request_ts 
      limit 1) 
returning psp.*; 

questo sarebbe un buon candidato per l'utilizzo di un WITH involucro, invece di la sub-query relativamente brutta? Se è così, perché?

+0

Secondo la documentazione, l'uso di 'WITH [RECURSIVE]' sopra 'INSERT' e' UPDATE' è stato aggiunto in PostgreSQL 9.1. –

+0

@JoeyAdams - usando con dml - un altro livello della cipolla per capire –

risposta

18

Se non ci può essere concorrente l'accesso in scrittura ai tabelle coinvolte, ci sono condizioni di gara nei seguenti query sopra. Considerate:


Il vostro esempio può utilizzare un CTE (espressione di tabella comune), ma vi darà nulla una subquery non poteva fare:

WITH x AS (
    SELECT psp_id 
    FROM global.prospect 
    WHERE status IN ('new', 'reset') 
    ORDER BY request_ts 
    LIMIT 1 
    ) 
UPDATE global.prospect psp 
SET status = status || '*' 
FROM x 
WHERE psp.psp_id = x.psp_id 
RETURNING psp.*; 

BTW, la riga restituita sarà la versione aggiornata.


Se si voleva inserire la riga restituita in un'altra tabella, è lì che una clausola WITH diventa essenziale:

WITH x AS (
    SELECT psp_id 
    FROM global.prospect 
    WHERE status IN ('new', 'reset') 
    ORDER BY request_ts 
    LIMIT 1 
    ), y AS (
    UPDATE global.prospect psp 
    SET status = status || '*' 
    FROM x 
    WHERE psp.psp_id = x.psp_id 
    RETURNING psp.* 
    ) 
INSERT INTO z 
SELECT * 
FROM y 

dati che modificano le query utilizzando CTE sono possibili con PostgreSQL 9.1 o versioni successive.
Leggi more in the excellent manual.

+0

wow - davvero buono - grazie. sono d'accordo con la qualità di pg doc, ma fino ad oggi le CTE sono state una di quelle cose che suonano interessanti quando ne ho letto, ma in pratica non ne ho mai avuto la possibilità. i tuoi due esempi (penso!) hanno aiutato molto –

9

WITH consente di definire "tabelle temporanee" per l'utilizzo in una query SELECT. Ad esempio, recentemente ho scritto una query come questa, per calcolare i cambiamenti tra i due gruppi:

-- Let o be the set of old things, and n be the set of new things. 
WITH o AS (SELECT * FROM things(OLD)), 
    n AS (SELECT * FROM things(NEW)) 

-- Select both the set of things whose value changed, 
-- and the set of things in the old set but not in the new set. 
SELECT o.key, n.value 
    FROM o 
    LEFT JOIN n ON o.key = n.key 
    WHERE o.value IS DISTINCT FROM n.value 

UNION ALL 

-- Select the set of things in the new set but not in the old set. 
SELECT n.key, n.value 
    FROM o 
    RIGHT JOIN n ON o.key = n.key 
    WHERE o.key IS NULL; 

Definendo i "tavoli" o e n in alto, ho potuto evitare di ripetere le espressioni things(OLD) e things(NEW).

Certo, potremmo probabilmente eliminare il UNION ALL utilizzando un FULL JOIN, ma non ero in grado di farlo nel mio caso particolare.


Se ho capito bene la tua ricerca, lo fa:

  • Trova la più antica fila global.prospect il cui stato è 'nuovo' o 'reset'.

  • Mark con l'aggiunta di un asterisco per il suo status di

  • ritorno della fila (compreso il nostro tweak per status).

Non credo che WITH semplificherà nulla nel tuo caso. Può essere un po 'più elegante di utilizzare una clausola FROM, però:

update global.prospect psp 
set status = status || '*' 
from (select psp_id 
     from global.prospect 
     where status = 'new' or status = 'reset' 
     order by request_ts 
     limit 1 
     ) p2 
where psp.psp_id = p2.psp_id 
returning psp.*; 

testato. Fammi sapere se funziona.

E 'più o meno esattamente quello che hai già, ad eccezione di:

  • Questo può essere facilmente esteso per aggiornare più righe. Nella tua versione, che utilizza un'espressione sottoquery, la query fallirebbe se la sottoquery venisse modificata per generare più righe.

  • Non ho indicato il valore global.prospect nella sottoquery, quindi è un po 'più facile da leggere. Poiché utilizza una clausola FROM, verrà visualizzato un errore se si fa accidentalmente riferimento alla tabella che si sta aggiornando.

  • Nella versione, l'espressione di sottoquery viene rilevata per ogni singolo elemento. Sebbene PostgreSQL debba ottimizzare questo aspetto e valutare l'espressione solo una volta, questa ottimizzazione scompare se si fa riferimento accidentalmente a una colonna in psp o si aggiunge un'espressione volatile.

+0

Per aggiornare più righe, non ci sarebbe bisogno di alcuna forma di subquery o CTE, solo: 'UPDATE global.prospect SET status = status || '*' WHERE stato IN ('nuovo', 'reset') RESTITUZIONE *; ' –