Como uso currval () no PostgreSQL para obter o último ID inserido?

64

Eu tenho uma mesa:

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

Eu quero o "último ID inserido" dessa tabela, sem usar RETURNING idna inserção. Parece haver uma função CURRVAL(), mas não entendo como usá-la.

Eu tentei com:

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

mas nenhum deles funciona. Como posso usar currval()para obter o último ID inserido?

Jonas
fonte
2
Os leitores deste problema / solução devem estar cientes de que o uso de currval () geralmente é desencorajado, pois a cláusula RETURNING fornece o identificador sem a sobrecarga da consulta adicional e sem a possibilidade de retornar o WRONG VALUE (que currval fará em alguns casos de uso.)
chander
11
@ chander: você tem alguma referência para essa reivindicação? currval() Definitivamente, o uso de não é desencorajado.
a_horse_with_no_name
Talvez seja uma questão de opinião se o uso do currval é desencorajado, mas em certos casos os usuários devem estar cientes de que ele pode oferecer um valor que não é o que você espera (tornando RETURNING a melhor escolha quando suportado.) Suponha que você tenha a tabela A que usa a sequência a_seq e a tabela B que também usa a_seq (chamando nextval ('a_seq') para a coluna PK.) Suponha que você também tenha um gatilho (a_trg) que seja inserido na tabela B ON INSERT na tabela A. Nesse caso, a função currval () (após a inserção na tabela A) retornará o número gerado para a inserção na tabela B, não na tabela A.
chander

Respostas:

51

Se você criar uma coluna, o serialPostgreSQL criará automaticamente uma sequência para isso.

O nome da sequência é gerado automaticamente e sempre é tablename_columnname_seq; no seu caso, a sequência será nomes names_id_seq.

Depois de inserir na tabela, você pode chamar currval()com esse nome de sequência:

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

Em vez de codificar o nome da sequência, você também pode usar pg_get_serial_sequence():

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

Dessa forma, você não precisa confiar na estratégia de nomes que o Postgres usa.

Ou, se você não quiser usar o nome da sequência, use lastval()

um cavalo sem nome
fonte
Eu acho que não é bom usar currval()em uma configuração multiusuário. Por exemplo, em um servidor da web.
Jonas
9
Não, você está enganado. currval()é "local" para sua conexão atual. Portanto, não há problema em usá-lo em um ambiente multiusuário. Esse é o objetivo de uma sequência.
a_horse_with_no_name 13/06
11
Isso pode ocorrer no caso (bastante raro) em que sua inserção aciona mais inserções na mesma tabela, não?
leonbloy
11
@a_horse_with_no_name: Eu estava pensando não em várias inserções (que são fáceis de identificar), mas em gatilhos (talvez desconhecidos) definidos na tabela. Tente, por exemplo, isso no acima: gist.github.com/anonymous/9784814
leonbloy
11
SEMPRE não é o nome da tabela e da coluna. Se já houver uma sequência com esse nome, ela gerará uma nova sequência concatenando ou incrementando um número no final e talvez seja necessário abreviá-lo se exceder o limite (que parece ter 62 caracteres).
21416 PhilHibbs
47

Isso é direto do Stack Overflow

Como foi apontado por @a_horse_with_no_name e @Jack Douglas, o currval funciona apenas com a sessão atual. Portanto, se você concorda com o fato de que o resultado pode ser afetado por uma transação não confirmada de outra sessão e ainda deseja algo que funcione entre as sessões, você pode usar o seguinte:

SELECT last_value FROM your_sequence_name;

Use o link para SO para obter mais informações.

A partir da documentação do Postgres , é claramente afirmado que

É um erro chamar lastval se nextval ainda não tiver sido chamado na sessão atual.

Então, eu acho que, estritamente falando, para usar corretamente currval ou last_value para uma sequência entre sessões, você precisaria fazer algo assim?

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

Supondo, é claro, que você não terá uma inserção ou qualquer outra maneira de usar o campo serial na sessão atual.

Slak
fonte
3
Não consigo pensar em uma situação em que isso seria útil.
usar o seguinte código
Eu estava pensando se esta é uma maneira de obter currval, se nextval não tiver sido chamado na sessão atual. Alguma sugestão então?
Slak 02/10/1914
Eu precisava fazer isso quando codificava as chaves primárias para os dados de fixação gerados, onde queria que os valores de PK que normalmente seriam gerados de forma incremental fossem determinados com antecedência, para facilitar o teste do cliente. Para oferecer suporte a inserções ao fazer isso em uma coluna que normalmente é governada pelo valor padrão de um, nextval()é necessário definir manualmente a sequência para corresponder ao número de registros de fixação que você inseriu com os IDs codificados. Além disso, a maneira de resolver o problema de currval () / lastval () não estar disponível pré-nextval é apenas SELECTna sequência diretamente.
Peter M. Elias
@ ypercubeᵀᴹ Pelo contrário, não consigo pensar em nenhuma boa razão para usar a resposta "correta" que foi escolhida. Esta resposta não requer a inserção de um registro na tabela. Isso responde à pergunta também. Novamente, não consigo pensar em uma boa razão para NÃO usar essa resposta sobre a escolhida.
Henley Chiu
11
Esta resposta não envolve a modificação da tabela. O verificado faz. Idealmente, você só quer saber o último ID sem fazer nenhuma alteração. E se este for um banco de dados de produção? Você não pode simplesmente inserir uma linha aleatória sem possíveis percussões. Portanto, essa resposta é mais segura e correta.
Henley Chiu
14

Você precisa chamar nextvalessa sequência nesta sessão antes decurrval :

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

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

portanto, você não pode encontrar o 'último ID inserido' da sequência, a menos que isso insertseja feito na mesma sessão (uma transação pode reverter, mas a sequência não)

como apontado na resposta de a_horse, create tablecom uma coluna do tipo serialcriará automaticamente uma sequência e a usará para gerar o valor padrão da coluna; portanto, um insertnormalmente acessa nextvalimplicitamente:

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
fonte
3

Portanto, existem alguns problemas com esses vários métodos:

Currval obtém apenas o último valor gerado na sessão atual - o que é ótimo se você não tiver mais nada gerando valores, mas nos casos em que você pode chamar um gatilho e / ou ter a sequência avançada mais de uma vez na transação atual, é não retornará o valor correto. Isso não é um problema para 99% das pessoas por aí - mas é algo que se deve levar em consideração.

A melhor maneira de obter o identificador exclusivo atribuído após uma operação de inserção é usando a cláusula RETURNING. O exemplo abaixo assume que a coluna vinculada à sequência é chamada "id":

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

Observe que a utilidade da cláusula RETURNING vai muito além de apenas obter a sequência, pois ela também:

  • Retornar valores que foram usados ​​para a "inserção final" (depois, por exemplo, um gatilho BEFORE pode ter alterado os dados que estão sendo inseridos.)
  • Retorne os valores que estavam sendo excluídos:

    excluir da tabela A onde id> 100 retornando *

  • Retorne as linhas modificadas após um UPDATE:

    atualizar tabela Um conjunto X = 'y' onde blah = 'blech' retornando *

  • Use o resultado de uma exclusão para uma atualização:

    WITH A como (excluir * da tabela A como id de retorno) atualização B set delete = true onde id (selecione id de A);

lustre
fonte
Obviamente, o OP disse explicitamente que não queria usar uma RETURNINGcláusula - mas não há nada errado em tornar os benefícios de usá-la mais clara para os outros.
RDFozz
Eu estava realmente apenas tentando apontar as armadilhas dos vários outros métodos (em que, a menos que se tenha cuidado, pode retornar um valor diferente do esperado) - e esclarecer as melhores práticas.
chander
1

Eu tive que executar uma consulta, apesar de usar SQLALchemy, porque não tive sucesso usando currval.

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

Este foi um projeto python flask + postgresql.

Jalal
fonte
1

Você precisa GRANTusar no esquema, assim:

GRANT USAGE ON SCHEMA schema_name to user;

e

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;
AMML
fonte
11
Bem-vindo ao dba.se! Cheers em seu primeiro post! Não parece que a postagem original seja especificamente sobre permissões. Talvez considere expandir sua resposta para incluir algumas especificidades sobre falhas de permissão ao chamar a currval()função para torná-la um pouco mais relevante para esse segmento.
Peter Vandivier 12/12/19
0

No PostgreSQL 11.2, você pode tratar a sequência como uma tabela:

Exemplo se você tiver uma sequência chamada: 'names_id_seq'

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

Isso deve fornecer o último ID inserido (4 neste caso), o que significa que o valor atual (ou o valor que deve ser usado para o próximo ID) deve ser 5.

scifisamurai
fonte
-2

Versões diferentes do PostgreSQL podem ter funções diferentes para obter o ID da sequência atual ou a próxima.

Primeiro, você precisa conhecer a versão do seu Postgres. Usando a versão selecionada (); para obter a versão.

No PostgreSQL 8.2.15, você obtém o ID da sequência atual usando select last_value from schemaName.sequence_name.

Se a declaração acima não funcionar, você pode usar select currval('schemaName.sequence_name');

Robin
fonte
2
Alguma prova para diferentes versões fazendo diferente?
Dezso