PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Renvoie l'id si une ligne existe, INSERT sinon

Une solution dans une seule instruction SQL. Nécessite PostgreSQL 8.4 ou plus tard.
Considérez la démo suivante :

Configuration du test :

CREATE TEMP TABLE tbl (
  id  serial PRIMARY KEY
 ,txt text   UNIQUE   -- obviously there is unique column (or set of columns)
);

INSERT INTO tbl(txt) VALUES ('one'), ('two');

Commande INSÉRER / SÉLECTIONNER :

WITH v AS (SELECT 'three'::text AS txt)
    ,s AS (SELECT id FROM tbl JOIN v USING (txt))
    ,i AS (
       INSERT INTO tbl (txt)
       SELECT txt
       FROM   v
       WHERE  NOT EXISTS (SELECT * FROM s)
       RETURNING id
       )
SELECT id, 'i'::text AS src FROM i
UNION  ALL
SELECT id, 's' FROM s;
  • Le premier CTE v n'est pas strictement nécessaire, mais permet d'entrer vos valeurs une seule fois.

  • Le deuxième CTE sélectionne l'id de tbl si la "ligne" existe.

  • Le troisième inserts CTE i la "ligne" dans tbl si (et seulement si) il n'existe pas, retour de id .

  • Le dernier SELECT renvoie l'id . J'ai ajouté une colonne src indiquant la "source" - si la "ligne" préexistait et id vient d'un SELECT, ou la "ligne" était nouvelle et le id aussi .

  • Cette version doit être aussi rapide que possible car elle n'a pas besoin d'un SELECT supplémentaire de tbl et utilise les CTE à la place.

Pour sécuriser cela contre d'éventuelles conditions de concurrence dans un environnement multi-utilisateur :
Également pour les techniques mises à jour à l'aide du nouveau UPSERT dans Postgres 9.5 ou plus tard :

  • SELECT ou INSERT est-il dans une fonction sujette à des conditions de concurrence ?