INSERT en utilisant les résultats de CTE INSERT pour fournir des valeurs d'ID uniques

8

J'écris un travail pour transformer les données d'une ancienne conception en une nouvelle conception. Dans ce processus, je dois prendre l'ID d'une insertion dans une table distincte et l'utiliser dans une insertion vers la table cible, en tant que telle:

CREATE TABLE t1 {
  t1_id BIGSERIAL,
  col1 VARCHAR
};
CREATE TABLE t2 {
  t2_id BIGSERIAL,
  col2 VARCHAR, -- renamed from col1 to avoid confusion
  t1_id BIGINT REFERENCES t1.t1_id
};

J'ai le SQL défini qui correspond au formulaire suivant:

WITH ins AS (
  INSERT INTO t1 (t1_id) VALUES (DEFAULT) RETURNING t1_id
) INSERT INTO t2
  (col1, t1_id)
SELECT
  a.val1, (SELECT * FROM ins)
FROM t3 a;

Je voulais que cela exécute le SELECT * FROM inspour chaque ligne de la SELECT.. mais à la place, il ne l'exécute qu'une seule fois et utilise cette valeur pour toutes les lignes de la SELECT. Comment puis-je restructurer mon SQL pour obtenir le comportement souhaité?

edit4

t1 finit par ressembler à:

1,<NULL>
(1 row)

t2 finit par ressembler à:

10,'a',1
11,'b',1 -- problem with id from t1 being 1
12,'c',1 -- problem with id from t1 being 1
.
.

À quoi je veux que t1 ressemble:

1,<NULL>
2,<NULL>
3,<NULL>
.
.

À quoi je veux que t2 ressemble:

10,'a',1
11,'b',2 -- id from t1 of 2
12,'c',3 -- id from t1 of 3
.
.

modifier Pour répondre à ce qu'a dit a_horse_with_no_name, j'ai également essayé ceci (avec le même résultat):

WITH ins AS (
  INSERT INTO t1 (t1_id) VALUES (DEFAULT) RETURNING t1_id
) INSERT INTO t2
  (col1, t1_id)
SELECT
  a.val1, b.t1_id
FROM t3 a
JOIN ins b ON TRUE;

edit2 J'ai juste essayé de référencer directement le approprié SEQUENCEdans ma requête, et cela fonctionne - mais je n'aime pas du tout cette solution (principalement parce que je n'aime pas les noms d'objets codés en dur.) S'il existe une solution autre que de référencer directement le nom du SEQUENCEje l'apprécierais. :)

edit3 Je suppose qu'une autre solution serait d'utiliser un PROCEDUREpour faire le INSERTau lieu d'un CTE .. mais j'apprécierais toujours les options / suggestions.

Joishi Bodio
la source
1
Vous devez rejoindre insett3
a_horse_with_no_name
J'ai également essayé cela et il n'a calculé la valeur qu'une seule fois. Mais peut-être que ma jointure n'était pas tout à fait correcte. Je vais modifier mon message pour montrer ce que j'ai essayé avec ça.
Joishi Bodio
1
Vous insérez une seule ligne dans t1et ne fournissez aucune valeur pour t1.col1. D'où les données doivent-elles provenir pour cette colonne? Est t1.col1lié à t2.col1?
ypercubeᵀᴹ
ypercube - t1.col1 peut être NULL et sera inséré dans un processus ultérieur. Parce que je référençais le CTE en tant que SUBSELECT dans les valeurs de ligne réelles, j'ai pensé qu'il serait exécuté plus d'une fois - mais il s'avère que j'avais tort dans cette hypothèse .. c'est pourquoi je pose cette question ici. J'ai déjà essayé de rechercher une réponse sur Google au cours des dernières heures et je n'ai pas encore trouvé ce qui est correct. Et non, t1.col1 n'est pas lié à t2.col1 .. désolé pour cette confusion.
Joishi Bodio
1
Pourtant, INSERT INTO t1 (t1_id) VALUES (DEFAULT)insère seulement 1 ligne dans t1. Donc, peu importe si vous mettez insla FROMclause dans la clause et que vous la joignez t3ou non. Pouvez-vous nous montrer comment insérer 2 lignes (ou plus) t1? Et plus important encore, comment savez-vous laquelle des 2 (ou plus) t1.idvaleurs correspondrait aux lignes insérées dans t2?
ypercubeᵀᴹ

Réponses:

8

Je ne comprends pas pourquoi vous avez besoin de 2 tables si elles n'ont qu'une relation 1-1. Mais le voici ( pkc'est la clé primaire de t3):

WITH ins AS (
  INSERT INTO t1 (col1) 
    SELECT NULL FROM t3 
  RETURNING t1_id
) 
, r AS
( SELECT t1_id, ROW_NUMBER() OVER () AS rn
  FROM ins
) 
, t AS
( SELECT *, ROW_NUMBER() OVER () AS rn
  FROM t3
) 
INSERT INTO t2
  (col1, t1_id)
SELECT
  t.val1, r.t1_id
FROM t 
  JOIN r USING (rn) ;

Si votre t3 est le résultat d'un SELECT au lieu d'une table préexistante, vous pouvez l'implémenter tel quel afin de ne pas avoir à répéter la requête t3 deux fois:

WITH t3 AS (
  SELECT ...
), ins AS (
  INSERT INTO t1 (col1)
    SELECT NULL FROM t3
  RETURNING t1_id
), r AS (
  SELECT t1_id, ROW_NUMBER() OVER () AS rn
  FROM ins
), t AS (
  SELECT *, ROW_NUMBER() OVER () AS rn
  FROM t3
) INSERT INTO t2
  (col1, t1_id)
SELECT
  t.val1, r.t1_id
FROM t 
  JOIN r USING (rn);
ypercubeᵀᴹ
la source
La raison pour laquelle j'ai besoin des deux tables est qu'il existe une autre table qui devra également stocker des valeurs dans t1 .. (t1 aura des liens vers t2 et t4) t1 est censé être une table pour les informations de contact (avec fkeys pour adresse, adresse e-mail et numéros de téléphone) et t2 et t4 sont tous deux des entités dans des domaines différents qui devront avoir des informations de contact associées. Je peux avoir une partie de mon vocabulaire incorrect, mais c'est essentiellement la raison. Merci pour la réponse - je vais tester.
Joishi Bodio
Modification d'une erreur mineure. Utilisez la dernière version.
ypercubeᵀᴹ
OK, ça a du sens alors. Mais vous n'avez peut-être pas besoin du t2_idtout. Il semble que vous pouvez utiliser le t2(t1_id)comme PK de t2.
ypercubeᵀᴹ
:) Cela me donne une erreur de syntaxe avec DEFAULT pour le moment - essayant de comprendre ce que cela pourrait être. ERROR: syntax error at or near "DEFAULT" LINE 2: DEFAULT AS contact_detail_id
Joishi Bodio
Hm, ça ne DEFAULTpeut pas être utilisé de cette façon. Ni retour let.pk
ypercubeᵀᴹ