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] JOIN
invece di [INNER] JOIN
significa che le righe da val
non vengono eliminate quando non viene trovata alcuna corrispondenza foo
. Invece, NULL
è inserito per foo_id
.
L' VALUES
espressione 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.
id
come nome della colonna è un anti-pattern diffuso. Dovrebbe essere foo_id
e bar_id
o niente descrittivo. Quando si unisce un gruppo di tabelle, si finisce con più colonne tutte denominate id
...
Considerare semplice text
o varchar
invece di varchar(n)
. Se devi davvero imporre una limitazione di lunghezza, aggiungi un CHECK
vincolo:
Potrebbe essere necessario aggiungere cast di tipo esplicito. Poiché l' VALUES
espressione 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 foo
al 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 foo
ancora. Due righe per illustrare la necessità DISTINCT
nella prima INSERT
istruzione.
Spiegazione dettagliata
Il 1 ° CTE sel
fornisce file multiple di dati di input. La sottoquery val
con l' VALUES
espressione può essere sostituita con una tabella o una sottoquery come origine. Immediatamente LEFT JOIN
per foo
aggiungere le righe foo_id
preesistenti type
. Tutte le altre righe arrivano in foo_id IS NULL
questo modo.
Il 2 ° CTE ins
inserisce nuovi tipi distinti ( foo_id IS NULL
) in foo
, e restituisce i nuovi generati foo_id
, insieme a type
per unire nuovamente per inserire le righe.
L'esterno finale INSERT
ora 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 KEY
vincoli 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 VALUES
dell'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' VALUES
espressione 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 SELECT
o INSERT
su foo
: type
viene 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 INSERT
o UPDATE
(vero "UPSERT") su bar
: Se description
esiste già, type
viene 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 type
effettivamente cambia:
... passa valori anche a tipi di riga noti con un VARIADIC
parametro. Nota il massimo predefinito di 100 parametri! Confrontare:
Esistono molti altri modi per passare più righe ...
Relazionato: