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
detbl
si la "ligne" existe. -
Le troisième inserts CTE i la "ligne" dans
tbl
si (et seulement si) il n'existe pas, retour deid
. -
Le dernier
SELECT
renvoie l'id
. J'ai ajouté une colonnesrc
indiquant la "source" - si la "ligne" préexistait etid
vient d'un SELECT, ou la "ligne" était nouvelle et leid
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 ?