ChatGPT解决这个技术问题 Extra ChatGPT

How to reset postgres' primary key sequence when it falls out of sync?

I ran into the problem that my primary key sequence is not in sync with my table rows.

That is, when I insert a new row I get a duplicate key error because the sequence implied in the serial datatype returns a number that already exists.

It seems to be caused by import/restores not maintaining the sequence properly.

I am curious.. are you dropping the db before you do a restore? I have a faint recollection of this happening, but I could be wrong :P
The PostgreSQL wiki has a page on Fixing Sequences.
Just to aid googleability, the error message thrown here is: "duplicate key value violates unique constraint ..."
This is how sqlsequencereset in Django does it : SELECT setval(pg_get_serial_sequence("",'id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "";
The first instance of the needs to be wrapped in single quotes for the pg_get_serioal_sequence function to work: SELECT setval(pg_get_serial_sequence('','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM ""

C
Craig Ringer
-- 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;

Source - Ruby Forum


At any rate, adding 1 to MAX(id) will leave a single number gap in your IDs, since what setval sets is the last value of the sequence, not the next.
Your example will not work if there is no rows in the table. So there SQL given bellow is more safe: SELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), true);
@Valery: But in order to avoid gaps mentioned by @mikl two comments above, you need SELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
All issues solved and combined into a single query: SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
If your application cares about gaps in sequences, your application is broken. Gaps in sequences are normal, and can occur due to unplanned database shutdowns, transaction rollbacks after errors, etc.
F
FoolishSeth

pg_get_serial_sequence can be used to avoid any incorrect assumptions about the sequence name. This resets the sequence in one shot:

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

Or more concisely:

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

However this form can't handle empty tables correctly, since max(id) is null, and neither can you setval 0 because it would be out of range of the sequence. One workaround for this is to resort to the ALTER SEQUENCE syntax i.e.

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

But ALTER SEQUENCE is of limited use because the sequence name and restart value cannot be expressions.

It seems the best all-purpose solution is to call setval with false as the 3rd parameter, allowing us to specify the "next value to use":

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

This ticks all my boxes:

avoids hard-coding the actual sequence name handles empty tables correctly handles tables with existing data, and does not leave a hole in the sequence

Finally, note that pg_get_serial_sequence only works if the sequence is owned by the column. This will be the case if the incrementing column was defined as a serial type, however if the sequence was added manually it is necessary to ensure ALTER SEQUENCE .. OWNED BY is also performed.

i.e. if serial type was used for table creation, this should all work:

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;

But if sequences were added manually:

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;

There's no need in '+1' in the query, setval() sets current value, and nextval() will already return current value +1.
Function wrapping this method that takes one parameter - table_name - is in my answer below: stackoverflow.com/a/13308052/237105
@AntonyHatchkins cheers. Just saw another repeat of the +1 bug so finally swatted that for good I hope
you can also specify schema name when needed:pg_get_serial_sequence('schema_name.table_name', 'id')
there is typo in last line, it should be t2 in the end
E
Erwin Brandstetter

The shortest and fastest way

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

tbl_id being the serial or IDENTITY column of table tbl, drawing from the sequence tbl_tbl_id_seq (resulting default name). See:

Auto increment table column

If you don't know the name of the attached sequence (which doesn't have to be in default form), use pg_get_serial_sequence() (works for IDENTITY, too):

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

There is no off-by-one error here. The manual:

The two-parameter form sets the sequence's last_value field to the specified value and sets its is_called field to true, meaning that the next nextval will advance the sequence before returning a value.

Bold emphasis mine.

If the table can be empty, and to actually start from 1 in this case:

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

We can't just use the 2-parameter form and start with 0 because the lower bound of sequences is 1 by default (unless customized).

Safe under concurrent write load

To also defend against concurrent sequence activity or writes, lock the table in SHARE mode. It keeps concurrent transactions from writing a higher number (or anything at all).

To also take clients into account that may have fetched sequence numbers in advance without any locks on the main table, yet (can happen in certain setups), only increase the current value of the sequence, never decrease it. That may seem paranoid, but that's in accord with the nature of sequences and defending against concurrency issues.

BEGIN;

LOCK TABLE tbl IN SHARE MODE;

SELECT setval('tbl_tbl_id_seq', max(tbl_id))
FROM   tbl
HAVING max(tbl_id) > (SELECT last_value FROM tbl_tbl_id_seq); -- prevent lower number

COMMIT;

SHARE mode is strong enough for the purpose. The manual:

This mode protects a table against concurrent data changes.

It conflicts with ROW EXCLUSIVE mode.

The commands UPDATE, DELETE, and INSERT acquire this lock mode on the target table.


Where the "STANDARD community-library of essencial functions"? The second select clause of this answer in an EXECUTE format() (like @EB.'s) is an essencial function! How to fix this lack of standard library in PostgreSQL????
Doesn't matter if there's an off-by-one. Gaps in sequences are normal. If your app can't cope, your app is broken, because gaps can also arise due to transaction rollbacks, unplanned server shutdowns, etc.
@Craig: The off-by-one error I addressed (and isn't there) would matter since we'd risk a duplicate key error otherwise. The opposite direction of your considerations; seems like a misunderstanding.
ah, makes sense.
Great answer! A caveat is the examples are confusing because the table and column names are so similar to each other… Here is an updated example for table "roles" and sequenced column "id": SELECT setval('roles_id_seq', max(id)) FROM roles;
D
David Buck

This will reset all sequences from public making no assumptions about table or column names. Tested on version 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 very useful function! Our sequence names didn't match the table names exactly, so I used substring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'. Works perfectly.
Note that this will fail with sequence names containing single quotes, or table names with capitals, spaces, etc in their name. The quote_literal and quote_ident functions, or preferably the format function, should really be used here.
Wish I could give this more than one vote...nicely done sir. Works great on Postgres 9.1 as well, for me at least.
This is great. I used substring(column_default from 'nextval\(''(.+)''::regclass\)') to explicitly grab the sequence name. Worked like a charm.
I was searching for this solution for more than a day now, Thanks a lot, even i used the method suggested by @ChrisLercher, for replacing the text substring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
t
tardate

ALTER SEQUENCE sequence_name RESTART WITH (SELECT max(id) FROM table_name); Doesn't work.

Copied from @tardate answer:

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

that's a syntax error for me in 8.4 (at ^(SELECT... ). RESTART WITH seems to only accept an ordinal value. This works though: SELECT setval(pg_get_serial_sequence('table_name', 'id'), (SELECT MAX(id) FROM table_name)+1);
Muruges's solution doesn't work in 9.4 either. Don't understand why so much upvotes on this answer. ALTER SEQUENCE doesn't allow subqueries. Solution by @tardate works perfectly. Edited answer to remove incorrect data.
ALTER SEQUENCE worked perfect for me. I had uses COPY to bring in some data and there were gaps in the primary keys and INSERT's were throwing duplicate key exceptions. Setting the sequence did the trick. 9.4
you can also specify schema name when needed:pg_get_serial_sequence('schema_name.table_name', 'id')
J
Jonatas Walker

This command for only change auto generated key sequence value in postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

In place of zero you can put any number from which you want to restart sequence.

default sequence name will "TableName_FieldName_seq". For example, if your table name is "MyTable" and your field name is "MyID", then your sequence name will be "MyTable_MyID_seq".

This is answer is same as @murugesanponappan's answer, but there is a syntax error in his solution. you can not use sub query (select max()...) in alter command. So that either you have to use fixed numeric value or you need to use a variable in place of sub query.


This is the perfect solution thank you very much sir. But in my case I had an error, so I had to change it to ALTER SEQUENCE "your_sequence_name" RESTART WITH 1;
D
DevonDahon

In the example below, the table name is users and the schema name is public (default schema), replace it according to your needs.

1. Check the max id:

SELECT MAX(id) FROM public.users;

2. Check the next value:

SELECT nextval('public."users_id_seq"');

3. If the next value is lower than the max id, reset it:

SELECT setval('public."users_id_seq"',
  (SELECT MAX(id) FROM public.users)
);

Note:

nextval() will increment the sequence before returning the current value while currval() would just return the current value, as documented here.


E
EB.

Reset all sequences, no assumptions about names except that the primary key of each table is "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%';

Worked perfectly on my 9.1 version
You need to add quote if table contains upper case: pg_get_serial_sequence(''"' || tablename || '"''
This is the best function! You can avoid quote problems (and enhance elegance) with format, something like EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
a
alvherre

These functions are fraught with perils when sequence names, column names, table names or schema names have funny characters such as spaces, punctuation marks, and the like. I have written this:

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

You can call it for a single sequence by passing it the OID and it will return the highest number used by any table that has the sequence as default; or you can run it with a query like this, to reset all the sequences in your database:

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

Using a different qual you can reset only the sequence in a certain schema, and so on. For example, if you want to adjust sequences in the "public" schema:

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

Note that due to how setval() works, you don't need to add 1 to the result.

As a closing note, I have to warn that some databases seem to have defaults linking to sequences in ways that do not let the system catalogs have full information of them. This happens when you see things like this in psql's \d:

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

Note that the nextval() call in that default clause has a ::text cast in addition to the ::regclass cast. I think this is due to databases being pg_dump'ed from old PostgreSQL versions. What will happen is that the function sequence_max_value() above will ignore such a table. To fix the problem, you can redefine the DEFAULT clause to refer to the sequence directly without the cast:

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

Then psql displays it properly:

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

As soon as you've fixed that, the function works correctly for this table as well as all others that might use the same sequence.


This is amazing thanx! It should be noted that I needed to add a cast at the assignment (line 21 in the function code) like this: newmax := r.max::bigint; to make it work correctly for me.
Had to change this as well: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' notice the added ::bigint cast within the dynamically build query.
C
Community

I suggest this solution found on postgres wiki. It updates all sequences of your tables.

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;

How to use(from postgres wiki):

Save this to a file, say 'reset.sql'

Run the file and save its output in a way that doesn't include the usual headers, then run that output. Example:

Example:

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

Original article(also with fix for sequence ownership) here


Oh that was a silly mistake, In my case data had been migrated into the postgres DB instead of sentry. I hope it'll help others
V
Vao Tsun

Yet another plpgsql - resets only if 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;
$$
;

also commenting the line --execute format('alter sequence will give the list, not actually resetting the value


u
user457226

Reset all sequence from public

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

It appears that this approach make assumptions about the column and tables names so it didn't work for me
Would not that damage data in the database?
Y
Yehia Amer

This issue happens with me when using entity framework to create the database and then seed the database with initial data, this makes the sequence mismatch.

I Solved it by Creating a script to run after seeding the database:

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$

why the MAX("Id") + 1 it works best for me when the sequence is = to the max.
where need to run this script? I mean pgAdmin or command line ?
How could I run this script after a success Talend job?
I like this solution the most, but the answer has two problems: does not work with empty tables and create gap (offset by 1). This is the fixed version with also info on how to call it: gist.github.com/lzap/a6a9554211d546da52300cc0cd937e60
I
Ian Bytchek

Some really hardcore answers here, I'm assuming it used to be really bad at around the time when this has been asked, since a lot of answers from here don't works for version 9.3. The documentation since version 8.0 provides an answer to this very question:

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

Also, if you need to take care of case-sensitive sequence names, that's how you do it:

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

D
Daniel Cristian Cruz

My version use the first one, with some error checking...

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;

Thank you for the error checking! Much appreciated as the table/column names get truncated if they're too long, which your RAISE WARNING identified for me.
A
Antony Hatchkins

Putting it all together

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

will fix 'id' sequence of the given table (as usually necessary with django for instance).


a
anydasa

Recheck all sequence in public schema function

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;

m
mauro

before I had not tried yet the code : in the following I post the version for the sql-code for both Klaus and user457226 solutions which worked on my pc [Postgres 8.3], with just some little adjustements for the Klaus one and of my version for the user457226 one.

Klaus solution :

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 solution :

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

B
Baldiry

This answer is a copy from mauro.

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

This works well as a single pass script to fix all sequences in your db
u
user

If you see this error when you are loading custom SQL data for initialization, another way to avoid this is:

Instead of writing:

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

Remove the id (primary key) from initial data

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

This keeps the Postgres sequence in sync !


S
Stanislav Yanev

To restart all sequence to 1 use:

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

m
mauro

The Klaus answer is the most useful, execpt for a little miss : you have to add DISTINCT in select statement.

However, if you are sure that no table+column names can be equivalent for two different tables, you can also use :

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

which is an extension of user457226 solution for the case when some interested column name is not 'ID'.


...of course, also a change in "reset_sequence" is needed, that is adding a "columnname" parameter, to use instead of "id".
N
Nintynuts

I spent an hour trying to get djsnowsill's answer to work with a database using Mixed Case tables and columns, then finally stumbled upon the solution thanks to a comment from Manuel Darveau, but I thought I could make it a bit clearer for everyone:

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%';

This has the benefit of:

not assuming ID column is spelled a particular way.

not assuming all tables have a sequence.

working for Mixed Case table/column names.

using format to be more concise.

To explain, the problem was that pg_get_serial_sequence takes strings to work out what you're referring to, so if you do:

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

This is achieved using ''%1$I'' in the format string, '' makes an apostrophe 1$ means first arg, and I means in quotes


М
Михаил Шатилов
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

While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value.
A
Asad Rao

Just run below command:

SELECT setval('my_table_seq', (SELECT max(id) FROM my_table));

W
Wolph

Ugly hack to fix it using some shell magic, not a great solution but might inspire others with similar problems :)

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 -

N
Nick Van Berckelaer

A method to update all sequences in your schema that are used as an ID:

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

A
Alexi Theodore

So I can tell there aren't enough opinions or reinvented wheels in this thread, so I decided to spice things up.

Below is a procedure that:

is focused (only affects) on sequences that are associated with tables

works for both SERIAL and GENERATED AS IDENTITY columns

works for good_column_names and "BAD_column_123" names

automatically assigns the respective sequences' defined start value if the table is empty

allows for a specific sequences to be affected only (in schema.table.column notation)

has a preview mode

CREATE OR REPLACE PROCEDURE pg_reset_all_table_sequences(
    IN commit_mode BOOLEAN DEFAULT FALSE
,   IN mask_in TEXT DEFAULT NULL
) AS
$$
DECLARE
    sql_reset TEXT;
    each_sec RECORD;
    new_val TEXT;
BEGIN

sql_reset :=
$sql$
SELECT setval(pg_get_serial_sequence('%1$s.%2$s', '%3$s'), coalesce(max("%3$s"), %4$s), false) FROM %1$s.%2$s;
$sql$
;

FOR each_sec IN (

    SELECT
        quote_ident(table_schema) as table_schema
    ,   quote_ident(table_name) as table_name
    ,   column_name
    ,   coalesce(identity_start::INT, seqstart) as min_val
    FROM information_schema.columns
    JOIN pg_sequence ON seqrelid = pg_get_serial_sequence(quote_ident(table_schema)||'.'||quote_ident(table_name) , column_name)::regclass
    WHERE
        (is_identity::boolean OR column_default LIKE 'nextval%') -- catches both SERIAL and IDENTITY sequences

    -- mask on column address (schema.table.column) if supplied
    AND coalesce( table_schema||'.'||table_name||'.'||column_name = mask_in, TRUE )
)
LOOP

IF commit_mode THEN
    EXECUTE format(sql_reset, each_sec.table_schema, each_sec.table_name, each_sec.column_name, each_sec.min_val) INTO new_val;
    RAISE INFO 'Resetting sequence for: %.% (%) to %'
        ,   each_sec.table_schema
        ,   each_sec.table_name
        ,   each_sec.column_name
        ,   new_val
    ;
ELSE
    RAISE INFO 'Sequence found for resetting: %.% (%)'
        ,   each_sec.table_schema
        ,   each_sec.table_name
        ,   each_sec.column_name
    ;
END IF
;

END LOOP;

END
$$
LANGUAGE plpgsql
;

to preview:

call pg_reset_all_table_sequences();

to commit:

call pg_reset_all_table_sequences(true);

to specify only your target table:

call pg_reset_all_table_sequences('schema.table.column');


Please change setval(.. false) to true! Ref: postgresql.org/docs/current/sql-altersequence.html "restart...This is similar to calling the setval function with is_called = false: the specified value will be returned by the next call of nextval. ..." (I got key violation with an INSERT because my NEXTVAL was the same as MAX(ID), using your procedure.)
H
Hank Gay

Try reindex.

UPDATE: As pointed out in the comments, this was in reply to the original question.


reindex didn't work, it only seems to increment the index by 1
reindex didn't work because it was answering your original question, about database indexes, not sequences
b
brianwaganer

There are a lot of good answers here. I had the same need after reloading my Django database.

But I needed:

All in one Function

Could fix one or more schemas at a time

Could fix all or just one table at a time

Also wanted a nice way to see exactly what had changed, or not changed

This seems very similar need to what the original ask was for. Thanks to Baldiry and Mauro got me on the right track.

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;

Then to Execute and See the changes run:

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

Returns

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

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now