Postgresを使用して一度に3つのテーブルにデータを挿入します


84

1つのクエリで3つのテーブルにデータを挿入したいと思います。
私のテーブルは以下のようになります:

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

挿入するたびにキーを取得し、そのキーを次のテーブルに挿入する必要があります。
私の質問は:

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;

しかし、単一のクエリを実行すると、値が返されるだけで、次のクエリですぐに再利用することはできません。

これを達成する方法は?

回答:


136

データ変更CTEを使用する

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;

それぞれがINSERT前のものに依存します。前のから行が返されない場合は、補助テーブルに何も挿入されないSELECTようにVALUESする代わりにINSERT。(Postgres 9.5以降では、を追加する可能性がありON CONFLICTます。)
この方法では、少し短く、高速になります。

通常、完全なデータ行を1か所提供する方が便利です。

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 <>フィドルはこちら

データ型がターゲットテーブルから派生する場所にアタッチさVALUESれたVALUES式とは対照的に、スタンドアロン式で明示的な型キャストが必要になる場合がありINSERTます。見る:

複数の行が同一(firstname, lastname)である可能性がある場合は、最初の行の重複を折りたたむ必要がある場合がありますINSERT

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

CTEの代わりに(一時的な)テーブルをデータソースとして使用できますdata

これを(firstname, lastname)、テーブルのUNIQUE制約およびON CONFLICTクエリの句と組み合わせるのはおそらく理にかなっています。

関連:


1
リプレイに感謝します。挿入の失敗が発生した場合、トランザクションのロールアウトを追加できます。はい、どうすればよいですか
Faisal

3
これは単一のSQLステートメントです。複数のステートメントを1つのトランザクションにバンドルすることはできますが、これを分割することはできません。また、デニスが彼のコメントで言っていること。そして、私は私の答えにいくつかのリンクを追加しました。
Erwin Brandstetter 2013

2
@mmcrae:はい、できます。関連:dba.stackexchange.com/questions/151199/...
アーウィンBrandstetter

1
@No_name:確かに、さまざまな方法です。詳細を定義して質問することをお勧めします。コンテキストについては、いつでもここにリンクできます。または、ここにコメントをドロップして、私の注意を引き付けてください。
ErwinBrandstetter20年

1
これはタイプミスですか?あなたの答えINSERT INTO sample1 (user_id, adddetails)では、そうではありません(sample_id, addetails)か?
アダムヒューズ

19

このようなもの

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

挿入から生成されたIDsample2は必要ないため、returning最後の挿入から句を削除しました。


私は、内部値を選択するこのアプローチが好きです。これは、より一貫性だとも文と内側戻し別名をドロップすることができます
mattdlockyer

6

通常、複雑なクエリの記述を避けるためにトランザクションを使用します。

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

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

Postgresタグが正しい場合は、CTEを使用することもできます。例えば:

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
挿入が失敗した場合、このクエリでトランザクションをどのように達成しますか?ロールバックを実行できます
Faisal

1
次に、もちろんクエリを修正した後、トランザクション全体(またはcte)がロールバックされるため、すべてを最初からやり直します。ところで、挿入がときどき失敗する場合は、おそらく何か問題があります。挿入が失敗するのが合理的である唯一のケースは、同時トランザクション中に重複する一意のキーに遭遇するアップサートシナリオであり、それでも、防弾を行う必要がある場合は、アドバイザリロックまたはテーブルロックを取得できます。
Denis de Bernardy 2013

3

サンプルテーブルに挿入後トリガーを作成して、他の2つのテーブルに挿入することができます。

これを行うことで私が見る唯一の問題は、adddetailsを挿入する方法がないことです。これは常に空であるか、この場合はssです。サンプルテーブルに実際にはない列をサンプルに挿入する方法はないため、最初の挿入と一緒に送信することはできません。

もう1つのオプションは、挿入を実行するためのストアドプロシージャを作成することです。

ここで話しているデータベースは、mysqlとpostgressqlのタグが付いた質問がありますか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.