web-dev-qa-db-ja.com

関係するテーブルから選択して、1つのクエリで複数のテーブルに行を挿入します

次の形式の2つのテーブルがあります(つまり、すべてのfooはちょうど1つのバーにリンクされています)。

CREATE TABLE foo (
    id INTEGER PRIMARY KEY,
    x INTEGER NOT NULL,
    y INTEGER NOT NULL,
    ...,
    bar_id INTEGER UNIQUE NOT NULL,
    FOREIGN key (bar_id) REFERENCES bar(id)
);

CREATE TABLE bar (
    id INTEGER PRIMARY KEY,
    z INTEGER NOT NULL,
    ...
);

ネストされたクエリを使用して、特定の条件を満たすfooの行をコピーするのは簡単です。

INSERT INTO foo (...) (SELECT ... FROM foo WHERE ...)

しかし、barの各行に対してfooの関連する行のコピーを作成し、barのIDを新しいfoo行。単一のクエリでこれを行う方法はありますか?

望ましい結果の具体例:

-- Before query:

foo(id=1,x=3,y=4,bar_id=100)  .....  bar(id=100,z=7)
foo(id=2,x=9,y=6,bar_id=101)  .....  bar(id=101,z=16)
foo(id=3,x=18,y=0,bar_id=102) .....  bar(id=102,z=21)


-- Query copies all pairs of foo/bar rows for which x>3:

-- Originals
foo(id=1,x=3,y=4,bar_id=101)  .....  bar(id=101,z=7)
foo(id=2,x=9,y=6,bar_id=102)  .....  bar(id=102,z=16)
foo(id=3,x=18,y=0,bar_id=103) .....  bar(id=103,z=21)

-- "Copies" of foo(id=2,...) and foo(id=3,...), with matching copies of
-- bar(id=102,...) and bar(id=103,...)
foo(id=4,x=9,y=6,bar_id=104)  .....  bar(id=104,z=16)
foo(id=5,x=18,y=0,bar_id=105) .....  bar(id=105,z=21)
17
foldl

最終版

... OPからの詳細情報の後。このデモを検討してください:

_-- DROP TABLE foo; DROP TABLE bar;

CREATE TEMP TABLE bar (
 id serial PRIMARY KEY  -- using a serial column!
,z  integer NOT NULL
);

CREATE TEMP TABLE foo (
 id     serial PRIMARY KEY  -- using a serial column!
,x      integer NOT NULL
,y      integer NOT NULL
,bar_id integer UNIQUE NOT NULL REFERENCES bar(id)
);
_

値を挿入-barを最初に。
このような質問でテストデータを提供した場合、非常にhelpfulになります。

_INSERT INTO bar (id,z) VALUES
 (100, 7)
,(101,16)
,(102,21);

INSERT INTO foo (id, x, y, bar_id) VALUES
 (1, 3,4,100)
,(2, 9,6,101)
,(3,18,0,102);
_

シーケンスを現在の値に設定するか、重複キー違反が発生します。

_SELECT setval('foo_id_seq', 3);
SELECT setval('bar_id_seq', 102);
_

チェック:

_-- SELECT nextval('foo_id_seq')
-- SELECT nextval('bar_id_seq')
-- SELECT * from bar;
-- SELECT * from foo;
_

クエリ:

_WITH a AS (
    SELECT f.x, f.y, bar_id, b.z
    FROM   foo f
    JOIN   bar b ON b.id = f.bar_id
    WHERE  x > 3
    ),b AS (
    INSERT INTO bar (z)
    SELECT z
    FROM   a
    RETURNING z, id AS bar_id
    )
INSERT INTO foo (x, y, bar_id)
SELECT a.x, a.y, b.bar_id
FROM   a
JOIN   b USING (z);
_

これで、前回の更新で説明したとおりになります。

このクエリでは、zUNIQUEであると想定しています。 zが一意でない場合、より複雑になります。この場合のウィンドウ関数row_number()を使用する準備ができたソリューションについては、 この関連する回答のクエリ2 を参照してください。

また、foobar1:1の関係を単一の結合テーブルに置き換えることを検討してください。


データ修正CTE

詳細情報の後の2番目の回答。

単一のクエリでfooandbarに行を追加する場合は、 データ修正CTE を使用できます。 = PostgreSQLから9.1

_WITH x AS (
    INSERT INTO bar (col1, col2)
    SELECT f.col1, f.col2
    FROM   foo f
    WHERE  f.id BETWEEN 12 AND 23 -- some filter
    RETURNING col1, col2, bar_id  -- assuming bar_id is a serial column
    )
INSERT INTO foo (col1, col2, bar_id)
SELECT col1, col2, bar_id
FROM   x;
_

fooから値を取得し、barに挿入し、自動生成された_bar_id_と一緒に返して、thatを挿入しますfooに。他のデータも使用できます。

sqlfiddleで遊ぶための作業デモ です。


基礎

明確化する前の基本情報を含む元の回答。
基本的な形式は次のとおりです。

_INSERT INTO foo (...)
SELECT ... FROM foo WHERE ...
_

括弧は必要ありません。どのテーブルでも同じことができます

_INSERT INTO foo (...)
SELECT ... FROM bar WHERE ...
_

また、SELECTで挿入したテーブルに結合できます。

_INSERT INTO foo (...)
SELECT f.col1, f.col2, .. , b.bar_id
FROM   foo f
JOIN   bar b USING (foo_id);  -- present in foo and bar
_

これは、他のすべてのSELECTと同じです。挿入するテーブルを含めることができます。行が最初に読み込まれ、次に挿入されます。

29

idbarがシリアルでデフォルト値がnextval('bar_id_seq'::regclass)の場合、この関数を手動で呼び出してcteで新しいIDを取得できます。

with
s_bar as (
  SELECT id, z, nextval('bar_id_seq'::regclass) new_id
  FROM   bar
  WHERE  ...
),
s_foo as (
  SELECT x, y, bar_id
  FROM   foo
  WHERE  ...
),
i_bar as (
  INSERT INTO bar (id, z)
  SELECT new_id, z
  FROM   s_bar
),
i_foo as (
  INSERT INTO foo (x, y, bar_id)
  SELECT f.x, f.y, b.new_id
  FROM   s_foo f
  JOIN   s_bar b on b.id = f.bar_id
)
SELECT 1
0
Matveev Dmitriy