Как сбросить последовательность первичных ключей postgres, если они не синхронизированы?


523

Я столкнулся с проблемой, что моя первичная ключевая последовательность не синхронизирована со строками моей таблицы.

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

Кажется, это вызвано тем, что импорт / восстановление не поддерживает последовательность должным образом.


Мне любопытно .. вы бросаете БД, прежде чем делать восстановление? У меня есть слабое воспоминание об этом, но я могу ошибаться: P
Артур Томас,

25
В вики PostgreSQL есть страница, посвященная исправлению последовательностей .
Брэд Кох

14
Просто, чтобы помочь googleability, здесь выдается сообщение об ошибке: «Значение дублированного ключа нарушает уникальное ограничение ...»
superluminary

4
Вот как это делает sqlsequencereset в Django: SELECT setval (pg_get_serial_sequence ("<имя_таблицы>", 'id'), coalesce (max ("id"), 1), max ("id") не является нулевым) FROM "< table_name> ";
пользователь

Первый экземпляр <имя таблицы> должен быть заключен в одинарные кавычки для работы функции pg_get_serioal_sequence: SELECT setval (pg_get_serial_sequence ('<table_name>', 'id'), coalesce (max ("id"), 1) , max ("id") не является нулевым) FROM "<имя_таблицы>"
nclu

Ответы:


715
-- Login to psql and run the following

-- What is the result?
SELECT MAX(id) FROM your_table;

-- Then run...
-- This should be higher than the last result.
SELECT nextval('your_table_id_seq');

-- If it's not higher... run this set the sequence last to your highest id. 
-- (wise to run a quick pg_dump first...)

BEGIN;
-- protect against concurrent inserts while you update the counter
LOCK TABLE your_table IN EXCLUSIVE MODE;
-- Update the sequence
SELECT setval('your_table_id_seq', COALESCE((SELECT MAX(id)+1 FROM your_table), 1), false);
COMMIT;

Источник - Рубиновый Форум


12
В любом случае, добавление 1 к MAX (id) оставит один идентификатор в ваших идентификаторах, поскольку наборы setval являются последним значением последовательности, а не следующим.
Микл

6
Ваш пример не будет работать, если в таблице нет строк. Поэтому ниже приведенный SQL более безопасен: SELECT setval ('your_table_id_seq', coalesce ((выберите max (id) +1 из your_table), 1), true);
Валерий Викторовский

10
@Valery: Но чтобы избежать пробелов, упомянутых @mikl двумя комментариями выше, вам нужноSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Энтони Хэтчкинс

20
Все проблемы решены и объединены в один запрос:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Фрунси

15
Если ваше приложение заботится о пробелах в последовательностях, ваше приложение не работает. Пробелы в последовательностях нормальны и могут возникать из-за незапланированных остановок базы данных, откатов транзакций после ошибок и т. Д.
Крейг Рингер,

202

pg_get_serial_sequenceможет использоваться, чтобы избежать любых неправильных предположений о названии последовательности. Это сбрасывает последовательность в одном кадре:

SELECT pg_catalog.setval(pg_get_serial_sequence('table_name', 'id'), (SELECT MAX(id) FROM table_name)+1);

Или более кратко:

SELECT pg_catalog.setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;

Однако эта форма не может правильно обрабатывать пустые таблицы, так как max (id) является нулем, и вы также не можете установить 0, потому что он будет вне диапазона последовательности. Одним из способов решения этой проблемы является использование ALTER SEQUENCEсинтаксиса, т.е.

ALTER SEQUENCE table_name_id_seq RESTART WITH 1;
ALTER SEQUENCE table_name_id_seq RESTART; -- 8.4 or higher

Но ALTER SEQUENCEимеет ограниченное применение, поскольку имя последовательности и значение перезапуска не могут быть выражениями.

Кажется, лучшее универсальное решение - это вызов setvalс false в качестве 3-го параметра, что позволяет нам указать «следующее значение для использования»:

SELECT setval(pg_get_serial_sequence('t1', 'id'), coalesce(max(id),0) + 1, false) FROM t1;

Это помечает все мои коробки:

  1. избегает жесткого кодирования фактического имени последовательности
  2. правильно обрабатывает пустые таблицы
  3. обрабатывает таблицы с существующими данными и не оставляет дыры в последовательности

Наконец, обратите внимание, что это pg_get_serial_sequenceработает , только если последовательность принадлежит столбцу. Это будет иметь место, если столбец увеличения был определен как serialтип, однако, если последовательность была добавлена ​​вручную, необходимо убедиться, что ALTER SEQUENCE .. OWNED BYона также выполняется.

т.е. если serialдля создания таблицы использовался тип, все это должно работать:

CREATE TABLE t1 (
  id serial,
  name varchar(20)
);

SELECT pg_get_serial_sequence('t1', 'id'); -- returns 't1_id_seq'

-- reset the sequence, regardless whether table has rows or not:
SELECT setval(pg_get_serial_sequence('t1', 'id'), coalesce(max(id),0) + 1, false) FROM t1;

Но если последовательности были добавлены вручную:

CREATE TABLE t2 (
  id integer NOT NULL,
  name varchar(20)
);

CREATE SEQUENCE t2_custom_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

ALTER TABLE t2 ALTER COLUMN id SET DEFAULT nextval('t2_custom_id_seq'::regclass);

ALTER SEQUENCE t2_custom_id_seq OWNED BY t2.id; -- required for pg_get_serial_sequence

SELECT pg_get_serial_sequence('t2', 'id'); -- returns 't2_custom_id_seq'

-- reset the sequence, regardless whether table has rows or not:
SELECT setval(pg_get_serial_sequence('t2', 'id'), coalesce(max(id),0) + 1, false) FROM t1;

11
Там нет необходимости в «+1» в запросе, setval()устанавливает текущее значение и nextval()уже вернет текущее значение +1.
Энтони Хэтчкинс

1
Функция обтекания этого метода, которая принимает один параметр - имя_таблицы - в моем ответе ниже: stackoverflow.com/a/13308052/237105
Энтони Хэтчкинс,

@AntonyHatchkins ура. Только что увидел еще одно повторение ошибки +1, так что, наконец, ударил, что, к
счастью,

99

Самый короткий и быстрый способ:

SELECT setval('tbl_tbl_id_seq', max(tbl_id)) FROM tbl;

tbl_idявляясь serialстолбцом таблицы tbl, рисуя из последовательности tbl_tbl_id_seq(которая является автоматическим именем по умолчанию).

Если вы не знаете имя присоединенной последовательности (которая не обязательно должна быть в форме по умолчанию), используйте pg_get_serial_sequence():

SELECT setval(pg_get_serial_sequence('tbl', 'tbl_id'), max(tbl_id)) FROM tbl;

Здесь нет единой ошибки. По документации:

Двухпараметрическая форма устанавливает поле последовательности last_valueв указанное значение и устанавливает в его is_calledполе значение true, что означает, что следующий nextvalбудет продвигать последовательность перед возвратом значения.

Жирный акцент мой.

Если таблица может быть пустой и фактически начинаться с 1 в этом случае:

SELECT setval(pg_get_serial_sequence('tbl', 'tbl_id')
            , COALESCE(max(tbl_id) + 1, 1)
            , false)
FROM tbl;

Мы не можем просто использовать форму 2-paremater и начать с 0того, что нижняя граница последовательностей по умолчанию равна 1 (если не настроена).

совпадение

Пока нет защиты от одновременной активности последовательности или записи в таблицу в вышеуказанных запросах. Если это актуально, вы можете заблокировать таблицу в эксклюзивном режиме. Он не позволяет одновременным транзакциям записывать большее число, пока вы пытаетесь синхронизироваться. (Это также временно блокирует безвредные записи, не связываясь с максимальным числом.)

Но это не учитывает клиентов, которые могли заранее получить порядковые номера без каких-либо блокировок на главной таблице (что может случиться). Чтобы учесть это, тоже только увеличивайте текущее значение последовательности, но никогда не уменьшайте его. Это может показаться параноидальным, но это соответствует природе последовательностей и защите от проблем параллелизма.

BEGIN;

LOCK TABLE tbl IN EXCLUSIVE MODE;

SELECT setval('tbl_tbl_id_seq', max(tbl_id))
FROM   tbl
HAVING max(tbl_id) > (SELECT last_value FROM tbl_tbl_id_seq);

COMMIT;

Где находится «СТАНДАРТНАЯ община-библиотека основных функций»? Второе предложение выбора этого ответа в EXECUTE format()(например, @ EB.) Является важной функцией! Как исправить это отсутствие стандартной библиотеки в PostgreSQL ????
Питер Краусс

Не имеет значения, если есть один за другим. Пробелы в последовательностях нормальные. Если ваше приложение не может справиться с ситуацией, оно сломано, поскольку из-за откатов транзакций, незапланированных отключений сервера и т. Д. Могут возникать пробелы
Крейг Рингер,

1
@Craig: ошибка, к которой я обратился (и ее нет), будет иметь значение, так как в противном случае мы рискуем получить ошибку дублирующего ключа. Обратное направление ваших соображений; кажется недоразумением.
Эрвин Брандштеттер,

ах, имеет смысл.
Крейг Рингер

Это работает для меня
гектар

54

Это сбросит все последовательности из общедоступного, не делая никаких предположений относительно имен таблиц или столбцов. Проверено на версии 8.4

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text, sequence_name text) RETURNS "pg_catalog"."void" AS 

    $body$  
      DECLARE 
      BEGIN 

      EXECUTE 'SELECT setval( ''' || sequence_name  || ''', ' || '(SELECT MAX(' || columnname || ') FROM ' || tablename || ')' || '+1)';



      END;  

    $body$  LANGUAGE 'plpgsql';


    select table_name || '_' || column_name || '_seq', reset_sequence(table_name, column_name, table_name || '_' || column_name || '_seq') from information_schema.columns where column_default like 'nextval%';

1
+1 очень полезная функция! Наши имена последовательностей не совпадают точно с именами таблиц, поэтому я использовал substring(column_default, '''(.*)''')вместо table_name || '_' || column_name || '_seq'. Работает отлично.
Крис Лерчер,

4
Обратите внимание, что это не удастся с именами последовательностей, содержащими одинарные кавычки, или именами таблиц с заглавными буквами, пробелами и т. Д. В имени. Функции quote_literalи quote_ident, или предпочтительно formatфункция, действительно должны использоваться здесь.
Крейг Рингер

2
Жаль, что я мог бы дать больше, чем один голос ... хорошо сделано, сэр. Отлично работает и на Postgres 9.1, по крайней мере для меня.
Пилман

1
Это замечательно. Я использовал, substring(column_default from 'nextval\(''(.+)''::regclass\)')чтобы явно захватить имя последовательности. Работал как шарм.
Мэтью Макдональд

Я искал это решение больше дня, спасибо большое, даже я использовал метод, предложенный @ChrisLercher, для замены текстаsubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv

43

ALTER SEQUENCE sequence_name RESTART WITH (SELECT max (id) FROM table_name); Не работает

Скопировано из ответа @tardate:

SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;

8
это синтаксическая ошибка для меня в 8.4 (в ^ (SELECT ...). RESTART WITH, кажется, принимает только порядковый номер. Это работает, хотя: SELECT setval (pg_get_serial_sequence ('table_name', 'id'), (SELECT MAX ( id) FROM table_name) +1);
дата

1
Решение Muruges также не работает в 9.4. Не понимаю, почему так много голосов против этого ответа. ALTER SEQUENCE не разрешает подзапросы. Решение от @tardate работает отлично. Отредактированный ответ для удаления неверных данных.
Владислав Раструсный

ALTER SEQUENCE работал идеально для меня. Я использовал COPY для ввода некоторых данных, и в первичных ключах были пробелы, а в INSERT создавались исключения дублирующихся ключей. Установка последовательности сделала свое дело. 9.4
user542319

22

Эта команда только для изменения автоматически сгенерированного значения последовательности клавиш в postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

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

имя последовательности по умолчанию будет "TableName_FieldName_seq". Например, если имя вашей таблицы и имя "MyTable"вашего поля "MyID", то имя вашей последовательности будет "MyTable_MyID_seq".

Этот ответ такой же, как ответ @ murugesanponappan, но в его решении есть синтаксическая ошибка. Вы не можете использовать подзапрос (select max()...)в alterкоманде. Так что либо вы должны использовать фиксированное числовое значение, либо вам нужно использовать переменную вместо подзапроса.


Это идеальное решение, большое спасибо, сэр. Но в моем случае у меня была ошибка, поэтому мне пришлось изменить ее на ALTER SEQUENCE "your_sequence_name" RESTART WITH 1;
Deunz

18

Сброс всех последовательностей, никаких предположений об именах, за исключением того, что первичным ключом каждой таблицы является «id»:

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text)
RETURNS "pg_catalog"."void" AS
$body$
DECLARE
BEGIN
    EXECUTE 'SELECT setval( pg_get_serial_sequence(''' || tablename || ''', ''' || columnname || '''),
    (SELECT COALESCE(MAX(id)+1,1) FROM ' || tablename || '), false)';
END;
$body$  LANGUAGE 'plpgsql';

select table_name || '_' || column_name || '_seq', reset_sequence(table_name, column_name) from information_schema.columns where column_default like 'nextval%';

Отлично сработало на моей версии 9.1
Валентин Васильев

Вы должны добавить цитату, если таблица содержит заглавные буквы:pg_get_serial_sequence(''"' || tablename || '"''
Мануэль Дарво

Это лучшая функция! Вы можете избежать проблем с EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss

13

Эти функции чреваты опасностями, когда имена последовательностей, имен столбцов, имен таблиц или схем имеют смешные символы, такие как пробелы, знаки пунктуации и тому подобное. Я написал это:

CREATE OR REPLACE FUNCTION sequence_max_value(oid) RETURNS bigint
VOLATILE STRICT LANGUAGE plpgsql AS  $$
DECLARE
 tabrelid oid;
 colname name;
 r record;
 newmax bigint;
BEGIN
 FOR tabrelid, colname IN SELECT attrelid, attname
               FROM pg_attribute
              WHERE (attrelid, attnum) IN (
                      SELECT adrelid::regclass,adnum
                        FROM pg_attrdef
                       WHERE oid IN (SELECT objid
                                       FROM pg_depend
                                      WHERE refobjid = $1
                                            AND classid = 'pg_attrdef'::regclass
                                    )
          ) LOOP
      FOR r IN EXECUTE 'SELECT max(' || quote_ident(colname) || ') FROM ' || tabrelid::regclass LOOP
          IF newmax IS NULL OR r.max > newmax THEN
              newmax := r.max;
          END IF;
      END LOOP;
  END LOOP;
  RETURN newmax;
END; $$ ;

Вы можете вызвать его для одной последовательности, передав ему OID, и он вернет наибольший номер, используемый любой таблицей, в которой эта последовательность установлена ​​по умолчанию; или вы можете запустить его с таким запросом, чтобы сбросить все последовательности в вашей базе данных:

 select relname, setval(oid, sequence_max_value(oid))
   from pg_class
  where relkind = 'S';

Используя другую квалификацию, вы можете сбросить только последовательность в определенной схеме и т. Д. Например, если вы хотите настроить последовательности в «публичной» схеме:

select relname, setval(pg_class.oid, sequence_max_value(pg_class.oid))
  from pg_class, pg_namespace
 where pg_class.relnamespace = pg_namespace.oid and
       nspname = 'public' and
       relkind = 'S';

Обратите внимание, что из-за того, как работает setval (), вам не нужно добавлять 1 к результату.

В качестве заключительного замечания я должен предупредить, что некоторые базы данных, по-видимому, имеют значения по умолчанию, связанные с последовательностями таким образом, что не позволяют системным каталогам иметь полную информацию о них. Это происходит, когда вы видите такие вещи в psql's \ d:

alvherre=# \d baz
                     Tabla «public.baz»
 Columna |  Tipo   |                 Modificadores                  
---------+---------+------------------------------------------------
 a       | integer | default nextval(('foo_a_seq'::text)::regclass)

Обратите внимание, что вызов nextval () в этом предложении по умолчанию имеет приведение :: text в дополнение к приведению :: regclass. Я думаю, это связано с тем, что базы данных были pg_dump'ом из старых версий PostgreSQL. Что произойдет, так это то, что функция sequence_max_value () выше будет игнорировать такую ​​таблицу. Чтобы устранить проблему, вы можете переопределить предложение DEFAULT, чтобы ссылаться на последовательность напрямую без приведения:

alvherre=# alter table baz alter a set default nextval('foo_a_seq');
ALTER TABLE

Тогда psql отображает это правильно:

alvherre=# \d baz
                     Tabla «public.baz»
 Columna |  Tipo   |             Modificadores              
---------+---------+----------------------------------------
 a       | integer | default nextval('foo_a_seq'::regclass)

Как только вы исправите это, функция будет работать правильно для этой таблицы, а также для всех других, которые могут использовать ту же последовательность.


Это удивительно, спасибо! Следует отметить, что мне нужно было добавить приведение к назначению (строка 21 в коде функции) следующим образом: newmax := r.max::bigint;чтобы оно работало правильно для меня.
Томми Браво

Пришлось также изменить это: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' заметить добавленное ::bigintприведение в динамически построенном запросе.
Томми Браво

9

Еще один plpgsql - сбрасывается только если max(att) > then lastval

do --check seq not in sync
$$
declare
 _r record;
 _i bigint;
 _m bigint;
begin
  for _r in (
    SELECT relname,nspname,d.refobjid::regclass, a.attname, refobjid
    FROM   pg_depend    d
    JOIN   pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
    JOIN pg_class r on r.oid = objid
    JOIN pg_namespace n on n.oid = relnamespace
    WHERE  d.refobjsubid > 0 and  relkind = 'S'
   ) loop
    execute format('select last_value from %I.%I',_r.nspname,_r.relname) into _i;
    execute format('select max(%I) from %s',_r.attname,_r.refobjid) into _m;
    if coalesce(_m,0) > _i then
      raise info '%',concat('changed: ',_r.nspname,'.',_r.relname,' from:',_i,' to:',_m);
      execute format('alter sequence %I.%I restart with %s',_r.nspname,_r.relname,_m+1);
    end if;
  end loop;

end;
$$
;

также комментирование строки --execute format('alter sequenceдаст список, фактически не сбрасывая значение


8

Сбросить всю последовательность из общего

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text) RETURNS "pg_catalog"."void" AS 
$body$  
  DECLARE 
  BEGIN 
  EXECUTE 'SELECT setval( ''' 
  || tablename  
  || '_id_seq'', ' 
  || '(SELECT id + 1 FROM "' 
  || tablename  
  || '" ORDER BY id DESC LIMIT 1), false)';  
  END;  
$body$  LANGUAGE 'plpgsql';

select sequence_name, reset_sequence(split_part(sequence_name, '_id_seq',1)) from information_schema.sequences
        where sequence_schema='public';

Похоже, что этот подход делает предположения об именах столбцов и таблиц, поэтому он не работает для меня
djsnowsill

Не повредит ли это данным в базе данных?
Зеннин

8

Я предлагаю это решение, найденное на postgres wiki. Он обновляет все последовательности ваших таблиц.

SELECT 'SELECT SETVAL(' ||
       quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
       ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
       quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
FROM pg_class AS S,
     pg_depend AS D,
     pg_class AS T,
     pg_attribute AS C,
     pg_tables AS PGT
WHERE S.relkind = 'S'
    AND S.oid = D.objid
    AND D.refobjid = T.oid
    AND D.refobjid = C.attrelid
    AND D.refobjsubid = C.attnum
    AND T.relname = PGT.tablename
ORDER BY S.relname;

Как использовать (из Вики Postgres):

  • Сохраните это в файл, скажем, «reset.sql»
  • Запустите файл и сохраните его вывод так, чтобы он не включал обычные заголовки, затем запустите этот вывод. Пример:

Пример:

psql -Atq -f reset.sql -o temp
psql -f temp
rm temp

Оригинальная статья (также с исправлением последовательности владения) здесь


7

Некоторые действительно жесткие ответы здесь, я предполагаю, что это было действительно плохо примерно в то время, когда об этом спрашивали, так как многие ответы отсюда не работают для версии 9.3. Документация , начиная с версии 8.0 дает ответ на этот самый вопрос:

SELECT setval('serial', max(id)) FROM distributors;

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

SELECT setval('"Serial"', max(id)) FROM distributors;

7

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

Я решил это, создав скрипт для запуска после заполнения базы данных:

DO
$do$
DECLARE tablename text;
BEGIN
    -- change the where statments to include or exclude whatever tables you need
    FOR tablename IN SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE' AND table_name != '__EFMigrationsHistory'
        LOOP
            EXECUTE format('SELECT setval(pg_get_serial_sequence(''"%s"'', ''Id''), (SELECT MAX("Id") + 1 from "%s"))', tablename, tablename);
    END LOOP;
END
$do$

1
почему MAX("Id") + 1это работает лучше для меня, когда последовательность = макс.
последняя ссылка

6

Моя версия использует первую, с некоторой проверкой ошибок ...

BEGIN;
CREATE OR REPLACE FUNCTION reset_sequence(_table_schema text, _tablename text, _columnname text, _sequence_name text)
RETURNS pg_catalog.void AS
$BODY$
DECLARE
BEGIN
 PERFORM 1
 FROM information_schema.sequences
 WHERE
  sequence_schema = _table_schema AND
  sequence_name = _sequence_name;
 IF FOUND THEN
  EXECUTE 'SELECT setval( ''' || _table_schema || '.' || _sequence_name  || ''', ' || '(SELECT MAX(' || _columnname || ') FROM ' || _table_schema || '.' || _tablename || ')' || '+1)';
 ELSE
  RAISE WARNING 'SEQUENCE NOT UPDATED ON %.%', _tablename, _columnname;
 END IF;
END; 
$BODY$
 LANGUAGE 'plpgsql';

SELECT reset_sequence(table_schema, table_name, column_name, table_name || '_' || column_name || '_seq')
FROM information_schema.columns
WHERE column_default LIKE 'nextval%';

DROP FUNCTION reset_sequence(_table_schema text, _tablename text, _columnname text, _sequence_name text) ;
COMMIT;

Спасибо за проверку ошибок! Очень ценится, поскольку имена таблиц / столбцов усекаются, если они слишком длинные, что вы RAISE WARNINGопределили для меня.
Николас Райли

5

Собираем все вместе

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text) 
RETURNS "pg_catalog"."void" AS
$body$
DECLARE
BEGIN
  EXECUTE 'SELECT setval( pg_get_serial_sequence(''' || tablename || ''', ''id''),
  (SELECT COALESCE(MAX(id)+1,1) FROM ' || tablename || '), false)';
END;
$body$  LANGUAGE 'plpgsql';

исправит id'последовательность данной таблицы (как обычно необходимо, например, для django).


4

прежде чем я еще не попробовал код: в следующем я выкладываю версию для sql-кода для решений Klaus и user457226, которая работала на моем компьютере [Postgres 8.3], с небольшими изменениями для Klaus и моей версии для пользователя 457226 один.

Клаус решение:

drop function IF EXISTS rebuilt_sequences() RESTRICT;
CREATE OR REPLACE FUNCTION  rebuilt_sequences() RETURNS integer as
$body$
  DECLARE sequencedefs RECORD; c integer ;
  BEGIN
    FOR sequencedefs IN Select
      constraint_column_usage.table_name as tablename,
      constraint_column_usage.table_name as tablename, 
      constraint_column_usage.column_name as columnname,
      replace(replace(columns.column_default,'''::regclass)',''),'nextval(''','') as sequencename
      from information_schema.constraint_column_usage, information_schema.columns
      where constraint_column_usage.table_schema ='public' AND 
      columns.table_schema = 'public' AND columns.table_name=constraint_column_usage.table_name
      AND constraint_column_usage.column_name = columns.column_name
      AND columns.column_default is not null
   LOOP    
      EXECUTE 'select max('||sequencedefs.columnname||') from ' || sequencedefs.tablename INTO c;
      IF c is null THEN c = 0; END IF;
      IF c is not null THEN c = c+ 1; END IF;
      EXECUTE 'alter sequence ' || sequencedefs.sequencename ||' restart  with ' || c;
   END LOOP;

   RETURN 1; END;
$body$ LANGUAGE plpgsql;

select rebuilt_sequences();

user457226 решение:

--drop function IF EXISTS reset_sequence (text,text) RESTRICT;
CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text,columnname text) RETURNS bigint --"pg_catalog"."void"
AS
$body$
  DECLARE seqname character varying;
          c integer;
  BEGIN
    select tablename || '_' || columnname || '_seq' into seqname;
    EXECUTE 'SELECT max("' || columnname || '") FROM "' || tablename || '"' into c;
    if c is null then c = 0; end if;
    c = c+1; --because of substitution of setval with "alter sequence"
    --EXECUTE 'SELECT setval( "' || seqname || '", ' || cast(c as character varying) || ', false)'; DOES NOT WORK!!!
    EXECUTE 'alter sequence ' || seqname ||' restart with ' || cast(c as character varying);
    RETURN nextval(seqname)-1;
  END;
$body$ LANGUAGE 'plpgsql';

select sequence_name, PG_CLASS.relname, PG_ATTRIBUTE.attname,
       reset_sequence(PG_CLASS.relname,PG_ATTRIBUTE.attname)
from PG_CLASS
join PG_ATTRIBUTE on PG_ATTRIBUTE.attrelid = PG_CLASS.oid
join information_schema.sequences
     on information_schema.sequences.sequence_name = PG_CLASS.relname || '_' || PG_ATTRIBUTE.attname || '_seq'
where sequence_schema='public';

4

Перепроверьте всю последовательность в публичной функции схемы

CREATE OR REPLACE FUNCTION public.recheck_sequence (
)
RETURNS void AS
$body$
DECLARE
  _table_name VARCHAR;
  _column_name VARCHAR;  
  _sequence_name VARCHAR;
BEGIN
  FOR _table_name IN SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public' LOOP
    FOR _column_name IN SELECT column_name FROM information_schema.columns WHERE table_name = _table_name LOOP
        SELECT pg_get_serial_sequence(_table_name, _column_name) INTO _sequence_name;
        IF _sequence_name IS NOT NULL THEN 
            EXECUTE 'SELECT setval('''||_sequence_name||''', COALESCE((SELECT MAX('||quote_ident(_column_name)||')+1 FROM '||quote_ident(_table_name)||'), 1), FALSE);';
        END IF;
    END LOOP;   
  END LOOP;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;

3

Чтобы перезапустить всю последовательность до 1, используйте:

-- Create Function
CREATE OR REPLACE FUNCTION "sy_restart_seq_to_1" (
    relname TEXT
)
RETURNS "pg_catalog"."void" AS
$BODY$

DECLARE

BEGIN
    EXECUTE 'ALTER SEQUENCE '||relname||' RESTART WITH 1;';
END;
$BODY$

LANGUAGE 'plpgsql';

-- Use Function
SELECT 
    relname
    ,sy_restart_seq_to_1(relname)
FROM pg_class
WHERE relkind = 'S';

2

Ответ Клауса - самый полезный, за исключением небольшой ошибки: вы должны добавить DISTINCT в операторе select.

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

select sequence_name, --PG_CLASS.relname, PG_ATTRIBUTE.attname
       reset_sequence(split_part(sequence_name, '_id_seq',1))
from PG_CLASS
join PG_ATTRIBUTE on PG_ATTRIBUTE.attrelid = PG_CLASS.oid
join information_schema.sequences
     on information_schema.sequences.sequence_name = PG_CLASS.relname || '_' || PG_ATTRIBUTE.attname
where sequence_schema='public';

который является расширением решения user457226 для случая, когда какое-то интересующее имя столбца не является «ID».


... конечно, также необходимо изменить "reset_sequence", то есть добавить параметр "columnname", чтобы использовать вместо "id".
Мауро

2

Если вы видите эту ошибку при загрузке пользовательских данных SQL для инициализации, другой способ избежать этого:

Вместо того чтобы писать:

INSERT INTO book (id, name, price) VALUES (1 , 'Alchemist' , 10),

Удалить id(первичный ключ) из исходных данных

INSERT INTO book (name, price) VALUES ('Alchemist' , 10),

Это поддерживает последовательность Postgres в синхронизации!


2

Этот ответ является копией от Мауро.

drop function IF EXISTS rebuilt_sequences() RESTRICT;
CREATE OR REPLACE FUNCTION  rebuilt_sequences() RETURNS integer as
$body$
  DECLARE sequencedefs RECORD; c integer ;
  BEGIN
    FOR sequencedefs IN Select
      DISTINCT(constraint_column_usage.table_name) as tablename,
      constraint_column_usage.column_name as columnname,
      replace(replace(columns.column_default,'''::regclass)',''),'nextval(''','') as sequencename
      from information_schema.constraint_column_usage, information_schema.columns
      where constraint_column_usage.table_schema ='public' AND 
      columns.table_schema = 'public' AND columns.table_name=constraint_column_usage.table_name
      AND constraint_column_usage.column_name = columns.column_name
      AND columns.column_default is not null 
      ORDER BY sequencename
   LOOP    
      EXECUTE 'select max('||sequencedefs.columnname||') from ' || sequencedefs.tablename INTO c;
      IF c is null THEN c = 0; END IF;
      IF c is not null THEN c = c+ 1; END IF;
      EXECUTE 'alter sequence ' || sequencedefs.sequencename ||' minvalue '||c ||' start ' || c ||' restart  with ' || c;
   END LOOP;

   RETURN 1; END;
$body$ LANGUAGE plpgsql;

select rebuilt_sequences();

2

Я потратил час, пытаясь получить ответ djsnowsill для работы с базой данных, используя таблицы и столбцы Mixed Case, и, наконец, наткнулся на решение благодаря комментарию от Мануэля Дарво, но я подумал, что могу сделать его немного понятнее для всех:

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text)
RETURNS "pg_catalog"."void" AS
$body$
DECLARE
BEGIN
EXECUTE format('SELECT setval(pg_get_serial_sequence(''%1$I'', %2$L),
        (SELECT COALESCE(MAX(%2$I)+1,1) FROM %1$I), false)',tablename,columnname);
END;
$body$  LANGUAGE 'plpgsql';

SELECT format('%s_%s_seq',table_name,column_name), reset_sequence(table_name,column_name) 
FROM information_schema.columns WHERE column_default like 'nextval%';

Это имеет преимущество:

  • не предполагая, что идентификатор столбца пишется определенным образом.
  • не предполагая, что все таблицы имеют последовательность.
  • работает для смешанных имен таблиц / столбцов.
  • используя формат, чтобы быть более кратким.

Чтобы объяснить, проблема заключалась в том, что pg_get_serial_sequenceдля определения того, на что вы ссылаетесь , нужны строки, поэтому если вы это сделаете:

"TableName" --it thinks it's a table or column
'TableName' --it thinks it's a string, but makes it lower case
'"TableName"' --it works!

Это достигается с помощью ''%1$I''в строке формата, ''делает апостроф 1$означает первый аргумент и Iозначает в кавычках


2
select 'SELECT SETVAL(' || seq [ 1] || ', COALESCE(MAX('||column_name||')+1, 1) ) FROM '||table_name||';'
from (
       SELECT table_name, column_name, column_default, regexp_match(column_default, '''.*''') as seq
       from information_schema.columns
       where column_default ilike 'nextval%'
     ) as sequense_query

4
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и / или как этот код отвечает на вопрос, повышает его долгосрочную ценность.
Yeya

1

Гадкий взлом, чтобы исправить это с помощью некоторой магии оболочки, не очень хорошее решение, но может вдохновить других на подобные проблемы :)

pg_dump -s <DATABASE> | grep 'CREATE TABLE' | awk '{print "SELECT setval(#" $3 "_id_seq#, (SELECT MAX(id) FROM " $3 "));"}' | sed "s/#/'/g" | psql <DATABASE> -f -

0

Попробуйте переиндексировать .

ОБНОВЛЕНИЕ: Как отмечено в комментариях, это было в ответе на оригинальный вопрос.


Reindex не работал, он только увеличивает индекс на 1
meleyal

3
Reindex не работал, потому что он отвечал на ваш первоначальный вопрос, об индексах базы данных, а не о последовательностях
Vinko Vrsalovic

0

SELECT setval... делает JDBC борком, поэтому вот Java-совместимый способ сделать это:

-- work around JDBC 'A result was returned when none was expected.'
-- fix broken nextval due to poorly written 20140320100000_CreateAdminUserRoleTables.sql
DO 'BEGIN PERFORM setval(pg_get_serial_sequence(''admin_user_role_groups'', ''id''), 1 + COALESCE(MAX(id), 0), FALSE) FROM admin_user_role_groups; END;';

0

Метод для обновления всех последовательностей в вашей схеме, которые используются в качестве идентификатора:

DO $$ DECLARE
  r RECORD;
BEGIN
FOR r IN (SELECT tablename, pg_get_serial_sequence(tablename, 'id') as sequencename
          FROM pg_catalog.pg_tables
          WHERE schemaname='YOUR_SCHEMA'
          AND tablename IN (SELECT table_name 
                            FROM information_schema.columns 
                            WHERE table_name=tablename and column_name='id')
          order by tablename)
LOOP
EXECUTE
        'SELECT setval(''' || r.sequencename || ''', COALESCE(MAX(id), 1), MAX(id) IS NOT null)
         FROM ' || r.tablename || ';';
END LOOP;
END $$;


0

Здесь много хороших ответов. У меня возникла такая же необходимость после перезагрузки базы данных Django.

Но мне нужно было:

  • Все в одной функции
  • Может исправить одну или несколько схем одновременно
  • Может исправить все или только один стол за один раз
  • Также хотел хороший способ увидеть, что именно изменилось, или не изменилось

Кажется, это очень похоже на то, что было в оригинальном запросе.
Благодаря Балдири и Мауро вывели меня на правильный путь.

drop function IF EXISTS reset_sequences(text[], text) RESTRICT;
CREATE OR REPLACE FUNCTION reset_sequences(
    in_schema_name_list text[] = '{"django", "dbaas", "metrics", "monitor", "runner", "db_counts"}',
    in_table_name text = '%') RETURNS text[] as
$body$
  DECLARE changed_seqs text[];
  DECLARE sequence_defs RECORD; c integer ;
  BEGIN
    FOR sequence_defs IN
        select
          DISTINCT(ccu.table_name) as table_name,
          ccu.column_name as column_name,
          replace(replace(c.column_default,'''::regclass)',''),'nextval(''','') as sequence_name
          from information_schema.constraint_column_usage ccu,
               information_schema.columns c
          where ccu.table_schema = ANY(in_schema_name_list)
            and ccu.table_schema = c.table_schema
            AND c.table_name = ccu.table_name
            and c.table_name like in_table_name
            AND ccu.column_name = c.column_name
            AND c.column_default is not null
          ORDER BY sequence_name
   LOOP
      EXECUTE 'select max(' || sequence_defs.column_name || ') from ' || sequence_defs.table_name INTO c;
      IF c is null THEN c = 1; else c = c + 1; END IF;
      EXECUTE 'alter sequence ' || sequence_defs.sequence_name || ' restart  with ' || c;
      changed_seqs = array_append(changed_seqs, 'alter sequence ' || sequence_defs.sequence_name || ' restart with ' || c);
   END LOOP;
   changed_seqs = array_append(changed_seqs, 'Done');

   RETURN changed_seqs;
END
$body$ LANGUAGE plpgsql;

Затем выполнить и увидеть изменения:

select *
from unnest(reset_sequences('{"django", "dbaas", "metrics", "monitor", "runner", "db_counts"}'));

Возвращает

activity_id_seq                          restart at 22
api_connection_info_id_seq               restart at 4
api_user_id_seq                          restart at 1
application_contact_id_seq               restart at 20
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.