¿Cómo uso currval () en PostgreSQL para obtener la última identificación insertada?


64

Tengo una mesa:

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

Quiero la "última identificación insertada" de esa tabla, sin usar RETURNING iden insertar. Parece que hay una función CURRVAL(), pero no entiendo cómo usarla.

He intentado con:

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

pero ninguno de ellos funciona. ¿Cómo puedo usar currval()para obtener la última identificación insertada?


2
Los lectores de este problema / solución deben tener en cuenta que el uso de currval () generalmente se desaconseja, ya que la cláusula RETURNING proporciona el identificador sin la sobrecarga de la consulta adicional y sin la posibilidad de devolver el VALOR INCORRECTO (que currval hará en algunos casos de uso.)
chander

1
@chander: ¿tiene alguna referencia para ese reclamo? El uso de currval() definitivamente no se desaconseja.
a_horse_with_no_name

Tal vez sea una cuestión de opinión si se desaconseja el uso de currval, pero en ciertos casos los usuarios deben ser conscientes de que puede ofrecer un valor que no es el esperado (por lo tanto, RETORNO es la mejor opción donde sea compatible). tiene la tabla A que usa la secuencia a_seq, y la tabla B que también usa a_seq (llamando a nextval ('a_seq') para la columna PK). Suponga que también tiene un activador (a_trg) que se inserta en la tabla B AL INSERTAR en la tabla A. En ese caso, la función currval () (después de inserción en la tabla a) devolverá el número generado para el inserto en el vector B, no tabla A.
chander

Respuestas:


51

Si crea una columna como serialPostgreSQL crea automáticamente una secuencia para eso.

El nombre de la secuencia se genera automáticamente y siempre es tablename_columnname_seq, en su caso la secuencia será nombres names_id_seq.

Después de insertar en la tabla, puede llamar currval()con ese nombre de secuencia:

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=>

En lugar de codificar el nombre de la secuencia, también puede usar pg_get_serial_sequence()en su lugar:

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

De esa manera, no necesita confiar en la estrategia de nombres que utiliza Postgres.

O si no desea utilizar el nombre de la secuencia, use lastval()


Supongo que no es bueno usarlo currval()en una configuración multiusuario. Por ejemplo, en un servidor web.
Jonas

99
No, estás equivocado. currval()es "local" a su conexión actual. Por lo tanto, no hay problema para usarlo en un entorno multiusuario. Ese es todo el propósito de una secuencia.
a_horse_with_no_name

1
Esto podría romperse en el caso (bastante raro) en el que su inserción activa más inserciones en la misma tabla, ¿no?
leonbloy

1
@a_horse_with_no_name: Estaba pensando no en múltiples inserciones (eso es fácil de detectar) sino en desencadenantes (quizás desconocidos) definidos en la tabla. Pruebe, por ejemplo, esto en lo anterior: gist.github.com/anonymous/9784814
leonbloy

1
No siempre es el nombre de la tabla y la columna. Si ya hay una secuencia con ese nombre, generará una nueva secuencia concatenando o incrementando un número al final, y puede que necesite acortar el nombre si excede el límite (que parece tener 62 caracteres).
PhilHibbs

47

Esto es directamente desde Stack Overflow

Como lo señalaron @a_horse_with_no_name y @Jack Douglas, currval solo funciona con la sesión actual. Entonces, si está de acuerdo con el hecho de que el resultado podría verse afectado por una transacción no confirmada de otra sesión, y aún desea algo que funcione en todas las sesiones, puede usar esto:

SELECT last_value FROM your_sequence_name;

Use el enlace a SO para obtener más información.

Sin embargo, de la documentación de Postgres , se afirma claramente que

Es un error llamar a lastval si nextval aún no se ha llamado en la sesión actual.

Entonces, supongo que, estrictamente hablando, para usar correctamente currval o last_value para una secuencia entre sesiones, ¿debería hacer algo así?

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

Suponiendo, por supuesto, que no tendrá una inserción ni ninguna otra forma de usar el campo serial en la sesión actual.


3
No puedo pensar en una situación en la que esto sea útil.
ypercubeᵀᴹ

Me preguntaba si esta es una forma de obtener currval, si no se ha llamado a nextval en la sesión actual. ¿Alguna sugerencia entonces?
Slak

Tenía que hacer esto cuando codificaba las claves primarias para los datos de los dispositivos generados donde quería que los valores de PK que normalmente se generarían de forma incremental se determinaran con anticipación para facilitar las pruebas de los clientes. Para admitir inserciones al hacer eso en una columna que normalmente se rige por el valor predeterminado de a nextval(), debe configurar manualmente la secuencia para que coincida con el número de registros de dispositivos que insertó con las ID codificadas. Además, la forma de resolver el problema de que currval () / lastval () no esté disponible pre-nextval es simplemente SELECTen la secuencia directamente.
Peter M. Elias

@ ypercubeᵀᴹ Por el contrario, no se me ocurre ninguna buena razón para usar la respuesta "correcta" que se eligió. Esta respuesta no requiere insertar un registro en la tabla. Esto responde a la pregunta también. Nuevamente, no puedo pensar en una buena razón para NO usar esta respuesta sobre la elegida.
Henley Chiu

1
Esta respuesta no implica modificar la tabla en absoluto. El marcado lo hace. Idealmente, solo desea conocer la última ID sin hacer CUALQUIER cambio. ¿Qué pasa si este es un DB de producción? No puede simplemente insertar una fila aleatoria sin posibles percusiones. Por lo tanto, esta respuesta es más segura y correcta.
Henley Chiu

14

Usted necesita llamar nextvala esta secuencia en esta sesión antescurrval :

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

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

por lo que no puede encontrar la 'última identificación insertada' de la secuencia a menos que insertse realice en la misma sesión (una transacción puede revertirse pero la secuencia no)

como se señala en la respuesta de a_horse, create tablecon una columna de tipo serialcreará automáticamente una secuencia y la usará para generar el valor predeterminado para la columna, por lo que insertnormalmente se accede nextvalimplícitamente a:

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)

3

Entonces, hay algunos problemas con estos diversos métodos:

Currval solo obtiene el último valor generado en la sesión actual, lo cual es excelente si no tiene nada más que genere valores, pero en los casos en que podría llamar a un desencadenante y / o avanzar la secuencia más de una vez en la transacción actual, es No va a devolver el valor correcto. Eso no es un problema para el 99% de las personas, pero es algo que uno debe tener en cuenta.

La mejor manera de obtener el identificador único asignado después de una operación de inserción es usar la cláusula RETURNING. El siguiente ejemplo supone que la columna vinculada a la secuencia se llama "id":

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

Tenga en cuenta que la utilidad de la cláusula RETURNING va mucho más allá de simplemente obtener la secuencia, ya que también:

  • Valores devueltos que se usaron para la "inserción final" (después, por ejemplo, un disparador ANTES podría haber alterado los datos que se están insertando).
  • Devuelve los valores que se estaban eliminando:

    eliminar de la tabla A donde id> 100 regresando *

  • Devuelve las filas modificadas después de una ACTUALIZACIÓN:

    actualizar la tabla A conjunto X = 'y' donde blah = 'blech' regresando *

  • Use el resultado de una eliminación para una actualización:

    CON A como (eliminar * de la tabla A como ID de retorno) actualización B establecer eliminado = verdadero donde id en (seleccionar id de A);


Por supuesto, el OP dijo explícitamente que no querían usar una RETURNINGcláusula, pero no hay nada de malo en hacer que los beneficios de usarlo sean más claros para los demás.
RDFozz

Realmente solo estaba tratando de señalar las trampas de los otros métodos (en que, a menos que uno tuviera cuidado, podría devolver un valor diferente al esperado) y aclarar la mejor práctica.
Chander

1

Tuve que ejecutar una consulta a pesar de usar SQLALchemy porque no tuve éxito al usar currval.

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

Este fue un matraz de Python + proyecto postgresql.



1

Necesita GRANTusar en el esquema, así:

GRANT USAGE ON SCHEMA schema_name to user;

y

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;

1
Bienvenido a dba.se! ¡Salud en tu primer post! No parece que la publicación original esté específicamente relacionada con los permisos. ¿Quizás considere expandir su respuesta para incluir algunos detalles sobre fallas de permisos al llamar a la currval()función para que sea un poco más relevante para este hilo?
Peter Vandivier

0

En PostgreSQL 11.2 puede tratar la secuencia como una tabla que parece:

Ejemplo si tiene una secuencia llamada: 'names_id_seq'

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

Eso debería darle la última identificación insertada (4 en este caso), lo que significa que el valor actual (o el valor que debe usarse para la siguiente identificación) debe ser 5.


-2

Las diferentes versiones de PostgreSQL pueden tener diferentes funciones para obtener la identificación de secuencia actual o siguiente.

Primero, debes conocer la versión de tus Postgres. Utilizando select version (); para obtener la versión

En PostgreSQL 8.2.15, obtiene la identificación de secuencia actual utilizando select last_value from schemaName.sequence_name.

Si la declaración anterior no funciona, puede usar select currval('schemaName.sequence_name');


2
¿Alguna prueba de que diferentes versiones lo hacen de manera diferente?
dezso
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.