Einfaches INSERT
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);
Die Verwendung von a LEFT [OUTER] JOIN
anstelle von [INNER] JOIN
bedeutet, dass Zeilen von val
nicht gelöscht werden, wenn in keine Übereinstimmung gefunden wird foo
. Stattdessen NULL
wird für eingegeben foo_id
.
Der VALUES
Ausdruck in der Unterabfrage entspricht dem CTE von @ ypercube . Gängige Tabellenausdrücke bieten zusätzliche Funktionen und sind in großen Abfragen leichter zu lesen, sie stellen jedoch auch ein Optimierungshindernis dar. Daher sind Unterabfragen in der Regel etwas schneller, wenn keine der oben genannten Anforderungen erfüllt ist.
id
als Spaltenname dient ein weit verbreitetes Anti-Pattern. Sollte foo_id
und bar_id
oder etwas beschreibend sein. Wenn Sie eine Reihe von Tabellen verbinden, erhalten Sie am Ende mehrere Spalten mit dem Namen id
...
Betrachten Sie einfach text
oder varchar
statt varchar(n)
. Wenn Sie wirklich eine Längenbeschränkung festlegen müssen, fügen Sie eine CHECK
Einschränkung hinzu:
Möglicherweise müssen Sie explizite Typumwandlungen hinzufügen. Da der VALUES
Ausdruck nicht direkt an eine Tabelle angehängt ist (wie in INSERT ... VALUES ...
), können keine Typen abgeleitet und Standarddatentypen ohne explizite Typdeklaration verwendet werden, was möglicherweise nicht in allen Fällen funktioniert. Es ist genug, um es in der ersten Reihe zu tun, der Rest wird in die Schlange fallen.
INSERT fügt gleichzeitig fehlende FK-Zeilen ein
Wenn Sie nicht vorhandene Einträge im laufenden Betrieb erstellen möchten, sind CTEs foo
in einer einzelnen SQL-Anweisung von entscheidender Bedeutung:
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);
Beachten Sie die zwei neuen einzufügenden Platzhalterzeilen. Beide sind lila , was es in foo
noch nicht gibt . Zwei Zeilen zur Veranschaulichung der Notwendigkeit DISTINCT
in der ersten INSERT
Anweisung.
Schritt für Schritt Erklärung
Der 1. CTE sel
stellt mehrere Zeilen von Eingabedaten bereit. Die Unterabfrage val
mit dem VALUES
Ausdruck kann durch eine Tabelle oder Unterabfrage als Quelle ersetzt werden. Sofort LEFT JOIN
, foo
um die foo_id
für bereits vorhandene type
Zeilen anzufügen. Alle anderen Zeilen erhalten auf foo_id IS NULL
diese Weise.
Der 2. CTE ins
fügt verschiedene neue Typen ( foo_id IS NULL
) ein foo
und gibt den neu generierten foo_id
zusammen mit dem type
zurück, um Zeilen einzufügen.
Das letzte äußere INSERT
kann jetzt eine foo.id für jede Zeile einfügen: Entweder der Typ, der bereits vorhanden war, oder er wurde in Schritt 2 eingefügt.
Genau genommen geschehen beide Einfügungen "parallel", aber da dies eine einzelne Anweisung ist, werden sich Standardeinschränkungen FOREIGN KEY
nicht beschweren. Die referenzielle Integrität wird standardmäßig am Ende der Anweisung erzwungen.
SQL Fiddle für Postgres 9.3. (Funktioniert genauso in 9.1.)
Es ist eine winzige Race - Bedingung , wenn Sie mehrere dieser Abfragen gleichzeitig ausgeführt werden . Lesen Sie mehr unter verwandten Fragen hier und hier und hier . Wirklich nur unter starker gleichzeitiger Belastung, wenn überhaupt. Im Vergleich zu Caching-Lösungen, wie sie in einer anderen Antwort angekündigt wurden, ist die Chance winzig .
Funktion für den wiederholten Gebrauch
Für die wiederholte Verwendung würde ich eine SQL-Funktion erstellen, die ein Array von Datensätzen als Parameter verwendet und unnest(param)
anstelle des VALUES
Ausdrucks verwendet.
Wenn Ihnen die Syntax für Arrays von Datensätzen zu unübersichtlich ist, verwenden Sie eine durch Kommas getrennte Zeichenfolge als Parameter _param
. Zum Beispiel des Formulars:
'description1,type1;description2,type2;description3,type3'
Verwenden Sie dann diesen Befehl, um den VALUES
Ausdruck in der obigen Anweisung zu ersetzen :
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Funktion mit UPSERT in Postgres 9.5
Erstellen Sie einen benutzerdefinierten Zeilentyp für die Parameterübergabe. Wir könnten darauf verzichten, aber es ist einfacher:
CREATE TYPE foobar AS (description text, type text);
Funktion:
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;
Anruf:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
Schnell und absolut zuverlässig für Umgebungen mit gleichzeitigen Transaktionen.
Zusätzlich zu den obigen Abfragen ...
... trifft zu SELECT
oder INSERT
ein foo
: Alle type
, die noch nicht in der FK-Tabelle vorhanden sind, werden eingefügt. Vorausgesetzt, die meisten Typen existieren bereits. Um absolut sicher zu sein und Rennbedingungen auszuschließen, werden vorhandene Zeilen, die wir benötigen, gesperrt (damit gleichzeitige Transaktionen nicht stören können). Wenn das für Ihren Fall zu paranoid ist, können Sie Folgendes ersetzen:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
mit
ON CONFLICT(type) DO NOTHING
... gilt INSERT
oder UPDATE
(wahres "UPSERT") am bar
: Wenn das description
bereits existiert, type
wird es aktualisiert:
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
Aber nur wenn sich type
tatsächlich ändert:
... übergibt Werte als bekannte Zeilentypen mit einem VARIADIC
Parameter. Beachten Sie das Standardmaximum von 100 Parametern! Vergleichen Sie:
Es gibt viele andere Möglichkeiten, mehrere Zeilen zu übergeben ...
Verbunden: