INSERTO semplice
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
L'uso di un LEFT [OUTER] JOINinvece di [INNER] JOINsignifica che le righe da val non vengono eliminate quando non viene trovata alcuna corrispondenza foo. Invece, NULLè inserito per foo_id.
L' VALUESespressione nella sottoquery fa lo stesso del CTE di @ ypercube . Le espressioni di tabella comuni offrono funzionalità aggiuntive e sono più facili da leggere in query di grandi dimensioni, ma rappresentano anche barriere di ottimizzazione. Quindi le subquery sono in genere un po 'più veloci quando nessuna delle precedenti è necessaria.
idcome nome della colonna è un anti-pattern diffuso. Dovrebbe essere foo_ide bar_ido niente descrittivo. Quando si unisce un gruppo di tabelle, si finisce con più colonne tutte denominate id...
Considerare semplice texto varcharinvece di varchar(n). Se devi davvero imporre una limitazione di lunghezza, aggiungi un CHECKvincolo:
Potrebbe essere necessario aggiungere cast di tipo esplicito. Poiché l' VALUESespressione non è direttamente collegata a una tabella (come in INSERT ... VALUES ...), i tipi non possono essere derivati e i tipi di dati predefiniti vengono utilizzati senza una dichiarazione di tipo esplicita, che potrebbe non funzionare in tutti i casi. Basta farlo nella prima fila, il resto andrà in linea.
INSERISCI le righe FK mancanti contemporaneamente
Se si desidera creare voci inesistenti fooal volo, in una singola istruzione SQL , i CTE sono strumentali:
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
Nota le due nuove righe fittizie da inserire. Entrambi sono viola , che non esiste fooancora. Due righe per illustrare la necessità DISTINCTnella prima INSERTistruzione.
Spiegazione dettagliata
Il 1 ° CTE selfornisce file multiple di dati di input. La sottoquery valcon l' VALUESespressione può essere sostituita con una tabella o una sottoquery come origine. Immediatamente LEFT JOINper fooaggiungere le righe foo_idpreesistenti type. Tutte le altre righe arrivano in foo_id IS NULLquesto modo.
Il 2 ° CTE insinserisce nuovi tipi distinti ( foo_id IS NULL) in foo, e restituisce i nuovi generati foo_id, insieme a typeper unire nuovamente per inserire le righe.
L'esterno finale INSERTora può inserire un foo.id per ogni riga: o il tipo preesistente o è stato inserito nel passaggio 2.
A rigor di termini, entrambi gli inserti si verificano "in parallelo", ma poiché si tratta di una singola istruzione, i FOREIGN KEYvincoli predefiniti non si lamentano. L'integrità referenziale viene applicata alla fine dell'istruzione per impostazione predefinita.
SQL Fiddle for Postgres 9.3. (Funziona allo stesso modo in 9.1.)
C'è una minuscola condizione di competizione se si eseguono più di queste query contemporaneamente. Leggi di più sotto le domande correlate qui e qui e qui . Succede davvero solo con un carico simultaneo pesante, se mai. In confronto a caching soluzioni come pubblicizzato in un'altra risposta, la probabilità è super-piccola .
Funzione per uso ripetuto
Per un uso ripetuto, creerei una funzione SQL che accetta un array di record come parametro e lo usa unnest(param)al posto VALUESdell'espressione.
In alternativa, se la sintassi per le matrici di record è troppo complicata per te, utilizzare una stringa separata da virgola come parametro _param. Ad esempio del modulo:
'description1,type1;description2,type2;description3,type3'
Quindi utilizzare questo per sostituire l' VALUESespressione nell'istruzione precedente:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Funzione con UPSERT in Postgres 9.5
Crea un tipo di riga personalizzato per il passaggio dei parametri. Potremmo farne a meno, ma è più semplice:
CREATE TYPE foobar AS (description text, type text);
Funzione:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
Chiamata:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
Veloce e solido per ambienti con transazioni simultanee.
Oltre alle query sopra, questo ...
... si applica SELECTo INSERTsu foo: typeviene inserito qualsiasi elemento che non esiste nella tabella FK. Supponendo che la maggior parte dei tipi preesistano. Per essere assolutamente sicuri ed escludere le condizioni di gara, le file esistenti di cui abbiamo bisogno sono bloccate (in modo che le transazioni simultanee non possano interferire). Se è troppo paranoico per il tuo caso, puoi sostituire:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
con
ON CONFLICT(type) DO NOTHING
... si applica INSERTo UPDATE(vero "UPSERT") su bar: Se descriptionesiste già, typeviene aggiornato:
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
Ma solo se typeeffettivamente cambia:
... passa valori anche a tipi di riga noti con un VARIADICparametro. Nota il massimo predefinito di 100 parametri! Confrontare:
Esistono molti altri modi per passare più righe ...
Relazionato: