Fügen Sie mit Postgres Daten in jeweils 3 Tabellen ein


81

Ich möchte Daten mit einer einzigen Abfrage in 3 Tabellen einfügen.
Meine Tabellen sehen wie folgt aus:

CREATE TABLE sample (
   id        bigserial PRIMARY KEY,
   lastname  varchar(20),
   firstname varchar(20)
);

CREATE TABLE sample1(
   user_id    bigserial PRIMARY KEY,
   sample_id  bigint REFERENCES sample,
   adddetails varchar(20)
);

CREATE TABLE sample2(
   id      bigserial PRIMARY KEY,
   user_id bigint REFERENCES sample1,
   value   varchar(10)
);

Ich bekomme für jede Einfügung einen Schlüssel und muss diesen Schlüssel in die nächste Tabelle einfügen.
Meine Anfrage lautet:

insert into sample(firstname,lastname) values('fai55','shaggk') RETURNING id;
insert into sample1(sample_id, adddetails) values($id,'ss') RETURNING user_id;
insert into sample2(user_id, value) values($id,'ss') RETURNING id;

Wenn ich jedoch einzelne Abfragen ausführe, geben sie nur Werte an mich zurück und ich kann sie nicht sofort in der nächsten Abfrage wiederverwenden.

Wie erreicht man das?

Antworten:


131

Verwenden Sie datenmodifizierende CTEs :

WITH ins1 AS (
   INSERT INTO sample(firstname, lastname)
   VALUES ('fai55', 'shaggk')
-- ON     CONFLICT DO NOTHING         -- optional addition in Postgres 9.5+
   RETURNING id AS sample_id
   )
, ins2 AS (
   INSERT INTO sample1 (sample_id, adddetails)
   SELECT sample_id, 'ss' FROM ins1
   RETURNING user_id
   )
INSERT INTO sample2 (user_id, value)
SELECT user_id, 'ss2' FROM ins2;

Jeder INSERThängt von dem vorhergehenden ab. SELECTanstatt VALUESsicherzustellen, dass nichts in Nebentabellen eingefügt wird, wenn keine Zeile von einer vorherigen zurückgegeben wird INSERT. (Seit Postgres 9.5+ können Sie eine hinzufügen ON CONFLICT.) Auf
diese Weise ist es auch etwas kürzer und schneller.

In der Regel ist es bequemer , vollständige Datenzeilen an einem Ort bereitzustellen :

WITH data(firstname, lastname, adddetails, value) AS (
   VALUES                              -- provide data here
      ('fai55', 'shaggk', 'ss', 'ss2') -- see below
    , ('fai56', 'XXaggk', 'xx', 'xx2') -- works for multiple input rows
       --  more?                      
   )
, ins1 AS (
   INSERT INTO sample (firstname, lastname)
   SELECT firstname, lastname          -- DISTINCT? see below
   FROM   data
   -- ON     CONFLICT DO NOTHING       -- UNIQUE constraint? see below
   RETURNING firstname, lastname, id AS sample_id
   )
, ins2 AS (
   INSERT INTO sample1 (sample_id, adddetails)
   SELECT ins1.sample_id, d.adddetails
   FROM   data d
   JOIN   ins1 USING (firstname, lastname)
   RETURNING sample_id, user_id
   )
INSERT INTO sample2 (user_id, value)
SELECT ins2.user_id, d.value
FROM   data d
JOIN   ins1 USING (firstname, lastname)
JOIN   ins2 USING (sample_id);

db <> hier fummeln

Möglicherweise benötigen Sie explizite Typumwandlungen in einem eigenständigen VALUESAusdruck - im Gegensatz zu einem VALUESAusdruck, der an einen Ort angehängt ist, an INSERTdem Datentypen aus der Zieltabelle abgeleitet werden. Sehen:

Wenn mehrere Zeilen mit identischen kommen können (firstname, lastname) sein können, müssen Sie möglicherweise zuerst Duplikate falten INSERT:

...
INSERT INTO sample (firstname, lastname)
SELECT DISTINCT firstname, lastname FROM data
...

Sie können anstelle des CTE eine (temporäre) Tabelle als Datenquelle verwenden data.

Es wäre wahrscheinlich sinnvoll, dies mit einer EINZIGARTIGEN Einschränkung zu kombinieren (firstname, lastname) in der Tabelle und einer ON CONFLICTKlausel in der Abfrage .

Verbunden:


1
Vielen Dank für die Wiederholung kann ich Transaktions-Rollout hinzufügen, wenn eine fehlerhafte Einfügung auftritt. Ja, wie kann ich
Faisal

3
Dies ist eine einzelne SQL-Anweisung. Man kann mehrere Anweisungen in einer einzigen Transaktion bündeln, aber man kann diese nicht aufteilen. Auch was Denis in seinem Kommentar sagt. Und ich habe einige Links zu meiner Antwort angehängt.
Erwin Brandstetter

2
@mmcrae: Ja, das kannst du. Siehe auch
Erwin Brandstetter

1
@No_name: sicher, verschiedene Möglichkeiten. Ich schlage vor, Sie stellen eine Frage mit definierenden Details. Sie können hier immer für den Kontext verlinken. oder hinterlasse hier einen Kommentar, um meine Aufmerksamkeit zu erregen.
Erwin Brandstetter

1
Ist das ein Tippfehler? Sollte es in Ihrer Antwort INSERT INTO sample1 (user_id, adddetails)nicht sein (sample_id, addetails)?
Adam Hughes

17

Etwas wie das

with first_insert as (
   insert into sample(firstname,lastname) 
   values('fai55','shaggk') 
   RETURNING id
), 
second_insert as (
  insert into sample1( id ,adddetails) 
  values
  ( (select id from first_insert), 'ss')
  RETURNING user_id
)
insert into sample2 ( id ,adddetails) 
values 
( (select user_id from first_insert), 'ss');

Da die generierte ID aus der Einfügung in sample2nicht benötigt wird, habe ich die returningKlausel aus der letzten Einfügung entfernt.


Ich mag diesen Ansatz mit ausgewählten Innenwerten. Es ist konsistenter und kann auch die Rückgabe-Aliase in den with-Anweisungen
löschen

6

Normalerweise verwenden Sie eine Transaktion, um das Schreiben komplizierter Abfragen zu vermeiden.

http://www.postgresql.org/docs/current/static/sql-begin.html

http://dev.mysql.com/doc/refman/5.7/en/commit.html

Sie können auch einen CTE verwenden, vorausgesetzt, Ihr Postgres-Tag ist korrekt. Zum Beispiel:

with sample_ids as (
  insert into sample(firstname, lastname)
  values('fai55','shaggk')
  RETURNING id
), sample1_ids as (
  insert into sample1(id, adddetails)
  select id,'ss'
  from sample_ids
  RETURNING id, user_id
)
insert into sample2(id, user_id, value)
select id, user_id, 'val'
from sample1_ids
RETURNING id, user_id;

1
Danke, wie würde ich eine Transaktion in dieser Abfrage erreichen, wenn eine Einfügung fehlschlägt? Ich könnte ein Rollback durchführen
Faisal

1
Dann beginnen Sie alles von vorne, nachdem Sie natürlich die Abfragen korrigiert haben, da die gesamte Transaktion (oder das cte) zurückgesetzt würde. Übrigens, wenn Ihre Einsätze gelegentlich ausfallen, machen Sie wahrscheinlich etwas falsch. Der einzige Fall, in dem es vernünftig ist, dass eine Einfügung fehlschlägt, ist ein Upsert-Szenario, bei dem bei gleichzeitigen Transaktionen doppelte eindeutige Schlüssel auftreten. Selbst dann können Sie eine Hinweis- oder eine Tabellensperre erhalten, wenn Sie einen kugelsicheren Vorgang durchführen müssen.
Denis de Bernardy

3

Sie können einen After-Insert-Trigger für die Sample-Tabelle erstellen, um ihn in die beiden anderen Tabellen einzufügen.

Das einzige Problem, das ich dabei sehe, ist, dass Sie keine Möglichkeit haben, Adddetails einzufügen. Es wird immer leer sein oder in diesem Fall ss. Es gibt keine Möglichkeit, eine Spalte in eine Probe einzufügen, die nicht tatsächlich in der Probentabelle enthalten ist, sodass Sie sie nicht zusammen mit der angeborenen Einfügung senden können.

Eine andere Möglichkeit wäre, eine gespeicherte Prozedur zum Ausführen Ihrer Einfügungen zu erstellen.

Sie haben die Frage mysql und postgressql gestellt, über welche Datenbank sprechen wir hier?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.