Jak korzystać z currval () w PostgreSQL, aby uzyskać ostatni wstawiony identyfikator?

64

Mam stolik:

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

Chcę „ostatni wstawiony identyfikator” z tej tabeli, bez użycia RETURNING idwstawiania. Wygląda na to, że jest jakaś funkcja CURRVAL(), ale nie rozumiem, jak jej używać.

Próbowałem z:

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

ale żaden z nich nie działa. Jak mogę użyć, currval()aby uzyskać ostatni wstawiony identyfikator?

Jonas
źródło
2
Czytelnicy tego problemu / rozwiązania powinni być świadomi, że korzystanie z currval () jest ogólnie odradzane, ponieważ klauzula RETURNING zapewnia identyfikator bez narzutu dodatkowego zapytania i bez możliwości zwrócenia NIEPRAWIDŁOWEJ WARTOŚCI (co currval zrobi w niektóre przypadki użycia).
chander
1
@chander: czy masz jakieś odniesienia do tego roszczenia? Korzystanie z currval() zdecydowanie nie jest odradzane.
a_horse_w_no_name
Być może jest to kwestia opinii, czy korzystanie z currval jest odradzane, ale w niektórych przypadkach użytkownicy powinni być świadomi, że może on dostarczyć wartość, która nie jest oczekiwana (dzięki czemu POWRÓT jest lepszym wyborem, jeśli jest obsługiwany.) Załóżmy, że mają tabelę A, która wykorzystuje sekwencję a_seq, oraz tabelę B, która również używa a_seq (wywołanie nextval ('a_seq') dla kolumny PK). Załóżmy, że masz również wyzwalacz (a_trg), który wstawia do tabeli B ON INSERT do tabeli A. w takim przypadku funkcja currval () (po wstawieniu do tabeli A) zwróci liczbę wygenerowaną dla wstawki do tabeli B, a nie tabeli A.
Chander

Odpowiedzi:

51

Jeśli utworzysz kolumnę, ponieważ serialPostgreSQL automatycznie utworzy sekwencję dla tego.

Nazwa sekwencji jest generowana automatycznie i zawsze jest tablename_columnname_seq, w twoim przypadku sekwencją będą nazwy names_id_seq.

Po wstawieniu do tabeli możesz wywołać currval()tę nazwę sekwencji:

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

Zamiast na stałe wpisywać nazwę sekwencji, możesz również użyć pg_get_serial_sequence():

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

W ten sposób nie musisz polegać na strategii nazewnictwa używanej przez Postgres.

Lub jeśli nie chcesz w ogóle używać nazwy sekwencji, użyj lastval()

koń bez imienia
źródło
Myślę, że nie jest dobrze używać currval()w konfiguracji dla wielu użytkowników. Np. Na serwerze internetowym.
Jonas
9
Nie, mylisz się. currval()jest „lokalny” dla bieżącego połączenia. Nie ma więc problemu z użyciem go w środowisku wielu użytkowników. To jest cały cel sekwencji.
a_horse_w_no_name
1
Może to zepsuć się w (dość rzadkim) przypadku, w którym twoja wkładka wyzwala więcej wstawek w tej samej tabeli, prawda?
leonbloy
1
@ a_horse_w_na_nazwie: Myślałem nie o wielu wstawkach (to łatwo zauważyć), ale o (być może nieznanych) wyzwalaczach zdefiniowanych w tabeli. Spróbuj na przykład tego na powyższym: gist.github.com/anonymous/9784814
leonbloy
1
To NIE ZAWSZE nazwa tabeli i kolumny. Jeśli istnieje już sekwencja o tej nazwie, wygeneruje nową sekwencję, łącząc lub zwiększając liczbę na końcu, i może być konieczne skrócenie nazwy, jeśli przekroczy ona limit (który wydaje się mieć 62 znaki).
PhilHibbs
47

Jest to prosto z Przepełnienia stosu

Jak zauważyli @a_horse_w_nazwie i @Jack Douglas, currval działa tylko z bieżącą sesją. Jeśli więc zgadzasz się z faktem, że na wynik może mieć wpływ niezaangażowana transakcja innej sesji, a nadal chcesz czegoś, co będzie działać między sesjami, możesz użyć tego:

SELECT last_value FROM your_sequence_name;

Aby uzyskać więcej informacji, skorzystaj z łącza do SO.

Z dokumentacji PostgreSQL chociaż, to jest wyraźnie powiedziane, że

Błędem jest wywołanie lastval, jeśli nextval nie został jeszcze wywołany w bieżącej sesji.

Myślę więc, że mówiąc ściśle, aby właściwie użyć currval lub last_value dla sekwencji między sesjami, musisz zrobić coś takiego?

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

Zakładając oczywiście, że nie będziesz mieć wstawki ani żadnego innego sposobu używania pola szeregowego w bieżącej sesji.

Slak
źródło
3
Nie mogę wymyślić sytuacji, w której byłoby to przydatne.
ypercubeᵀᴹ
Zastanawiałem się tylko, czy jest to sposób na uzyskanie currval, czy nextval nie został wywołany w bieżącej sesji. Jakieś sugestie?
Slak
Musiałem to zrobić, kiedy kodowałem klucze podstawowe dla generowanych danych urządzeń, gdzie chciałem, aby wartości PK, które normalnie byłyby generowane przyrostowo, były ustalane z wyprzedzeniem, aby ułatwić testowanie przez klienta. Aby wesprzeć wstawianie podczas wykonywania tej operacji w kolumnie, która zwykle podlega domyślnej wartości z a nextval(), musisz ręcznie ustawić sekwencję, aby dopasować liczbę wstawionych rekordów urządzeń z zakodowanymi identyfikatorami. Ponadto sposobem rozwiązania problemu niedostępności currval () / lastval () przed-nextval jest po prostu SELECTbezpośrednio na sekwencji.
Peter M. Elias,
@ ypercubeᵀᴹ Przeciwnie, nie mogę wymyślić żadnego dobrego powodu, aby użyć „poprawnej” odpowiedzi, która została wybrana. Ta odpowiedź nie wymaga wstawienia rekordu do tabeli. To także odpowiada na pytanie. Ponownie nie mogę znaleźć żadnego dobrego powodu, by NIE używać tej odpowiedzi w stosunku do wybranej.
Henley Chiu,
1
Ta odpowiedź w ogóle nie wymaga modyfikacji tabeli. Zaznaczony robi. Idealnie jest, jeśli chcesz znać ostatni identyfikator bez dokonywania ŻADNYCH zmian. Co jeśli jest to produkcyjna baza danych? Nie można po prostu wstawić losowego wiersza bez możliwych instrumentów perkusyjnych. Dlatego ta odpowiedź jest bezpieczniejsza i poprawna.
Henley Chiu
14

Musisz zadzwonić nextvalpo tę sekwencję w tej sesji przedcurrval :

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

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

więc nie można znaleźć „ostatniego wstawionego identyfikatora” z sekwencji, chyba że insertzostanie to zrobione w tej samej sesji (transakcja może zostać wycofana, ale sekwencja nie zostanie)

jak wskazano w odpowiedzi konia, create tablekolumna typu serialautomatycznie utworzy sekwencję i użyje jej do wygenerowania domyślnej wartości dla kolumny, więc insertnormalnie uzyskuje dostęp nextvalpośredni:

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
źródło
3

Istnieją więc problemy z tymi różnymi metodami:

Currval pobiera tylko ostatnią wartość wygenerowaną w bieżącej sesji - co jest świetne, jeśli nie masz żadnych innych wartości generujących, ale w przypadkach, w których możesz wywołać wyzwalacz i / lub ustawić sekwencję więcej niż raz w bieżącej transakcji, nie zwróci prawidłowej wartości. To nie jest problem dla 99% ludzi, ale należy to wziąć pod uwagę.

Najlepszym sposobem na przypisanie unikalnego identyfikatora po operacji wstawienia jest użycie klauzuli RETURNING. Poniższy przykład zakłada, że ​​kolumna powiązana z sekwencją nosi nazwę „id”:

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

Zauważ, że użyteczność klauzuli RETURNING wykracza daleko poza samo uzyskanie sekwencji, ponieważ:

  • Zwróć wartości, które były używane dla „wstawienia końcowego” (po, na przykład, wyzwalacz PRZED mógł zmienić wstawiane dane).
  • Zwróć wartości, które zostały usunięte:

    usuń z tabeli A, gdzie zwracany jest identyfikator> 100 *

  • Zwróć zmodyfikowane wiersze po aktualizacji:

    aktualizacja tabeli Zestaw X = „y”, gdzie blah = „blech” powraca *

  • Użyj wyniku usunięcia do aktualizacji:

    Z A jako (usuń * z tabeli A jako zwracany identyfikator) aktualizacja B zestaw usunięty = prawda gdzie id w (wybierz identyfikator z A);

cedzak
źródło
Oczywiście PO wyraźnie powiedział, że nie chce używać RETURNINGklauzuli - ale nie ma nic złego w tym, że korzyści płynące z korzystania z niej będą jaśniejsze dla innych.
RDFozz
Naprawdę po prostu próbowałem wskazać pułapki różnych innych metod (w tym przypadku, jeśli jedna nie była ostrożna, mogła zwrócić wartość inną niż oczekiwana) - i wyjaśnić najlepsze praktyki.
chander
1

Musiałem wykonać zapytanie pomimo użycia SQLALchemy, ponieważ nie udało mi się użyć currval.

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

To był projekt kolby pytona + postgresql.

Jalal
źródło
1

Musisz GRANTużyć na schemacie, tak jak to:

GRANT USAGE ON SCHEMA schema_name to user;

i

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;
AMML
źródło
1
Witamy w dba.se! Pozdrawiam twój pierwszy post! Nie wygląda na to, że oryginalny post dotyczy uprawnień. Być może rozważ rozszerzenie swojej odpowiedzi, aby zawierała pewne szczegóły dotyczące awarii uprawnień podczas wywoływania currval()funkcji, aby uczynić ją bardziej odpowiednią dla tego wątku?
Peter Vandivier,
0

W PostgreSQL 11.2 sekwencję można traktować jak tabelę:

Przykład, jeśli masz sekwencję o nazwie: „names_id_seq”

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

To powinno dać ci ostatni wstawiony identyfikator (w tym przypadku 4), co oznacza, że ​​bieżąca wartość (lub wartość, która powinna być użyta dla id dalej) powinna wynosić 5.

scifisamurai
źródło
-2

Różne wersje PostgreSQL mogą mieć różne funkcje, aby uzyskać identyfikator bieżącej lub następnej sekwencji.

Najpierw musisz znać wersję swojego Postgresa. Korzystanie z wyboru wersji (); aby uzyskać wersję.

W PostgreSQL 8.2.15 otrzymujesz bieżący identyfikator sekwencji za pomocą select last_value from schemaName.sequence_name.

Jeśli powyższe oświadczenie nie działa, możesz użyć select currval('schemaName.sequence_name');

Rudzik
źródło
2
Czy jest jakiś dowód na to, że różne wersje robią to inaczej?
dezso