Fonction PostgreSQL pour le dernier ID inséré

321

Dans PostgreSQL, comment obtenir le dernier identifiant inséré dans une table?

Dans MS SQL, il y a SCOPE_IDENTITY ().

Veuillez ne pas me conseiller d'utiliser quelque chose comme ceci:

select max(id) from table
Anton
la source
pourquoi détestez-vous la fonction max? Je pense que c'est très simple. Y a-t-il un problème comme la sécurité?
jeongmin.cha
9
@ jeongmin.cha il y a un problème s'il y a d'autres opérations et plus d'insertions (opérations simultanées), cela signifie que l'id max a changé, à moins que et jusqu'à ce que vous preniez un verrou explicitement et ne le
libériez
Si le cas d'utilisation prévu consiste à utiliser le dernier ID inséré dans le cadre de la valeur d'une insertion ultérieure, consultez cette question .
Flux

Réponses:

606

( tl;dr: passez à l'option 3: INSÉRER avec RETOUR)

Rappelons que dans postgresql, il n'y a pas de concept "id" pour les tables, juste des séquences (qui sont généralement mais pas nécessairement utilisées comme valeurs par défaut pour les clés primaires de substitution, avec le pseudo-type SERIAL ).

Si vous souhaitez obtenir l'ID d'une ligne nouvellement insérée, il existe plusieurs façons:


Option 1: CURRVAL(<sequence name>);.

Par exemple:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval('persons_id_seq');

Le nom de la séquence doit être connu, c'est vraiment arbitraire; dans cet exemple, nous supposons que la table personsa une idcolonne créée avec le SERIALpseudo-type. Pour éviter de vous y fier et pour vous sentir plus propre, vous pouvez utiliser à la place pg_get_serial_sequence:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval(pg_get_serial_sequence('persons','id'));

Avertissement: currval()ne fonctionne qu'après un INSERT(qui s'est exécuté nextval()), dans la même session .


Option 2: LASTVAL();

Ceci est similaire au précédent, sauf que vous n'avez pas besoin de spécifier le nom de la séquence: il recherche la séquence modifiée la plus récente (toujours à l'intérieur de votre session, même mise en garde que ci-dessus).


Les deux CURRVALet LASTVALsont totalement sûrs simultanément. Le comportement de la séquence dans PG est conçu pour que différentes sessions n'interfèrent pas, donc il n'y a aucun risque de conditions de course (si une autre session insère une autre ligne entre mon INSERT et mon SELECT, j'obtiens toujours ma valeur correcte).

Cependant, ils ont un problème potentiel subtil. Si la base de données a un TRIGGER (ou RULE) qui, lors de l'insertion dans la personstable, fait des insertions supplémentaires dans d'autres tables ... alors LASTVALnous donnera probablement la mauvaise valeur. Le problème peut même se produire avec CURRVAL, si les insertions supplémentaires sont effectuées dans la même personstable (c'est beaucoup moins habituel, mais le risque existe toujours).


Option 3: INSERTavecRETURNING

INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;

C'est le moyen le plus propre, efficace et sûr d'obtenir l'identifiant. Il n'a aucun des risques du précédent.

Désavantages? Presque aucun: vous devrez peut-être modifier la façon dont vous appelez votre instruction INSERT (dans le pire des cas, votre couche API ou DB ne s'attend pas à ce qu'un INSERT renvoie une valeur); ce n'est pas du SQL standard (peu importe); il est disponible depuis Postgresql 8.2 (déc 2006 ...)


Conclusion: si vous le pouvez, optez pour l'option 3. Ailleurs, préférez 1.

Remarque: toutes ces méthodes sont inutiles si vous avez l'intention d'obtenir le dernier identifiant inséré globalement (pas nécessairement par votre session). Pour cela, vous devez recourir à SELECT max(id) FROM table(bien sûr, cela ne lira pas les insertions non validées d'autres transactions).

Inversement, vous ne devez jamais utiliser à la SELECT max(id) FROM tableplace l'une des 3 options ci-dessus, pour obtenir l'ID juste généré par votre INSERTinstruction, car (en dehors des performances), ce n'est pas sûr en même temps: entre votre INSERTet votre SELECTautre session peut avoir inséré un autre enregistrement.

leonbloy
la source
25
LASTVAL () peut être très mauvais, si vous ajoutez un déclencheur / règle insérant des lignes sur lui-même dans une autre table.
Kouber Saparev
3
SELECT max(id)ne fait malheureusement pas le travail non plus dès que vous commencez à supprimer des lignes.
Simon A. Eugster
2
@leonbloy Sauf si j'ai raté quelque chose, si vous avez des lignes avec des ID 1,2,3,4,5et supprimez les lignes 4 et 5, le dernier ID inséré est toujours 5, mais max()renvoie 3.
Simon A. Eugster
9
Un échantillon de la façon d'utiliser RETURNING id;pour insérer ceci dans un autre tableau serait le bienvenu!
Olivier Pons
8
Comment puis-je utiliser RETURNING idun script SQL alimenté par l' psqloutil de ligne de commande?
amoe
82

Voir la clause RETURNING de l' instruction INSERT . Fondamentalement, le INSERT se double d'une requête et vous renvoie la valeur qui a été insérée.

kwatford
la source
4
Fonctionne à partir de la version 8.2 et est la solution la meilleure et la plus rapide.
Frank Heikens
9
peut-être une explication rapide de l'utilisation de l'identifiant renvoyé?
Andrew
@Andrew, je ne suis pas sûr de comprendre la question. Vous ne savez pas comment récupérer les résultats d'une requête? Cela dépend de la langue / de la bibliothèque et devrait fonctionner de la même manière, que vous fassiez une sélection ou un insert de retour. La seule autre interprétation que je peux trouver est que vous avez réussi à récupérer l'ID de l'appel et que vous ne savez pas à quoi il sert ... dans ce cas, pourquoi l'avez-vous récupéré?
kwatford
8
@Andrew, Si vous exécutez à partir de la ligne de commande psql ceci :, il affiche insert into names (firstname, lastname) values ('john', 'smith') returning id;simplement id comme si vous exécutiez select id from names where id=$lastiddirectement. Si vous souhaitez enregistrer le retour dans une variable aa , alors insert into names (firstname, lastname) values ('john', 'smith') returning id into other_variable;si l'instruction contenant le retour est la dernière instruction d'une fonction , l'ID en cours returningde modification est renvoyé par la fonction dans son ensemble.
Alexander Bird
32

vous pouvez utiliser la clause RETURNING dans l'instruction INSERT, tout comme la suivante

wgzhao=# create table foo(id int,name text);
CREATE TABLE
wgzhao=# insert into foo values(1,'wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into foo values(3,'wgzhao') returning id;
 id 
----
  3
(1 row)

INSERT 0 1

wgzhao=# create table bar(id serial,name text);
CREATE TABLE
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  2
(1 row)

INSERT 0 
wgzhao
la source
28

La réponse de Leonbloy est assez complète. J'ajouterais seulement le cas spécial dans lequel on a besoin d'obtenir la dernière valeur insérée à partir d'une fonction PL / pgSQL où OPTION 3 ne correspond pas exactement.

Par exemple, si nous avons les tableaux suivants:

CREATE TABLE person(
   id serial,
   lastname character varying (50),
   firstname character varying (50),
   CONSTRAINT person_pk PRIMARY KEY (id)
);

CREATE TABLE client (
    id integer,
   CONSTRAINT client_pk PRIMARY KEY (id),
   CONSTRAINT fk_client_person FOREIGN KEY (id)
       REFERENCES person (id) MATCH SIMPLE
);

Si nous devons insérer un enregistrement client, nous devons nous référer à un enregistrement personne. Mais disons que nous voulons concevoir une fonction PL / pgSQL qui insère un nouvel enregistrement dans le client mais s'occupe également d'insérer le nouvel enregistrement de personne. Pour cela, nous devons utiliser une légère variation de l'OPTION 3 de leonbloy:

INSERT INTO person(lastname, firstname) 
VALUES (lastn, firstn) 
RETURNING id INTO [new_variable];

Notez qu'il existe deux clauses INTO. Par conséquent, la fonction PL / pgSQL serait définie comme:

CREATE OR REPLACE FUNCTION new_client(lastn character varying, firstn character varying)
  RETURNS integer AS
$BODY$
DECLARE
   v_id integer;
BEGIN
   -- Inserts the new person record and retrieves the last inserted id
   INSERT INTO person(lastname, firstname)
   VALUES (lastn, firstn)
   RETURNING id INTO v_id;

   -- Inserts the new client and references the inserted person
   INSERT INTO client(id) VALUES (v_id);

   -- Return the new id so we can use it in a select clause or return the new id into the user application
    RETURN v_id;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

Maintenant, nous pouvons insérer les nouvelles données en utilisant:

SELECT new_client('Smith', 'John');

ou

SELECT * FROM new_client('Smith', 'John');

Et nous obtenons l'ID nouvellement créé.

new_client
integer
----------
         1
Krauss
la source
9

Pour ceux qui ont besoin d'obtenir l'ensemble des données, vous pouvez ajouter

returning *

à la fin de votre requête pour obtenir l'objet all, y compris l'id.

emreoktem
la source
8

Voir l'exemple ci-dessous

CREATE TABLE users (
    -- make the "id" column a primary key; this also creates
    -- a UNIQUE constraint and a b+-tree index on the column
    id    SERIAL PRIMARY KEY,
    name  TEXT,
    age   INT4
);

INSERT INTO users (name, age) VALUES ('Mozart', 20);

Ensuite, pour obtenir le dernier identifiant inséré, utilisez ceci pour la table "utilisateur" nom de colonne seq "id"

SELECT currval(pg_get_serial_sequence('users', 'id'));
RINSON KE
la source
1

Essaye ça:

select nextval('my_seq_name');  // Returns next value

Si cela retourne 1 (ou quelle que soit la valeur de début de votre séquence), réinitialisez la séquence à sa valeur d'origine, en passant le faux drapeau:

select setval('my_seq_name', 1, false);

Autrement,

select setval('my_seq_name', nextValue - 1, true);

Cela restaurera la valeur de séquence à l'état d'origine et "setval" reviendra avec la valeur de séquence que vous recherchez.

Oozman
la source
1

Postgres a un mécanisme intégré pour le même, qui dans la même requête renvoie l'ID ou tout ce que vous souhaitez que la requête retourne. Voici un exemple. Considérez que vous avez créé une table qui comporte 2 colonnes colonne1 et colonne2 et que vous souhaitez que la colonne1 soit renvoyée après chaque insertion.

# create table users_table(id serial not null primary key, name character varying);
CREATE TABLE
#insert into users_table(name) VALUES ('Jon Snow') RETURNING id;
 id 
----
  1
(1 row)

# insert into users_table(name) VALUES ('Arya Stark') RETURNING id;
 id 
----
  2
(1 row)
Sandip Debnath
la source