How to reset Postgres' primary key sequence when it falls out of sync?
Asked Answered
S

35

736

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.

Smaragd answered 28/10, 2008 at 18:8 Comment(7)
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 :PHeady
The PostgreSQL wiki has a page on Fixing Sequences.Heilungkiang
Just to aid googleability, the error message thrown here is: "duplicate key value violates unique constraint ..."Janina
This is how sqlsequencereset in Django does it : SELECT setval(pg_get_serial_sequence("<table_name>",'id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "<table_name>";Unchain
The first instance of the <table name> needs to be wrapped in single quotes for the pg_get_serioal_sequence function to work: SELECT setval(pg_get_serial_sequence('<table_name>','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "<table_name>"Wiebmer
Related question: #62060447Deed
#244743Mardis
S
954
-- 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

Smaragd answered 28/10, 2008 at 18:14 Comment(20)
Thanks, phpPgAdmin has it under a tab named 'Indexes' so I thought this was the right naming. So I guess 'sequence' is the property of the 'index' that gets updated?Smaragd
The sequence is just a special value that gets incremented/set with nextval() and setval() functions. It's used primarily to create columns that will be incrementing automatically, you have a primary key that has as a default value 'nextval('primary_key_name_seq') and thus every time you insert ...Letter
... a new row, that value is obtained from the sequence. Indexes are a totally different beast, they also get automatically created for primary keys, but have nothing to do with which is the next value going to be. I haven't seen phpPgAdmin in ages, but I tend to think that that tab shows the ...Letter
... real indexes as well as the sequences, for example.Letter
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.Ammonate
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);Universe
@Valery: But in order to avoid gaps mentioned by @Ammonate two comments above, you need SELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);Implicative
Do gaps actually make any difference?Algorithm
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))Heterograft
@Heterograft your query didn't work for me since the nextval was already too big. The one posted by Antony Hatchkins worked better for meBordereau
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.Feininger
The query submitted by @Heterograft works with a few adjustments: SELECT setval(pg_get_serial_sequence('table_name', 'id'), COALESCE(MAX(id), 1), MAX(id) IS NOT null) FROM table_name (note use of quotes)Anurag
Can someone please provide some color on how to obtain "your_table_id_seq" in Postgres?Yawl
Follow up on how to obtain sequence name for your table: postgresql.org/message-id/[email protected] SELECT table_name, column_name, column_default from information_schema.columns where table_name='your_table';Yawl
@Ammonate it depends on the boolean argument (deafults to true). If false then it sets the next value, if true then it sets the current.Organogenesis
Thank You for this, helped me solve an issue I was struggling with for weeks!Natalyanataniel
I need to explicitely define the type SELECT setval('your_table_id_seq'::text, COALESCE((SELECT MAX(id)+1 FROM your_table), 1)::bigint, false);Portion
Concrete example: if your table is named menu_item, then Frunsi's statement would be: SELECT setval('menu_item_id_seq',(SELECT GREATEST(MAX(id)+1,nextval('menu_item_id_seq'))-1 FROM menu_item));Gimmick
Keep in mind that passing 1|true as the third parameter will result in an advancement of value+1, where value is the second bigint parameter. Passing 0|false will use the number as-is for the next sequence value.Golub
If Users is our table name and that table has primary key Id. For the first query: SELECT MAX("Id") FROM "Users";. For the second query (nextvalue) you will need to make it like this SELECT nextval('"Users_Id_seq"');.Mori
V
316

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:

  1. avoids hard-coding the actual sequence name
  2. handles empty tables correctly
  3. 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;
Vaughan answered 13/9, 2010 at 8:19 Comment(8)
There's no need in '+1' in the query, setval() sets current value, and nextval() will already return current value +1.Implicative
Function wrapping this method that takes one parameter - table_name - is in my answer below: https://mcmap.net/q/28911/-how-to-reset-postgres-39-primary-key-sequence-when-it-falls-out-of-syncImplicative
@AntonyHatchkins cheers. Just saw another repeat of the +1 bug so finally swatted that for good I hopeVaughan
you can also specify schema name when needed:pg_get_serial_sequence('schema_name.table_name', 'id')Succory
there is typo in last line, it should be t2 in the endEloquent
Thaaaank you for ALTER SEQUENCE table_name_id_seq RESTART :-)Donnetta
You can also use ALTER TABLE table_name ALTER COLUMN id RESTART WITH 1 now which doesn't require the sequence name at all only the column.Directive
Thank yoooooouuu '''SELECT pg_catalog.setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;'''Mcclendon
D
209

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:

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.

Dorothy answered 30/4, 2014 at 14:7 Comment(6)
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????Erdah
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.Feininger
@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.Dorothy
ah, makes sense.Feininger
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;Rumen
Thanks for the example @MasaSakano - that made it much clearer.Exeunt
F
73

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 INTEGER AS 
    
    $body$  
      DECLARE 
      retval  INTEGER;
      BEGIN 
    
      EXECUTE 'SELECT setval( ''' || sequence_name  || ''', ' || '(SELECT MAX(' || columnname || 
          ') FROM "' || tablename || '")' || '+1)' INTO retval;
      RETURN retval;
      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%';
Fremitus answered 4/11, 2010 at 21:3 Comment(5)
+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.Mize
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.Feininger
Wish I could give this more than one vote...nicely done sir. Works great on Postgres 9.1 as well, for me at least.Sp
This is great. I used substring(column_default from 'nextval\(''(.+)''::regclass\)') to explicitly grab the sequence name. Worked like a charm.Labiche
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'Disposal
P
48

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;
Packston answered 10/12, 2008 at 8:29 Comment(4)
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);Vaughan
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 @Vaughan works perfectly. Edited answer to remove incorrect data.Generation
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.4Henrie
you can also specify schema name when needed:pg_get_serial_sequence('schema_name.table_name', 'id')Succory
K
33

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.

Kieffer answered 7/4, 2021 at 9:13 Comment(0)
J
25

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.

Jaques answered 1/9, 2014 at 10:29 Comment(1)
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;Aedes
S
20

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%';
Saberhagen answered 31/1, 2013 at 19:0 Comment(5)
Worked perfectly on my 9.1 versionTug
You need to add quote if table contains upper case: pg_get_serial_sequence(''"' || tablename || '"''Luthern
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 );Erdah
The code is incorrect - you hard-coded the id columnCentavo
Yes boggy, that is what it says in the answer above the code snippet: "except that the primary key of each table is id".Saberhagen
M
16

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

Magneto answered 25/7, 2016 at 19:29 Comment(3)
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 othersJarrell
This code is buggy. In particular, if you have no rows in a table, it doesn't set the next sequence value to 1. It doesn't reset it at all.Inedible
I'll post a fixed version here.Inedible
S
14

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.

Shirtwaist answered 9/5, 2011 at 22:20 Comment(2)
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.Jeopardous
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.Jeopardous
M
10

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

Millen answered 20/4, 2017 at 12:10 Comment(0)
L
9

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';
Ladybird answered 24/9, 2010 at 11:40 Comment(2)
It appears that this approach make assumptions about the column and tables names so it didn't work for meFremitus
Would not that damage data in the database?Sidhu
T
9

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$
Trawler answered 8/4, 2018 at 15:22 Comment(4)
why the MAX("Id") + 1 it works best for me when the sequence is = to the max.Inroad
where need to run this script? I mean pgAdmin or command line ?Languor
How could I run this script after a success Talend job?Cigarette
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/a6a9554211d546da52300cc0cd937e60Condiment
R
7

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;
Rochellrochella answered 7/11, 2014 at 12:34 Comment(0)
B
6

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;
Boccie answered 13/9, 2011 at 18:22 Comment(1)
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.Antlion
I
5

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

Implicative answered 9/11, 2012 at 12:26 Comment(0)
I
5

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;
Inbeing answered 2/6, 2015 at 10:15 Comment(0)
J
4

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';
Jaworski answered 9/3, 2011 at 21:50 Comment(0)
G
4

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();
Geophagy answered 16/6, 2016 at 19:5 Comment(1)
This works well as a single pass script to fix all sequences in your dbCabasset
U
3

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 !

Unchain answered 23/5, 2014 at 13:52 Comment(0)
E
3

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';
Elephantine answered 1/9, 2016 at 14:53 Comment(0)
C
3

Just run below command:

SELECT setval('my_table_seq', (SELECT max(id) FROM my_table));
Concernment answered 1/10, 2019 at 13:3 Comment(0)
B
3

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_temp.pg_reset_all_table_sequences(
    IN commit_mode BOOLEAN DEFAULT FALSE
,   IN mask_in TEXT DEFAULT NULL
) AS
$$

/*
USAGE:

CALL pg_temp.pg_reset_all_table_sequences(<commit? FALSE | TRUE >, '<schema.table.column use % for wildcard>');

Examples:

CALL pg_temp.pg_reset_all_table_sequences(FALSE, 'your_schema.%'); -- PREVIEWS updates all sequences on all tables and columns in "your_schema"
CALL pg_temp.pg_reset_all_table_sequences(TRUE, 'your_schema.%'); -- COMMITS updates all sequences on all tables and columns in "your_schema"
CALL pg_temp.pg_reset_all_table_sequences(FALSE); -- PREVIEWS updates on all sequences in the entire database
CALL pg_temp.pg_reset_all_table_sequences(TRUE); -- COMMITS updates on all sequences in the entire database !!! probably not a good thing for a production env ¡¡¡

*/

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), TRUE) 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 ILIKE 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
;
Bloodletting answered 7/10, 2020 at 19:21 Comment(0)
J
2

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

Jaworski answered 9/3, 2011 at 21:5 Comment(1)
...of course, also a change in "reset_sequence" is needed, that is adding a "columnname" parameter, to use instead of "id".Jaworski
I
2

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

Isa answered 5/7, 2017 at 17:0 Comment(0)
D
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
Dahl answered 10/3, 2019 at 16:53 Comment(1)
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.Influent
L
2

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 $$;
Lindeberg answered 15/5, 2019 at 18:53 Comment(0)
T
1

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 -
Tavey answered 27/5, 2013 at 22:38 Comment(0)
N
1
SELECT setval('sequencename', COALESCE((SELECT MAX(id)+1 FROM tablename), 1), false);
Neoclassicism answered 1/12, 2022 at 11:11 Comment(1)
Please read How to Answer and edit your answer to contain an explanation as to why this code would actually solve the problem at hand. Always remember that you're not only solving the problem, but are also educating the OP and any future readers of this post.Alienor
D
1

None of the above answers work for me because I am truncating and reinserting seed data for unit tests.

If the state of the table can be empty OR have rows you need to use the following:

SELECT COALESCE(MAX(id::INTEGER), 1),
        SETVAL(
            PG_GET_SERIAL_SEQUENCE('TABLE_NAME', 'id'),
            COALESCE(MAX(id::INTEGER), 1),
            COUNT(id::integer) > 0)
        FROM TABLE_NAME;

The COUNT(id::integer) > 0 is to prevent the sequence from being set to 1 if the table is empty. You want the sequence to equal 0 so when postgres internally calls nextval() it will return 1, but you cannot manually pass 0 into the sequence function (confusing right...).

I am assuming your id column is NOT NULLABLE. If it is nullable then the count may not function as expected.

Directive answered 6/9, 2023 at 0:29 Comment(0)
H
0

Try reindex.

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

Headband answered 28/10, 2008 at 18:10 Comment(2)
reindex didn't work, it only seems to increment the index by 1Smaragd
reindex didn't work because it was answering your original question, about database indexes, not sequencesLetter
M
0

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
Macassar answered 16/5, 2020 at 3:31 Comment(0)
G
0

I couldn't find an answer explicitly for Rails.

From the rails console

ActiveRecord::Base.connection.execute("SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;")

Replacing table_name with users for example.

Gastrotomy answered 29/1, 2022 at 17:44 Comment(0)
I
0

This version of the postgres wiki article correctly resets the next-id value for a sequence even if there are no rows in the the table.

It also resets the cache for the next id value, which the postgres wiki article ignored.

DO
$do$
DECLARE
   _sql text;
BEGIN
  FOR _sql IN
     SELECT
        'SELECT SETVAL(' ||
           quote_literal(quote_ident(sequence_namespace.nspname) || '.' || quote_ident(class_sequence.relname)) ||
           ', (SELECT COALESCE(MAX(' ||quote_ident(pg_attribute.attname)|| ') + 1, 1) FROM ' ||
           quote_ident(table_namespace.nspname)|| '.'||quote_ident(class_table.relname)|| '), FALSE );'
    FROM pg_depend
        INNER JOIN pg_class AS class_sequence
            ON class_sequence.oid = pg_depend.objid
                AND class_sequence.relkind = 'S'
        INNER JOIN pg_class AS class_table
            ON class_table.oid = pg_depend.refobjid
        INNER JOIN pg_attribute
            ON pg_attribute.attrelid = class_table.oid
                AND pg_depend.refobjsubid = pg_attribute.attnum
        INNER JOIN pg_namespace as table_namespace
            ON table_namespace.oid = class_table.relnamespace
        INNER JOIN pg_namespace AS sequence_namespace
            ON sequence_namespace.oid = class_sequence.relnamespace
    ORDER BY sequence_namespace.nspname, class_sequence.relname
  LOOP
    EXECUTE _sql;
  END LOOP;
END
$do$;
Inedible answered 25/3, 2023 at 21:46 Comment(0)
S
-1

SELECT setval... makes JDBC bork, so here's a Java-compatible way of doing this:

-- 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;';
Shroudlaid answered 21/5, 2015 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.