web-dev-qa-db-fra.com

Insérez des données dans 3 tableaux à la fois à l'aide de Postgres

Je veux insérer des données dans 3 tables avec une seule requête.
Mes tableaux ressemblent à ci-dessous:

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

J'obtiendrai une clé en retour pour chaque insertion et je dois insérer cette clé dans le tableau suivant.
Ma requête est:

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;

Mais si j'exécute des requêtes uniques, elles me renvoient simplement des valeurs et je ne peux pas les réutiliser dans la requête suivante immédiatement.

Comment y parvenir?

56
Faisal

Utilisez CTE de modification des données :

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

Chaque INSERT dépend de la précédente. SELECT au lieu de VALUES s'assure que rien n'est inséré dans les tables subsidiaires si aucune ligne n'est renvoyée d'un INSERT précédent. (Connexes: les ON CONFLICT clause dans Postgres 9.5+)
C'est aussi un peu plus court et plus rapide de cette façon.


En règle générale, il est plus pratique de fournir des lignes de données complètes en un seul endroit:

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

Vous pouvez avoir besoin de transtypages de types explicites dans une expression VALUES distincte (par opposition à une expression VALUES attachée à un INSERT, où les types de données sont dérivés de la table cible.

Si plusieurs lignes peuvent être accompagnées de (firstname, lastname), vous devrez peut-être plier les doublons pour la première insertion:

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

Vous pouvez utiliser une table (temporaire) comme source de données au lieu de CTE data.

Connexes, avec plus de détails:

99
Erwin Brandstetter

Quelque chose comme ça

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

Comme l'id généré à partir de l'insert dans sample2 n'est pas nécessaire, j'ai supprimé la clause returning de la dernière insertion.

14

En règle générale, vous utiliseriez une transaction pour éviter d'écrire des requêtes compliquées.

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

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

Vous pouvez également utiliser un CTE, en supposant que votre balise Postgres est correcte. Par exemple:

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;
5
Denis de Bernardy

Vous pouvez créer un déclencheur après insertion sur la table d'échantillons à insérer dans les deux autres tables.

Le seul problème que je vois avec cela est que vous n'aurez pas un moyen d'insérer des adddetails, il sera toujours vide ou dans ce cas ss. Il n'y a aucun moyen d'insérer une colonne dans l'échantillon qui n'est pas réellement dans la table d'échantillons, vous ne pouvez donc pas l'envoyer avec l'insert initial.

Une autre option serait de créer une procédure stockée pour exécuter vos insertions.

Vous avez la question taged mysql et postgressql de quelle base de données parlons-nous ici?

3
DaImTo