Comment utiliser currval () dans PostgreSQL pour obtenir le dernier identifiant inséré?

64

J'ai une table:

CREATE TABLE names (id serial, name varchar(20))

Je veux le "dernier identifiant inséré" de cette table, sans utiliser RETURNING idon insert. Il semble y avoir une fonction CURRVAL(), mais je ne comprends pas comment l’utiliser.

J'ai essayé avec:

SELECT CURRVAL() AS id FROM names_id_seq
SELECT CURRVAL('names_id_seq')
SELECT CURRVAL('names_id_seq'::regclass)

mais aucun d'entre eux ne fonctionne. Comment puis-je utiliser currval()pour obtenir le dernier identifiant inséré?

Jonas
la source
2
Les lecteurs de ce problème / de cette solution doivent savoir que l'utilisation de currval () est généralement déconseillée, car la clause RETURNING fournit l'identifiant sans la surcharge de la requête supplémentaire et sans la possibilité de renvoyer la valeur WRONG VALUE (ce que fera currval dans quelques cas d'utilisation.)
chander
1
@chander: avez-vous des références pour cette demande? L'utilisation de currval() n'est certainement pas découragée.
a_horse_with_no_name
C’est peut-être une question d’opinion quant à savoir si l’utilisation de currval est découragée, mais dans certains cas, les utilisateurs doivent être conscients qu’il peut fournir une valeur qui ne correspond pas à vos attentes (le retour est donc le meilleur choix possible). avoir la table A qui utilise la séquence a_seq et la table B qui utilise également a_seq (appelant nextval ('a_seq') pour la colonne PK). Supposons que vous ayez également un déclencheur (a_trg) qui insère dans la table B ON INSERT dans la table A. Dans ce cas, la fonction currval () (après insertion dans la table A) renverra le numéro généré pour l'insertion dans la table B, pas la table A.
chander

Réponses:

51

Si vous créez une colonne en tant que serialPostgreSQL ™ crée automatiquement une séquence pour cela.

Le nom de la séquence est généré automatiquement et est toujours nom_table_nom_colonne_seq. Dans votre cas, la séquence sera nommée names_id_seq.

Après avoir inséré dans la table, vous pouvez appeler currval()avec ce nom de séquence:

postgres=> CREATE TABLE names in schema_name (id serial, name varchar(20));
CREATE TABLE
postgres=> insert into names (name) values ('Arthur Dent');
INSERT 0 1
postgres=> select currval('names_id_seq');
 currval
---------
       1
(1 row)
postgres=>

Au lieu de coder en dur le nom de la séquence, vous pouvez également utiliser pg_get_serial_sequence():

select currval(pg_get_serial_sequence('names', 'id'));

De cette façon, vous n’avez pas besoin de vous fier à la stratégie de nommage utilisée par Postgres.

Ou si vous ne souhaitez pas utiliser le nom de la séquence, utilisez lastval()

un cheval sans nom
la source
Je suppose que ce n'est pas bon d'utiliser currval()dans une configuration multi-utilisateur. Par exemple, sur un serveur Web.
Jonas
9
Non, vous vous trompez. currval()est "local" à votre connexion actuelle. Il n'y a donc aucun problème à l'utiliser dans un environnement multi-utilisateur. C'est tout le but d'une séquence.
a_horse_with_no_name
1
Cela pourrait casser dans le cas (assez rare) dans lequel votre insertion déclenche plusieurs insertions dans la même table, non?
leonbloy
1
@a_horse_with_no_name: Je ne pensais pas sur plusieurs insertions (facile à repérer) mais sur des déclencheurs (peut-être inconnus) définis sur la table. Essayez par exemple ceci ci-dessus: gist.github.com/anonymous/9784814
leonbloy
1
Ce n'est pas TOUJOURS le nom de la table et de la colonne. S'il existe déjà une séquence portant ce nom, une nouvelle séquence sera générée en concaténant ou en incrémentant un nombre à la fin. Il sera peut-être nécessaire de raccourcir le nom s'il dépasse la limite (qui semble comporter 62 caractères).
PhilHibbs
47

Ceci vient directement de Stack Overflow

Comme l'ont souligné @a_horse_with_no_name et @Jack Douglas, currval ne fonctionne qu'avec la session en cours. Donc, si vous êtes d'accord avec le fait que le résultat peut être affecté par une transaction non validée d'une autre session et que vous souhaitez toujours quelque chose qui fonctionnera sur plusieurs sessions, vous pouvez utiliser ceci:

SELECT last_value FROM your_sequence_name;

Utilisez le lien vers SO pour plus d'informations.

D'après la documentation de Postgres , il est clairement indiqué que

C'est une erreur d'appeler lastval si nextval n'a pas encore été appelé dans la session en cours.

Donc, je suppose que, à proprement parler, pour utiliser correctement currval ou last_value pour une séquence sur plusieurs sessions, vous auriez besoin de faire quelque chose comme ça?

SELECT setval('serial_id_seq',nextval('serial_id_seq')-1);

En supposant, bien sûr, que vous n'ayez pas d'insertion ou de toute autre manière d'utiliser le champ série dans la session en cours.

Slak
la source
3
Je ne peux pas penser à une situation où cela serait utile.
Ypercubeᵀᴹ
Je me demandais simplement si c’est un moyen d’obtenir Currval, si nextval n’a pas été appelé dans la session en cours. Des suggestions alors?
Slak
Je devais le faire lorsque je codais en dur pour les données de fixture générées pour lesquelles je souhaitais que les valeurs PK qui seraient normalement générées de manière incrémentielle soient déterminées à l'avance pour faciliter les tests sur les clients. Afin de prendre en charge les insertions lorsque vous faites cela sur une colonne qui est normalement régie par la valeur par défaut de a, nextval()vous devez ensuite définir manuellement la séquence pour qu'elle corresponde au nombre d'enregistrements de fixture que vous avez insérés avec les ID codés en dur. De plus, le moyen de résoudre le problème de currval () / lastval () n'étant pas disponible pré-nextval est uniquement SELECTsur la séquence directement.
Peter M. Elias
@ ypercubeᵀᴹ Au contraire, je ne vois aucune bonne raison d'utiliser la réponse "correcte" choisie. Cette réponse ne nécessite pas l'insertion d'un enregistrement dans la table. Cela répond également à la question. Encore une fois, je ne vois aucune bonne raison de NE PAS utiliser cette réponse par rapport à celle choisie.
Henley Chiu
1
Cette réponse n’implique aucune modification de la table. Le coché fait. Idéalement, vous voulez simplement connaître le dernier ID sans apporter AUCUN changement. Que se passe-t-il s'il s'agit d'une base de données de production? Vous ne pouvez pas simplement insérer une ligne aléatoire sans percussions possibles. Ainsi, cette réponse est plus sûre et correcte.
Henley Chiu
14

Vous devez appeler nextvalpour cette séquence dans cette session avantcurrval :

create sequence serial;
select nextval('serial');
 nextval
---------
       1
(1 row)

select currval('serial');
 currval
---------
       1
(1 row)

vous ne pouvez donc pas trouver le "dernier identifiant inséré" dans la séquence à moins que l'opération insertsoit effectuée dans la même session (une transaction peut être annulée mais la séquence ne le sera pas)

comme indiqué dans la réponse de a_horse, create tableune colonne de type serialcréera automatiquement une séquence et l'utilisera pour générer la valeur par défaut de la colonne, de sorte qu'un utilisateur insertaccède normalement de nextvalmanière implicite:

create table my_table(id serial);
NOTICE:  CREATE TABLE will create implicit sequence "my_table_id_seq" for 
         serial column "my_table.id"

\d my_table
                          Table "stack.my_table"
 Column |  Type   |                       Modifiers
--------+---------+-------------------------------------------------------
 id     | integer | not null default nextval('my_table_id_seq'::regclass)

insert into my_table default values;
select currval('my_table_id_seq');
 currval
---------
       1
(1 row)
Jack Douglas
la source
3

Donc, il y a quelques problèmes avec ces différentes méthodes:

Currval n'obtient que la dernière valeur générée dans la session en cours - ce qui est très bien si vous n'avez rien d'autre qui génère des valeurs, mais dans les cas où vous pouvez appeler un déclencheur et / ou faire avancer la séquence plus d'une fois dans la transaction en cours, c'est ne va pas retourner la valeur correcte. Ce n'est pas un problème pour 99% des gens là-bas - mais c'est quelque chose que l'on devrait prendre en considération.

Le meilleur moyen d'obtenir l'identificateur unique attribué après une opération d'insertion consiste à utiliser la clause RETURNING. L'exemple ci-dessous suppose que la colonne liée à la séquence s'appelle "id":

insert into table A (cola,colb,colc) values ('val1','val2','val3') returning id;

Notez que l’utilité de la clause RETURNING va bien au-delà de l’obtention de la séquence, car elle aura également:

  • Renvoie les valeurs utilisées pour "l'insertion finale" (après, par exemple, un déclencheur BEFORE pourrait avoir modifié les données insérées.)
  • Renvoie les valeurs en cours de suppression:

    supprimer de la table A où id> 100 retournant *

  • Renvoie les lignes modifiées après un UPDATE:

    mettre à jour la table A set X = 'y' où blah = 'blech' retournant *

  • Utilisez le résultat d'une suppression pour une mise à jour:

    WITH A en tant que (supprimer * de la table A en tant qu'identifiant renvoyé) update B set delete = true où id in (sélectionnez id de A);

chander
la source
Bien sûr, le PO a explicitement dit qu'il ne souhaitait pas utiliser une RETURNINGclause - mais rien de mal à rendre les avantages de l'utiliser plus clair pour les autres.
RDFozz
J'essayais simplement de souligner les pièges des différentes autres méthodes (sauf si une personne faisait bien attention de ne pas pouvoir renvoyer une valeur autre que celle attendue) - et de clarifier les meilleures pratiques.
chander
1

J'ai dû exécuter une requête malgré l'utilisation de SQLALchemy car je n'avais pas réussi à utiliser currval.

nextId = db.session.execute("select last_value from <table>_seq").fetchone()[0] + 1

Il s’agissait d’un projet flacon en python + postgresql.

Jalal
la source
1

Vous devez GRANTutiliser le schéma, comme ceci:

GRANT USAGE ON SCHEMA schema_name to user;

et

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;
AMML
la source
1
Bienvenue sur dba.se! Bravo pour votre premier post! Il ne semble pas que l'original ait spécifiquement trait aux autorisations. Envisagez peut-être d'étendre votre réponse pour inclure des détails sur les échecs d'autorisations lorsque vous appelez la currval()fonction pour la rendre un peu plus pertinente pour ce fil?
Peter Vandivier
0

Dans PostgreSQL 11.2, vous pouvez traiter la séquence comme une table, il semble:

Exemple si vous avez une séquence nommée: 'names_id_seq'

select * from names_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          4 |      32 | t
(1 row)

Cela devrait vous donner le dernier identifiant inséré (4 dans ce cas), ce qui signifie que la valeur actuelle (ou la valeur à utiliser pour l'identifiant suivant) devrait être 5.

scifisamurai
la source
-2

Différentes versions de PostgreSQL peuvent avoir différentes fonctions pour obtenir l'identifiant de séquence actuel ou suivant.

Tout d’abord, vous devez connaître la version de votre Postgres. Utilisation de select version (); pour obtenir la version.

Dans PostgreSQL 8.2.15, vous obtenez l'ID de la séquence actuelle en utilisant select last_value from schemaName.sequence_name.

Si la déclaration ci-dessus ne fonctionne pas, vous pouvez utiliser select currval('schemaName.sequence_name');

Robin
la source
2
Toute preuve pour les différentes versions faisant différemment?
dezso