Come uso currval () in PostgreSQL per ottenere l'ultimo ID inserito?


64

Ho un tavolo:

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

Voglio "l'ultimo ID inserito" da quella tabella, senza usare RETURNING idsu insert. Sembra che ci sia una funzione CURRVAL(), ma non capisco come usarla.

Ho provato con:

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

ma nessuno di loro funziona. Come posso usare currval()per ottenere l'ultimo ID inserito?


2
I lettori di questo problema / soluzione dovrebbero essere consapevoli del fatto che l'uso di currval () è generalmente sconsigliato poiché la clausola RETURNING fornisce l'identificatore senza l'overhead della query aggiuntiva e senza la possibilità di restituire il valore errato (che valuterà in alcuni casi d'uso.)
modifica il

1
@chander: hai qualche riferimento per quel reclamo? L'uso di currval() sicuramente non è scoraggiato.
a_horse_with_no_name

Forse è una questione di opinione sul fatto se l'uso di currval sia scoraggiato, ma in alcuni casi gli utenti dovrebbero essere consapevoli del fatto che potrebbe fornire un valore che non è quello che ti aspetti (rendendo così il RITORNO la scelta migliore dove supportato). avere la tabella A che utilizza la sequenza a_seq e la tabella B che utilizza anche a_seq (chiamando nextval ('a_seq') per la colonna PK.) Supponiamo di avere anche un trigger (a_trg) che si inserisce nella tabella B IN INSERISCI nella tabella A. In in tal caso la funzione currval () (dopo l'inserimento nella tabella A) restituirà il numero generato per l'inserimento nella tabella B, non nella tabella A.
chander

Risposte:


51

Se crei una colonna come serialPostgreSQL crea automaticamente una sequenza per quello.

Il nome della sequenza è generato automaticamente ed è sempre tablename_columnname_seq, nel tuo caso la sequenza sarà nominata names_id_seq.

Dopo aver inserito nella tabella, puoi chiamare currval()con quel nome di sequenza:

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

Invece di codificare il nome della sequenza, puoi anche usare pg_get_serial_sequence():

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

In questo modo non è necessario fare affidamento sulla strategia di denominazione utilizzata da Postgres.

O se non vuoi usare affatto il nome della sequenza, usa lastval()


Immagino non sia buono da usare currval()in una configurazione multiutente. Ad esempio su un server web.
Jonas,

9
No ti sbagli. currval()è "locale" per la tua connessione corrente. Quindi non ci sono problemi ad usarlo in un ambiente multiutente. Questo è lo scopo di una sequenza.
a_horse_with_no_name,

1
Questo potrebbe rompersi nel caso (piuttosto raro) in cui il tuo inserto attiva più inserti nella stessa tabella, no?
leonbloy,

1
@a_horse_with_no_name: stavo pensando non su più inserti (è facile da individuare) ma su trigger (forse sconosciuti) definiti sul tavolo. Prova ad esempio su quanto sopra: gist.github.com/anonymous/9784814
leonbloy

1
Non è SEMPRE il nome della tabella e della colonna. Se esiste già una sequenza con quel nome, genererà una nuova sequenza concatenando o incrementando un numero alla fine e potrebbe essere necessario abbreviare il nome se supera il limite (che sembra essere di 62 caratteri).
PhilHibbs,

47

Questo è direttamente dallo Stack Overflow

Come è stato sottolineato da @a_horse_with_no_name e @Jack Douglas, currval funziona solo con la sessione corrente. Quindi, se sei d'accordo con il fatto che il risultato potrebbe essere influenzato da una transazione non impegnata di un'altra sessione e desideri comunque qualcosa che funzioni tra le sessioni, puoi usare questo:

SELECT last_value FROM your_sequence_name;

Utilizzare il collegamento a SO per ulteriori informazioni.

Dalla documentazione di Postgres, tuttavia, si afferma chiaramente che

È un errore chiamare lastval se nextval non è stato ancora chiamato nella sessione corrente.

Quindi immagino che, per usare correttamente currval o last_value per una sequenza tra sessioni, dovresti fare qualcosa del genere?

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

Supponendo, ovviamente, che non si avrà un inserto o qualsiasi altro modo di utilizzare il campo seriale nella sessione corrente.


3
Non riesco a pensare a una situazione in cui ciò sarebbe utile.
ypercubeᵀᴹ

Mi stavo solo chiedendo se questo è un modo per ottenere currval, se nextval non è stato chiamato nella sessione corrente. Qualche suggerimento allora?
Slak,

Ho dovuto farlo quando stavo codificando le chiavi primarie per i dati del dispositivo generato dove volevo che i valori PK che normalmente sarebbero stati generati in modo incrementale fossero determinati in anticipo per rendere più facile il test del client. Per supportare gli inserimenti quando lo si fa su una colonna che è normalmente governata dal valore predefinito da a, nextval()è necessario impostare manualmente la sequenza in modo che corrisponda al numero di record di dispositivi inseriti con gli ID codificati. Inoltre, il modo di risolvere il problema per cui currval () / lastval () non è disponibile pre-nextval è semplicemente SELECTsulla sequenza direttamente.
Peter M. Elias,

@ ypercubeᵀᴹ Al contrario, non riesco a pensare a nessuna buona ragione per usare la risposta "corretta" che è stata scelta. Questa risposta non richiede l'inserimento di un record nella tabella. Questo risponde anche alla domanda. Ancora una volta, non riesco a pensare a nessuna buona ragione per NON usare questa risposta su quella scelta.
Henley Chiu,

1
Questa risposta non implica affatto la modifica della tabella. Quello selezionato lo fa. Idealmente, vuoi solo conoscere l'ultimo ID senza apportare NESSUNA modifica. Cosa succede se si tratta di un DB di produzione? Non puoi semplicemente inserire una riga casuale senza possibili percussioni. Quindi questa risposta è più sicura e corretta.
Henley Chiu,

14

È necessario chiamare nextvalper questa sequenza in questa sessione prima dicurrval :

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

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

quindi non è possibile trovare l '"ultimo ID inserito" dalla sequenza a meno che non insertvenga eseguito nella stessa sessione (una transazione potrebbe eseguire il rollback ma la sequenza no)

come sottolineato nella risposta di a_horse, create tablecon una colonna di tipo serialcreerà automaticamente una sequenza e la userà per generare il valore predefinito per la colonna, quindi insertnormalmente accede 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)

3

Quindi ci sono alcuni problemi con questi vari metodi:

Currval ottiene solo l'ultimo valore generato nella sessione corrente, il che è fantastico se non hai altro che genera valori, ma nei casi in cui potresti chiamare un trigger e / o avere la sequenza avanzata più di una volta nella transazione corrente è non restituirà il valore corretto. Questo non è un problema per il 99% delle persone là fuori - ma è qualcosa che si dovrebbe prendere in considerazione.

Il modo migliore per ottenere l'identificatore univoco assegnato dopo un'operazione di inserimento è utilizzare la clausola RETURNING. L'esempio seguente presuppone che la colonna legata alla sequenza sia denominata "id":

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

Si noti che l'utilità della clausola RETURNING va ben oltre il semplice ottenere la sequenza, poiché inoltre:

  • Restituisce i valori utilizzati per l '"inserimento finale" (dopo, ad esempio, un trigger PRIMA potrebbe aver modificato i dati da inserire.)
  • Restituisce i valori che venivano eliminati:

    elimina dalla tabella A dove id> 100 restituisce *

  • Restituisce le righe modificate dopo un AGGIORNAMENTO:

    aggiorna la tabella A set X = 'y' dove blah = 'blech' restituisce *

  • Usa il risultato di un'eliminazione per un aggiornamento:

    CON A as (elimina * dalla tabella A come ID di ritorno) aggiorna B set delete = true dove id in (seleziona id da A);


Ovviamente, il PO ha esplicitamente affermato di non voler utilizzare una RETURNINGclausola, ma non c'è nulla di sbagliato nel rendere più chiari i vantaggi di usarla per gli altri.
RDFozz,

Stavo davvero solo cercando di sottolineare le insidie ​​dei vari altri metodi (in quanto a meno che uno non fosse attento poteva restituire un valore diverso da quello atteso) - e chiarire la migliore pratica.
chander

1

Ho dovuto eseguire una query nonostante l'utilizzo di SQLALchemy perché non sono riuscito a utilizzare currval.

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

Questo era un pallone in pitone + progetto postgresql.



1

È necessario GRANTutilizzare sullo schema, in questo modo:

GRANT USAGE ON SCHEMA schema_name to user;

e

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;

1
Benvenuto su dba.se! Saluti al tuo primo post! Non sembra che il Post originale riguardi specificamente le autorizzazioni. Forse prendere in considerazione l'espansione della risposta per includere alcuni dettagli sugli errori delle autorizzazioni quando si chiama la currval()funzione per renderla un po 'più rilevante per questo thread?
Peter Vandivier,

0

In PostgreSQL 11.2 puoi trattare la sequenza come una tabella sembra:

Esempio se hai una sequenza chiamata: 'names_id_seq'

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

Questo dovrebbe darti l'ultimo ID inserito (4 in questo caso), il che significa che il valore corrente (o il valore che dovrebbe essere usato per l'id successivo) dovrebbe essere 5.


-2

Versioni diverse di PostgreSQL possono avere funzioni diverse per ottenere l'id della sequenza corrente o successiva.

Innanzitutto, devi conoscere la versione di Postgres. Utilizzando select version (); per ottenere la versione.

In PostgreSQL 8.2.15, si ottiene l'id corrente della sequenza usando select last_value from schemaName.sequence_name.

Se l'istruzione precedente non funziona, è possibile utilizzare select currval('schemaName.sequence_name');


2
Qualche prova per versioni diverse che lo fanno diversamente?
dezso,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.