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] JOINanstelle von [INNER] JOINbedeutet, dass Zeilen von val nicht gelöscht werden, wenn in keine Übereinstimmung gefunden wird foo. Stattdessen NULLwird für eingegeben foo_id.
Der VALUESAusdruck 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.
idals Spaltenname dient ein weit verbreitetes Anti-Pattern. Sollte foo_idund bar_idoder etwas beschreibend sein. Wenn Sie eine Reihe von Tabellen verbinden, erhalten Sie am Ende mehrere Spalten mit dem Namen id...
Betrachten Sie einfach textoder varcharstatt varchar(n). Wenn Sie wirklich eine Längenbeschränkung festlegen müssen, fügen Sie eine CHECKEinschränkung hinzu:
Möglicherweise müssen Sie explizite Typumwandlungen hinzufügen. Da der VALUESAusdruck 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 fooin 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 foonoch nicht gibt . Zwei Zeilen zur Veranschaulichung der Notwendigkeit DISTINCTin der ersten INSERTAnweisung.
Schritt für Schritt Erklärung
Der 1. CTE selstellt mehrere Zeilen von Eingabedaten bereit. Die Unterabfrage valmit dem VALUESAusdruck kann durch eine Tabelle oder Unterabfrage als Quelle ersetzt werden. Sofort LEFT JOIN, fooum die foo_idfür bereits vorhandene typeZeilen anzufügen. Alle anderen Zeilen erhalten auf foo_id IS NULLdiese Weise.
Der 2. CTE insfügt verschiedene neue Typen ( foo_id IS NULL) ein foound gibt den neu generierten foo_idzusammen mit dem typezurück, um Zeilen einzufügen.
Das letzte äußere INSERTkann 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 KEYnicht 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 VALUESAusdrucks 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 VALUESAusdruck 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 SELECToder INSERTein 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 INSERToder UPDATE(wahres "UPSERT") am bar: Wenn das descriptionbereits existiert, typewird 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 typetatsächlich ändert:
... übergibt Werte als bekannte Zeilentypen mit einem VARIADICParameter. Beachten Sie das Standardmaximum von 100 Parametern! Vergleichen Sie:
Es gibt viele andere Möglichkeiten, mehrere Zeilen zu übergeben ...
Verbunden: