Как я могу использовать currval () в PostgreSQL, чтобы получить последний вставленный идентификатор?


64

У меня есть таблица:

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

Я хочу "последний вставленный идентификатор" из этой таблицы, без использования RETURNING idпри вставке. Кажется, есть функция CURRVAL(), но я не понимаю, как ее использовать.

Я пробовал с:

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

но никто из них не работает. Как я могу использовать, currval()чтобы получить последний вставленный идентификатор?


2
Читатели этой проблемы / решения должны знать, что использование currval (), как правило, не рекомендуется, так как предложение RETURNING предоставляет идентификатор без дополнительных затрат на дополнительный запрос и без возможности возврата НЕПРАВИЛЬНОГО ЗНАЧЕНИЯ (которое будет делать currval в некоторые варианты использования.)
chander

1
@chander: есть ли у вас ссылки на это утверждение? Использование currval() определенно не рекомендуется.
a_horse_with_no_name

Возможно, это вопрос мнения относительно того, не рекомендуется ли использовать currval, но в некоторых случаях пользователи должны знать, что он может дать значение, которое не соответствует вашим ожиданиям (таким образом, делая RETURNING лучшим выбором, где это поддерживается.) Предположим, вы иметь таблицу A, которая использует последовательность a_seq, и таблицу B, которая также использует a_seq (вызывая nextval ('a_seq') для столбца PK.) Предположим, у вас также есть триггер (a_trg), который вставляется в таблицу B ON INSERT для таблицы A. в этом случае функция currval () (после вставки в таблицу A) вернет число, сгенерированное для вставки в таблицу B, а не в таблицу A.
chander

Ответы:


51

Если вы создаете столбец, serialPostgreSQL автоматически создает последовательность для этого.

Имя последовательности генерируется автоматически и всегда является tablename_columnname_seq, в вашем случае последовательность будет иметь имена names_id_seq.

Вставив в таблицу, вы можете вызвать currval()с этим именем последовательности:

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

Вместо жесткого кодирования имени последовательности, вы также можете использовать pg_get_serial_sequence()вместо:

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

Таким образом, вам не нужно полагаться на стратегию именования, которую использует Postgres.

Или, если вы вообще не хотите использовать имя последовательности, используйте lastval()


Я думаю, что это не очень хорошо использовать currval()в многопользовательской настройке. Например, на веб-сервере.
Джонас

9
Нет, вы ошибаетесь. currval()является "локальным" для вашего текущего соединения. Таким образом, нет проблем с его использованием в многопользовательской среде. Вот и вся цель последовательности.
a_horse_with_no_name

1
Это может сломаться в (довольно редком) случае, когда ваша вставка вызывает больше вставок в той же таблице, нет?
leonbloy

1
@a_horse_with_no_name: я думал не о множественных вставках (это легко определить), а о (возможно, неизвестных) триггерах, определенных в таблице. Попробуйте, например, это на вышеупомянутом: gist.github.com/anonymous/9784814
leonbloy

1
Это не ВСЕГДА имя таблицы и столбца. Если последовательность с таким именем уже существует, она сгенерирует новую последовательность путем объединения или увеличения числа на конце, и может потребоваться сократить имя, если оно превысит предел (который составляет 62 символа).
PhilHibbs

47

Это прямо из переполнения стека

Как указали @a_horse_with_no_name и @Jack Douglas, currval работает только с текущим сеансом. Поэтому, если вы согласны с тем фактом, что на результат может повлиять незафиксированная транзакция другого сеанса, и вам все еще нужно что-то, что будет работать между сеансами, вы можете использовать это:

SELECT last_value FROM your_sequence_name;

Используйте ссылку на SO для получения дополнительной информации.

Из документации Postgres ясно сказано, что

Вызывать lastval будет ошибкой, если nextval еще не был вызван в текущем сеансе.

Итак, я думаю, строго говоря, чтобы правильно использовать currval или last_value для последовательности между сеансами, вам нужно сделать что-то подобное?

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

Предполагая, конечно, что у вас не будет вставки или любого другого способа использования последовательного поля в текущем сеансе.


3
Я не могу вспомнить ситуацию, когда это было бы полезно.
ypercubeᵀᴹ

Мне было просто интересно, если это способ получить currval, если nextval не был вызван в текущем сеансе. Есть предложения?
Слак

Мне приходилось делать это, когда я жестко программировал первичные ключи для генерируемых данных приборов, где я хотел, чтобы значения PK, которые обычно генерировались постепенно, определялись заранее, чтобы упростить тестирование клиента. Чтобы поддерживать вставки при выполнении этого в столбце, который обычно регулируется значением по умолчанию из a nextval(), необходимо вручную установить последовательность, соответствующую количеству записей приборов, которые вы вставили, с жестко закодированными идентификаторами. Кроме того, способ решения проблемы недоступности pre-nextval функции currval () / lastval () заключается в непосредственном SELECTиспользовании последовательности.
Питер М. Элиас

@ ypercubeᵀᴹ Напротив, я не могу придумать вескую причину, чтобы использовать «правильный» ответ, который был выбран. Этот ответ не требует вставки записи в таблицу. Это также отвечает на вопрос. Опять же, я не могу представить себе веской причины НЕ использовать этот ответ над выбранным
Хенли Чиу

1
Этот ответ не включает изменение таблицы вообще. Проверенный делает. В идеале вы просто хотите узнать последний идентификатор, не внося никаких изменений. Что если это производственная БД? Вы не можете просто вставить случайную строку без возможных ударных. Таким образом, этот ответ является как безопасным, так и правильным.
Хенли Чиу

14

Вам нужно вызвать nextvalэту последовательность в этом сеансе доcurrval :

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

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

таким образом, вы не можете найти «последний вставленный идентификатор» из последовательности, если это insertне сделано в том же сеансе (транзакция может откатиться, но последовательность не будет)

как указано в ответе a_horse, create tableстолбец типа serialавтоматически создаст последовательность и будет использовать ее для генерации значения по умолчанию для столбца, поэтому insertобычно доступ осуществляется nextvalнеявно:

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

Итак, есть некоторые проблемы с этими различными методами:

Currval получает только последнее значение, сгенерированное в текущем сеансе - это замечательно, если у вас больше ничего не генерируется, но в тех случаях, когда вы можете вызвать триггер и / или получить последовательность, продвинутую более одного раза в текущей транзакции, это не собираюсь возвращать правильное значение. Это не проблема для 99% людей, но это то, что нужно учитывать.

Лучший способ получить уникальный идентификатор, назначенный после операции вставки, - использовать предложение RETURNING. В приведенном ниже примере предполагается, что столбец, связанный с последовательностью, называется «id»:

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

Обратите внимание, что полезность предложения RETURNING выходит далеко за рамки простого получения последовательности, поскольку она также:

  • Возвращаемые значения, которые использовались для «последней вставки» (после, например, триггера BEFORE, возможно, изменились вставляемые данные.)
  • Вернуть значения, которые были удалены:

    удалить из таблицы A, где id> 100 возвращается *

  • Вернуть измененные строки после ОБНОВЛЕНИЯ:

    обновить таблицу Набор X = 'y', где blah = 'blech' возвращение *

  • Используйте результат удаления для обновления:

    WITH A as (удалить * из таблицы A в качестве возвращаемого идентификатора) update B установить удалено = true, где id in (выбрать идентификатор из A);


Конечно, ОП явно заявил, что не хочет использовать RETURNINGпредложение, но нет ничего плохого в том, чтобы сделать его использование более понятным для других.
RDFozz

На самом деле я просто пытался указать на подводные камни различных других методов (в том случае, если один из них не был осторожен, он мог бы вернуть значение, отличное от ожидаемого значения) - и разъяснить наилучшую практику.
от

1

Мне пришлось выполнить запрос, несмотря на использование SQLALchemy, потому что мне не удалось использовать currval.

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

Это был проект на python flask + postgresql.



1

Вам нужно GRANTиспользовать схему, как это:

GRANT USAGE ON SCHEMA schema_name to user;

а также

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;

1
Добро пожаловать на dba.se! Ура на ваш первый пост! Похоже, что оригинальная публикация специально не касается разрешений. Возможно, стоит расширить свой ответ, включив в него некоторые особенности, связанные с ошибками разрешений, при вызове currval()функции, чтобы сделать ее более релевантной для этого потока?
Питер Вандивье

0

В PostgreSQL 11.2 вы можете рассматривать последовательность как таблицу, которая выглядит так:

Пример, если у вас есть последовательность с именем: 'names_id_seq'

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

Это должно дать вам последний вставленный идентификатор (в данном случае 4), что означает, что текущее значение (или значение, которое следует использовать для следующего идентификатора) должно быть 5.


-2

Разные версии PostgreSQL могут иметь разные функции для получения текущего или следующего идентификатора последовательности.

Во-первых, вы должны знать версию вашего Postgres. Используя select version (); чтобы получить версию.

В PostgreSQL 8.2.15 вы получаете текущий идентификатор последовательности с помощью select last_value from schemaName.sequence_name.

Если приведенное выше утверждение не работает, вы можете использовать select currval('schemaName.sequence_name');


2
Есть ли доказательства того, что разные версии делают это по-разному?
Дезсо
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.