web-dev-qa-db-fra.com

INSÉRER des lignes dans plusieurs tables dans une même requête, en sélectionnant une table impliquée

J'ai deux tables de la forme suivante (c'est-à-dire que chaque foo est lié à exactement une barre).

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

Il est facile de copier des lignes dans foo qui répondent à une condition particulière en utilisant une requête imbriquée:

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

Mais je ne sais pas comment faire une copie de la ligne associée dans bar pour chaque ligne dans foo et insérer l'id de bar dans la nouvelle ligne foo Est-il possible de le faire en une seule requête?

Exemple concret de résultat souhaité:

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

Version finale

... après quelques informations supplémentaires de OP. Considérez cette démo:

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

Insérer des valeurs - bar en premier.
Il serait très utile si vous fournissiez des données de test dans votre question comme ceci!

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

Définissez les séquences sur les valeurs actuelles ou nous obtenons des violations de clé en double:

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

Chèques:

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

Question:

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

Cela devrait faire ce que votre dernière mise à jour décrit.

La requête suppose que z est UNIQUE. Si z n'est pas unique, cela devient plus complexe. Référez-vous à Requête 2 dans cette réponse associée pour une solution prête en utilisant la fonction de fenêtre row_number() dans ce cas.

En outre, envisagez de remplacer la relation 1: 1 entre foo et bar par une seule table unie.


Données modifiant le CTE

Deuxième réponse après plus d'infos.

Si vous souhaitez ajouter des lignes à fooetbar dans une requête unique, vous pouvez utiliser une donnée modifiant CTE depuis 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;

Je tire les valeurs de foo, les insère dans bar, les renvoie avec un bar_id généré automatiquement et insère ça dans foo. Vous pouvez également utiliser d'autres données.

Voici une démo de travail pour jouer avec sur sqlfiddle .


Les bases

Réponse originale avec des informations de base avant les clarifications.
Le formulaire de base est:

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

Aucune parenthèse nécessaire . Vous pouvez faire la même chose avec n'importe quelle table

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

Et vous pouvez rejoindre la table dans laquelle vous insérez dans le 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

C'est juste un SELECT comme un autre - qui peut inclure la table dans laquelle vous insérez. Les lignes sont d'abord lues, puis insérées.

29

si id of bar est en série et que sa valeur par défaut est nextval('bar_id_seq'::regclass), vous pouvez appeler cette fonction manuellement pour obtenir les nouveaux identifiants cte.

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