Wstaw dane w 3 tabelach naraz za pomocą Postgres

84

Chcę wstawić dane do 3 tabel za pomocą jednego zapytania.
Moje tabele wyglądają jak poniżej:

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)
);

Otrzymam klucz w zamian za każde włożenie i muszę wstawić ten klucz do następnej tabeli.
Moje zapytanie to:

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;

Ale jeśli uruchamiam pojedyncze zapytania, po prostu zwracają mi wartości i nie mogę ich natychmiast użyć ponownie w następnym zapytaniu.

Jak to osiągnąć?

Faisal
źródło

Odpowiedzi:

136

Użyj CTE modyfikujących dane :

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;

Każdy INSERTzależy od poprzedniego. SELECTzamiast VALUESupewniać się, że nic nie zostanie wstawione do tabel pomocniczych, jeśli żaden wiersz nie zostanie zwrócony z poprzedniego INSERT. (Od Postgres 9.5+ możesz dodać ON CONFLICT.)
W ten sposób jest też nieco krótszy i szybszy.

Zwykle wygodniej jest podać pełne wiersze danych w jednym miejscu :

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 <> skrzypce tutaj

Możesz potrzebować jawnych rzutów typu w VALUESwyrażeniu autonomicznym - w przeciwieństwie do VALUESwyrażenia dołączonego do, INSERTgdzie typy danych pochodzą z tabeli docelowej. Widzieć:

Jeśli wiele wierszy może (firstname, lastname)zawierać identyczne , może być konieczne złożenie duplikatów dla pierwszego INSERT:

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

Zamiast CTE można użyć tabeli (tymczasowej) jako źródła danych data.

Prawdopodobnie sensowne byłoby połączenie tego z ograniczeniem UNIQUE (firstname, lastname)w tabeli i ON CONFLICTklauzulą ​​w zapytaniu.

Związane z:

Erwin Brandstetter
źródło
1
dziękuję za powtórkę, czy mogę dodać realizację transakcji, jeśli wystąpi jakiekolwiek niepowodzenie wstawiania. tak, jak mogę i
Faisal
3
To jest pojedyncza instrukcja SQL. Można połączyć kilka wyciągów w jedną transakcję, ale tej jednej nie można podzielić. Również to, co mówi Denis w swoim komentarzu. I dołączyłem kilka linków do mojej odpowiedzi.
Erwin Brandstetter
2
@mmcrae: Tak, możesz. Powiązane: dba.stackexchange.com/questions/151199/…
Erwin Brandstetter
1
@No_name: jasne, różne sposoby. Proponuję zadać pytanie z doprecyzowaniem szczegółów. zawsze możesz tutaj linkować, aby uzyskać kontekst. lub upuść komentarz tutaj, aby zwrócić moją uwagę.
Erwin Brandstetter
1
Czy to pomyłka? Czy INSERT INTO sample1 (user_id, adddetails)nie powinno tak być w Twojej odpowiedzi (sample_id, addetails)?
Adam Hughes,
19

Coś takiego

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');

Ponieważ wygenerowany identyfikator z wstawiania do sample2nie jest potrzebny, usunąłem returningklauzulę z ostatniej wstawki.

koń bez imienia
źródło
Podoba mi się to podejście z wybranymi wartościami wewnętrznymi. Jest bardziej spójny, a także może upuszczać aliasy powrotne wewnątrz instrukcji with
mattdlockyer
6

Zazwyczaj używałbyś transakcji, aby uniknąć pisania skomplikowanych zapytań.

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

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

Możesz także użyć CTE, zakładając, że twój znacznik Postgres jest poprawny. Na przykład:

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;
Denis de Bernardy
źródło
1
dzięki, w jaki sposób mogę osiągnąć transakcję w tym zapytaniu, jeśli jakikolwiek wkład nie powiedzie się, co mogę zrobić
Faisal
1
Następnie zaczynasz wszystko od nowa, oczywiście po poprawieniu zapytań, ponieważ cała transakcja (lub cte) zostałby wycofany. Przy okazji, jeśli twoje wkładki czasami zawodzą, prawdopodobnie robisz coś nie tak. Jedynym przypadkiem, w którym rozsądne jest niepowodzenie wstawiania, jest scenariusz odwrotny, który powoduje zduplikowane unikalne klucze podczas jednoczesnych transakcji, a nawet wtedy możesz uzyskać blokadę doradczą lub blokadę tabeli, jeśli chcesz, aby wszystko było kuloodporne.
Denis de Bernardy
3

Możesz utworzyć wyzwalacz po wstawieniu w tabeli Sample, aby wstawić go do pozostałych dwóch tabel.

Jedynym problemem, jaki widzę, jest to, że nie będziesz mieć możliwości wstawiania adddetails, zawsze będzie on pusty lub w tym przypadku ss. Nie ma możliwości wstawienia kolumny do próbki, której nie ma w tabeli próbek, więc nie można jej wysłać razem z wkładką wewnętrzną.

Inną opcją byłoby utworzenie procedury składowanej do uruchamiania wstawek.

Masz pytanie taged mysql i postgressql o jakiej bazie danych tu mówimy?

DaImTo
źródło