Функція PostgreSQL для останнього вставленого ідентифікатора


321

Як у PostgreSQL я можу отримати останній ідентифікатор, вставлений у таблицю?

У MS SQL є SCOPE_IDENTITY ().

Будь ласка, не радить мені використовувати щось подібне:

select max(id) from table

чому ви ненавидите функцію max? Я думаю, що це дуже просто. Чи є якісь проблеми, такі як безпека?
jeongmin.cha

9
@ jeongmin.cha є проблема, якщо між ними є інші операції, і більше вставок (паралельних операцій) означає, що макс-ідентифікатор змінився, якщо тільки і доки явно не візьмеш замок і не відпустиш його
rohanagarwal

Якщо призначеним випадком використання є використання останнього вставленого ідентифікатора як частини значення наступної вставки, див. Це питання .
Флюс

Відповіді:


606

( tl;dr: goto варіант 3: ВСТАВИТИ З ПОВЕРНЕННЯМ)

Нагадаємо, що у postgresql не існує поняття "id" для таблиць, а лише послідовності (які зазвичай, але не обов'язково, використовуються як значення за замовчуванням для сурогатних первинних ключів, із ПЕРЕКЛЮЧЕННЯМ Псевдосерійного типу).

Якщо вам цікаво отримати ідентифікатор щойно вставленого рядка, є кілька способів:


Варіант 1: CURRVAL(<sequence name>);.

Наприклад:

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

Ім'я послідовності повинно бути відомо, це дійсно довільно; у цьому прикладі ми припускаємо, що в таблиці personsє idстовпець, створений за допомогою SERIALпсевдотипу. Щоб не покладатися на це і відчувати себе більш чистою, ви можете використовувати замість цього pg_get_serial_sequence:

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

Caveat: currval()працює лише після INSERT(який виконується nextval()), у тому ж сеансі .


Варіант 2: LASTVAL();

Це аналогічно попередньому, лише те, що вам не потрібно вказувати назву послідовності: вона шукає останню змінену послідовність (завжди всередині вашого сеансу, той самий застереження, як вище).


І те, CURRVALі LASTVALінше повністю безпечно. Поведінка послідовності в PG розроблена так, щоб різні сеанси не заважали, тому немає ризику перегонових умов (якщо інший сеанс вставить ще один рядок між моїм INSERT та моїм SELECT, я все одно отримаю своє правильне значення).

Однак у них є тонка потенційна проблема. Якщо в базі даних є якийсь TRIGGER (або RULE), який при вставці в personsтаблицю робить деякі додаткові вставки в інші таблиці ... тоді LASTVAL, ймовірно, дасть нам неправильне значення. Проблема може статися навіть із тим CURRVAL, що якщо додаткові вставки виконуються в одній personsтаблиці (це набагато менш звично, але ризик все ж існує).


Варіант 3: INSERTсRETURNING

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

Це найбільш чистий, ефективний та безпечний спосіб отримати ідентифікатор. Він не несе жодного з ризиків попереднього.

Недоліки? Майже немає: вам може знадобитися змінити спосіб виклику вашої заяви INSERT (в гіршому випадку, можливо, ваш API або рівень DB не очікує, що INSERT поверне значення); це не стандартний SQL (кого це цікавить); він доступний з Postgresql 8.2 (грудень 2006 ...)


Висновок: Якщо можете, перейдіть до варіанту 3. В іншому місці віддайте перевагу 1.

Примітка: всі ці методи марні, якщо ви маєте намір отримати останній вставлений ідентифікатор у всьому світі (не обов'язково за сеансом). Для цього потрібно вдатися SELECT max(id) FROM table(звичайно, це не буде читати невміщені вставки з інших транзакцій).

І навпаки, ви ніколи не використовуєте SELECT max(id) FROM tableзамість одного з 3 вищезазначених варіантів, щоб отримати ідентифікатор, щойно генерується вашим INSERTвисловлюванням, оскільки (крім продуктивності) це не є одночасно безпечним: між вашим INSERTта вашим SELECTіншим сеансом, можливо, було б вставлено інший запис.


25
LASTVAL () може бути дуже злим, якщо ви додасте тригер / правило, вставляючи рядки на себе в іншу таблицю.
Кубер Сапарев

3
SELECT max(id)на жаль, не виконує цю роботу, як тільки ви почнете видаляти рядки.
Саймон А. Егстер

2
@leonbloy Якщо я щось не пропустив, якщо у вас є рядки з ідентифікаторами 1,2,3,4,5та видалення рядків 4 і 5, останній вставлений ідентифікатор ще 5, але max()повертається 3.
Саймон А. Егстер

9
Зразок того, як можна RETURNING id;вставити це в іншу таблицю, буде вітатися!
Олів'є Понс

8
Як я можу використовувати RETURNING idсценарій SQL, що подається на інструмент psqlкомандного рядка?
аме

82

Див. Пункт ПОВЕРНЕННЯ оператора INSERT . В основному INSERT подвоюється як запит і повертає вам значення, яке було вставлено.


4
Працює з версії 8.2 і є найкращим і швидким рішенням.
Френк Хайкенс

9
можливо швидке пояснення, як використовувати згаданий повернений ідентифікатор?
Андрій

@Andrew Я не впевнений, що розумію питання. Ви не знаєте, як отримати результати запиту? Це залежить від мови / бібліотеки, і воно має працювати однаково незалежно від того, чи ви робите вибір або повертається вкладиш. Єдине інше тлумачення, яке я можу придумати, - це те, що ви успішно отримали ідентифікатор під час виклику і не знаєте, для чого це ... у такому випадку, чому ви його отримували?
kwatford

8
@Andrew, Якщо ви запускаєте з командного рядка psql це:, insert into names (firstname, lastname) values ('john', 'smith') returning id;то він просто виводить id так само, як якщо б ви бігли select id from names where id=$lastidбезпосередньо. Якщо ви хочете зберегти повернення у змінну aa , то insert into names (firstname, lastname) values ('john', 'smith') returning id into other_variable;Якщо вислів, що містить повернення, є останнім висловом функції , то ідентифікатор, що будується returning, повертається функцією в цілому.
Олександр Птах

32

ви можете використовувати пункт RETURNING в операторі INSERT, як і наступне

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 

28

Відповідь Леонблоя досить повна. Я хотів би додати лише спеціальний випадок, коли потрібно отримати останнє вставлене значення з функції PL / pgSQL, де OPTION 3 не відповідає точно.

Наприклад, якщо у нас є такі таблиці:

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
);

Якщо нам потрібно вставити клієнтський запис, ми маємо посилатися на запис особи. Але скажімо, що ми хочемо розробити функцію PL / pgSQL, яка вставляє нову запис у клієнт, але також піклується про вставлення нового запису людини. Для цього ми повинні використовувати незначну варіацію ВАРІАНТ 3 leonbloy:

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

Зауважте, що є два пункти INTO. Тому функцію PL / pgSQL визначали б як:

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;

Тепер ми можемо вставити нові дані, використовуючи:

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

або

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

І ми отримуємо новостворений ідентифікатор.

new_client
integer
----------
         1

9

Для тих, кому потрібно отримати весь запис даних, ви можете додати

returning *

до кінця запиту, щоб отримати весь об'єкт, включаючи ідентифікатор.


8

Дивіться приклад нижче

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);

Потім для отримання останнього вставленого ідентифікатора використовуйте це для таблиці "user" seq стовпця ім'я "id"

SELECT currval(pg_get_serial_sequence('users', 'id'));


1

Спробуйте це:

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

Якщо це повернення 1 (або що є початковим значенням для вашої послідовності), то скиньте послідовність назад до початкового значення, передаючи помилковий прапор:

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

Інакше

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

Це відновить значення послідовності до початкового стану, а "setval" повернеться зі значенням послідовності, яку ви шукаєте.


1

Postgres має вбудований механізм для того ж, який у тому самому запиті повертає ідентифікатор або все, що ви хочете, щоб запит повертався. ось приклад. Подумайте, у вас створена таблиця, яка містить 2 стовпці колонки1 та стовпця2, і ви хочете, щоб стовпець1 повертався після кожного вставки.

# 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)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.