Как сбросить последовательность первичных ключей postgres, если они не синхронизированы? - PullRequest
453 голосов
/ 28 октября 2008

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

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

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

Ответы [ 27 ]

634 голосов
/ 28 октября 2008
-- 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;

Источник - Ruby Forum

181 голосов
/ 13 сентября 2010

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;
66 голосов
/ 30 апреля 2014

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

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 продвинет последовательность перед возвратом значения.

Смелый акцент мой.

параллелизм

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

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

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;
49 голосов
/ 05 ноября 2010

Это сбросит все последовательности из общедоступных, не делая предположений о именах таблиц или столбцов. Проверено на версии 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%';
36 голосов
/ 10 декабря 2008

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;
19 голосов
/ 01 сентября 2014

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

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

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

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

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

16 голосов
/ 31 января 2013

Сброс всех последовательностей, без предположений об именах, за исключением того, что первичным ключом каждой таблицы является «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%';
12 голосов
/ 10 мая 2011

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

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)

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

6 голосов
/ 13 сентября 2011

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

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;
6 голосов
/ 07 ноября 2014

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

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

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

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