Funkcja PostgreSQL dla ostatniego wstawionego identyfikatora

321

W PostgreSQL, jak mogę wstawić ostatni identyfikator do tabeli?

W MS SQL jest SCOPE_IDENTITY ().

Proszę nie doradzać mi używania czegoś takiego:

select max(id) from table
Anton
źródło
dlaczego nienawidzisz funkcji max? Myślę, że to bardzo proste. Czy jest jakiś problem, taki jak bezpieczeństwo?
jeongmin.cha
9
@ jeongmin.cha występuje problem, jeśli pomiędzy nimi są inne operacje, a więcej wstawień (operacje współbieżne), oznacza, że ​​zmienił się max id, chyba że i dopóki nie zabierzesz blokady jawnie i jej nie zwolnisz
rohanagarwal
Jeśli zamierzonym przypadkiem użycia jest użycie ostatniego wstawionego identyfikatora jako części wartości kolejnej wstawki, zobacz to pytanie .
Flux

Odpowiedzi:

606

( tl;dr: goto opcja 3: WSTAWIANIE Z POWROTEM)

Przypomnijmy, że w postgresql nie ma pojęcia „id” dla tabel, tylko sekwencje (które są zwykle, ale niekoniecznie używane jako wartości domyślne dla zastępczych kluczy podstawowych, z pseudo-typem SERIAL ).

Jeśli chcesz uzyskać identyfikator nowo wstawionego wiersza, istnieje kilka sposobów:


Wariant 1: CURRVAL(<sequence name>);.

Na przykład:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval('persons_id_seq');

Nazwa sekwencji musi być znana, to naprawdę dowolne; w tym przykładzie zakładamy, że tabela personsma idkolumnę utworzoną za pomocą SERIALpseudo-typu. Aby uniknąć polegania na tym i poczuć się czystszym, możesz zamiast tego użyć pg_get_serial_sequence:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval(pg_get_serial_sequence('persons','id'));

Zastrzeżenie: currval()działa tylko po INSERT(który wykonał nextval()), w tej samej sesji .


Opcja 2: LASTVAL();

Jest to podobne do poprzedniego, tyle że nie musisz określać nazwy sekwencji: szuka najnowszej zmodyfikowanej sekwencji (zawsze w sesji, z tym samym zastrzeżeniem jak powyżej).


Zarówno CURRVALi LASTVALsą całkowicie zbieżne bezpieczne. Zachowanie sekwencji w PG jest zaprojektowane tak, aby inna sesja nie przeszkadzała, więc nie ma ryzyka warunków wyścigu (jeśli inna sesja wstawi kolejny wiersz między moim INSERT a moim SELECT, nadal otrzymuję swoją prawidłową wartość).

Jednak oni mają subtelny potencjalny problem. Jeśli baza danych ma TRIGGER (lub ZASADĘ), która po wstawieniu do personstabeli powoduje dodatkowe wstawienia w innych tabelach LASTVAL, prawdopodobnie poda nam niewłaściwą wartość. Problem może się nawet zdarzyć CURRVAL, jeśli dodatkowe wstawienia zostaną wykonane w tej samej personstabeli (jest to znacznie mniej normalne, ale ryzyko nadal istnieje).


Opcja 3: INSERTzRETURNING

INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;

Jest to najbardziej czysty, wydajny i bezpieczny sposób na uzyskanie identyfikatora. Nie ma żadnego ryzyka z poprzedniego.

Wady Prawie żaden: może być konieczna modyfikacja sposobu wywoływania instrukcji INSERT (w najgorszym przypadku warstwa API lub DB nie spodziewa się, że INSERT zwróci wartość); to nie jest standardowy SQL (kogo to obchodzi); jest dostępny od Postgresql 8.2 (grudzień 2006 ...)


Wniosek: jeśli możesz, wybierz opcję 3. W innym miejscu, wybierz 1.

Uwaga: wszystkie te metody są bezużyteczne, jeśli zamierzasz uzyskać ostatni wstawiony identyfikator globalnie (niekoniecznie przez twoją sesję). W tym celu należy skorzystać SELECT max(id) FROM table(oczywiście nie odczyta to nieprzypisanych wstawek z innych transakcji).

I odwrotnie, nigdy nie powinieneś używać SELECT max(id) FROM tablejednej z 3 powyższych opcji, aby uzyskać identyfikator wygenerowany właśnie przez twoją INSERTinstrukcję, ponieważ (oprócz wydajności) nie jest to jednocześnie bezpieczne: między twoją INSERTa SELECTinną sesją mogło wstawić inny rekord.

leonbloy
źródło
25
LASTVAL () może być bardzo zły, jeśli dodasz wyzwalacz / regułę wstawiając wiersze do siebie w innej tabeli.
Kouber Saparev
3
SELECT max(id)niestety nie wykonuje zadania, gdy tylko zaczniesz usuwać wiersze.
Simon A. Eugster,
2
@leonbloy Chyba że coś przeoczyłem, jeśli masz wiersze z identyfikatorami 1,2,3,4,5i usuwasz wiersze 4 i 5, ostatni wstawiony identyfikator to wciąż 5, ale max()zwraca 3.
Simon A. Eugster
9
Przydałaby się próbka użycia tego, RETURNING id;aby wstawić to do innej tabeli!
Olivier Pons,
8
Jak korzystać RETURNING idz skryptu SQL dostarczanego do psqlnarzędzia wiersza poleceń?
amoe
82

Zobacz klauzulę RETURNING instrukcji INSERT . Zasadniczo INSERT pełni również funkcję zapytania i zwraca wartość, która została wstawiona.

kwatford
źródło
4
Działa od wersji 8.2 i jest najlepszym i najszybszym rozwiązaniem.
Frank Heikens
9
może szybkie wyjaśnienie, jak korzystać ze wspomnianego zwróconego identyfikatora?
Andrew
@Andrew Nie jestem pewien, czy rozumiem pytanie. Nie wiesz, jak pobrać wyniki z zapytania? Jest to zależne od języka / biblioteki i powinno działać tak samo, niezależnie od tego, czy wykonujesz zaznaczoną, czy zwracaną wstawkę. Jedyną inną interpretacją, jaką mogę wymyślić, jest to, że udało ci się odzyskać identyfikator z rozmowy i nie wiesz, do czego on służy ... w takim razie, dlaczego go odzyskałeś?
kwatford 20.04.16
8
@Andrew, jeśli uruchamiasz z wiersza polecenia psql to :, insert into names (firstname, lastname) values ('john', 'smith') returning id;to po prostu wyświetla id tak, jakbyś działał select id from names where id=$lastidbezpośrednio. Jeśli chcesz zapisać zwrot w zmiennej , to insert into names (firstname, lastname) values ('john', 'smith') returning id into other_variable;jeśli instrukcja zawierająca zwracaną wartość jest ostatnią instrukcją w funkcji , wówczas identyfikator, który returningjest zwracany, jest zwracany przez funkcję jako całość.
Alexander Bird
32

możesz użyć klauzuli RETURNING w instrukcji INSERT, podobnie jak poniżej

wgzhao=# create table foo(id int,name text);
CREATE TABLE
wgzhao=# insert into foo values(1,'wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into foo values(3,'wgzhao') returning id;
 id 
----
  3
(1 row)

INSERT 0 1

wgzhao=# create table bar(id serial,name text);
CREATE TABLE
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  2
(1 row)

INSERT 0 
wgzhao
źródło
28

Odpowiedź Leonbloya jest dość kompletna. Dodałbym tylko specjalny przypadek, w którym trzeba uzyskać ostatnią wstawioną wartość z funkcji PL / pgSQL, gdzie OPCJA 3 nie pasuje dokładnie.

Na przykład, jeśli mamy następujące tabele:

CREATE TABLE person(
   id serial,
   lastname character varying (50),
   firstname character varying (50),
   CONSTRAINT person_pk PRIMARY KEY (id)
);

CREATE TABLE client (
    id integer,
   CONSTRAINT client_pk PRIMARY KEY (id),
   CONSTRAINT fk_client_person FOREIGN KEY (id)
       REFERENCES person (id) MATCH SIMPLE
);

Jeśli musimy wstawić rekord klienta, musimy odwołać się do rekordu osoby. Ale powiedzmy, że chcemy opracować funkcję PL / pgSQL, która wstawia nowy rekord do klienta, ale także dba o wstawienie rekordu nowej osoby. W tym celu musimy użyć niewielkiej odmiany OPCJI 3 firmy leonbloy:

INSERT INTO person(lastname, firstname) 
VALUES (lastn, firstn) 
RETURNING id INTO [new_variable];

Zauważ, że istnieją dwie klauzule INTO. Dlatego funkcję PL / pgSQL można zdefiniować następująco:

CREATE OR REPLACE FUNCTION new_client(lastn character varying, firstn character varying)
  RETURNS integer AS
$BODY$
DECLARE
   v_id integer;
BEGIN
   -- Inserts the new person record and retrieves the last inserted id
   INSERT INTO person(lastname, firstname)
   VALUES (lastn, firstn)
   RETURNING id INTO v_id;

   -- Inserts the new client and references the inserted person
   INSERT INTO client(id) VALUES (v_id);

   -- Return the new id so we can use it in a select clause or return the new id into the user application
    RETURN v_id;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

Teraz możemy wstawić nowe dane za pomocą:

SELECT new_client('Smith', 'John');

lub

SELECT * FROM new_client('Smith', 'John');

I otrzymujemy nowo utworzony identyfikator.

new_client
integer
----------
         1
Krauss
źródło
9

Dla tych, którzy potrzebują uzyskać wszystkie dane, możesz dodać

returning *

na końcu zapytania, aby uzyskać cały obiekt, w tym identyfikator.

emreoktem
źródło
8

Zobacz poniższy przykład

CREATE TABLE users (
    -- make the "id" column a primary key; this also creates
    -- a UNIQUE constraint and a b+-tree index on the column
    id    SERIAL PRIMARY KEY,
    name  TEXT,
    age   INT4
);

INSERT INTO users (name, age) VALUES ('Mozart', 20);

Następnie, aby uzyskać ostatni wstawiony identyfikator, użyj tego dla tabeli „user” seq nazwa kolumny „id”

SELECT currval(pg_get_serial_sequence('users', 'id'));
RINSON KE
źródło
1

Spróbuj tego:

select nextval('my_seq_name');  // Returns next value

Jeśli to zwróci 1 (lub cokolwiek to jest wartość_początkowa dla sekwencji), następnie zresetuj sekwencję z powrotem do pierwotnej wartości, przekazując flagę false:

select setval('my_seq_name', 1, false);

Inaczej,

select setval('my_seq_name', nextValue - 1, true);

Spowoduje to przywrócenie wartości sekwencji do pierwotnego stanu, a „setval” powróci z poszukiwaną wartością sekwencji.

Oozman
źródło
1

Postgres ma wbudowany mechanizm dla tego samego, który w tym samym zapytaniu zwraca id lub cokolwiek, co chcesz zwrócić zapytanie. Oto przykład. Załóżmy, że masz utworzoną tabelę z 2 kolumnami kolumna 1 i kolumna 2 i chcesz, aby kolumna 1 była zwracana po każdej wstawce.

# create table users_table(id serial not null primary key, name character varying);
CREATE TABLE
#insert into users_table(name) VALUES ('Jon Snow') RETURNING id;
 id 
----
  1
(1 row)

# insert into users_table(name) VALUES ('Arya Stark') RETURNING id;
 id 
----
  2
(1 row)
Sandip Debnath
źródło