关于postgresql:如何在postgres不同步时重置postgres的主键序列?

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

我遇到了我的主键序列与我的表行不同步的问题。

也就是说,当我插入一个新行时,我得到一个重复的键错误,因为串行数据类型中隐含的序列返回一个已经存在的数字。

这似乎是由导入/恢复不能正确维护序列引起的。

来源 - Ruby论坛


pg_get_serial_sequence可用于避免对序列名称的任何不正确的假设。这会一次性重置序列:

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

或者更简洁:

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

但是这个表单无法正确处理空表,因为max(id)为null,并且你也不能setval 0因为它超出了序列的范围。一种解决方法是采用ALTER SEQUENCE语法,即

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

ALTER SEQUENCE的用途有限,因为序列名称和重启值不能是表达式。

似乎最好的通用解决方案是使用false作为第三个参数调用setval,允许我们指定"下一个要使用的值":

1
SELECT SETVAL(pg_get_serial_sequence('t1', 'id'), COALESCE(MAX(id),0) + 1, FALSE) FROM t1;

这勾选了我的所有方框:

  • 避免硬编码实际的序列名称
  • 正确处理空表
  • 处理具有现有数据的表,并且不会留下
    序列中的洞
  • 最后,请注意pg_get_serial_sequence仅在序列归列所有时才有效。如果将递增列定义为serial类型,则会出现这种情况,但是如果手动添加序列,则必须确保也执行ALTER SEQUENCE .. OWNED BY

    即如果serial类型用于表创建,这应该都有效:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;

    但是如果手动添加序列:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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;


    最短最快的方式:

    1
    SELECT SETVAL('tbl_tbl_id_seq', MAX(tbl_id)) FROM tbl;

    tbl_id是表tblserial列,从序列tbl_tbl_id_seq(这是默认的自动名称)中提取。

    如果您不知道附加序列的名称(不必是默认格式),请使用pg_get_serial_sequence()

    1
    SELECT SETVAL(pg_get_serial_sequence('tbl', 'tbl_id'), MAX(tbl_id)) FROM tbl;

    这里没有一个错误的错误。每个文件:

    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.

    大胆强调我的。

    并发

    但是,在上述查询中,并没有防止并发序列活动或写入表。如果这是相关的,您可以将表锁定为独占模式。当您尝试同步时,它可以防止并发事务写入更高的数字。 (它还会暂时阻止无害的写入而不会弄乱最大数量。)

    但它没有考虑到客户端可能提前获取序列号而没有主表上的任何锁定(但可能发生)。为了实现这一点,也只增加序列的当前值,而不是减少它。它可能看起来很偏执,但这符合序列的本质和防御并发问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;


    这将重置所有公共序列,不对表名或列名进行假设。在8.4版本上测试过

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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%';


    ALTER SEQUENCE sequence_name RESTART WITH(SELECT max(id)FROM table_name);
    不行。

    复制自@tardate回答:

    1
    SELECT SETVAL(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM TABLE_NAME;


    此命令仅用于更改postgresql中自动生成的键序列值

    1
    ALTER SEQUENCE"your_sequence_name" RESTART WITH 0;

    代替零,您可以放置??要重新启动序列的任何数字。

    默认序列名称将"TableName_FieldName_seq"。例如,如果表名为"MyTable"且字段名称为"MyID",则序列名称将为"MyTable_MyID_seq"

    答案与@ murugesanponappan的答案相同,但他的解决方案中存在语法错误。您不能在alter命令中使用子查询(select max()...)。因此,您必须使用固定数值,或者需要使用变量代替子查询。


    重置所有序列,没有关于名称的假设,除了每个表的主键是"id":

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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%';


    当序列名,列名,表名或模式名具有空格,标点符号等有趣字符时,这些函数充满了危险。我写了这个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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传递它来为单个序列调用它,它将返回任何具有默认序列的表所使用的最大数字;或者您可以使用这样的查询运行它,以重置数据库中的所有序列:

    1
    2
    3
     SELECT relname, SETVAL(oid, sequence_max_value(oid))
       FROM pg_class
      WHERE relkind = 'S';

    使用不同的qual可以仅重置某个模式中的序列,依此类推。例如,如果要调整"公共"模式中的序列:

    1
    2
    3
    4
    5
    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的 d中看到这样的事情时会发生这种情况:

    1
    2
    3
    4
    5
    alvherre=# \d baz
                         Tabla ?public.baz?
     Columna |  Tipo   |                 Modificadores                  
    ---------+---------+------------------------------------------------
     a       | INTEGER | DEFAULT NEXTVAL(('foo_a_seq'::text)::regclass)

    请注意,除了:: regclass强制转换之外,该default子句中的nextval()调用还有一个:: text强制转换。我认为这是因为数据库是旧的PostgreSQL版本的pg_dump。会发生的是上面的函数sequence_max_value()将忽略这样的表。要解决此问题,您可以重新定义DEFAULT子句以直接引用序列而不使用强制转换:

    1
    2
    alvherre=# ALTER TABLE baz ALTER a SET DEFAULT NEXTVAL('foo_a_seq');
    ALTER TABLE

    然后psql正确显示它:

    1
    2
    3
    4
    5
    alvherre=# \d baz
                         Tabla ?public.baz?
     Columna |  Tipo   |             Modificadores              
    ---------+---------+----------------------------------------
     a       | INTEGER | DEFAULT NEXTVAL('foo_a_seq'::regclass)

    一旦修复了该函数,该函数就可以正常运行此表以及可能使用相同序列的所有其他函数。


    从公共重置所有序列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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';


    我的版本使用第一个,有一些错误检查...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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;


    这里有一些非常硬核的答案,我假设在这个问题的时候它曾经非常糟糕,因为这里的很多答案都不适用于9.3版本。自8.0版以来的文档提供了这个问题的答案:

    1
    SELECT SETVAL('serial', MAX(id)) FROM distributors;

    此外,如果您需要处理区分大小写的序列名称,那么您就是这样做的:

    1
    SELECT SETVAL('"Serial"', MAX(id)) FROM distributors;


    我建议在postgres wiki上找到这个解决方案。它会更新表的所有序列。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    SELECT 'SELECT SETVAL(' ||
           quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
           ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
           quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
    FROM pg_class AS S,
         pg_depend AS D,
         pg_class AS T,
         pg_attribute AS C,
         pg_tables AS PGT
    WHERE S.relkind = 'S'
        AND S.oid = D.objid
        AND D.refobjid = T.oid
        AND D.refobjid = C.attrelid
        AND D.refobjsubid = C.attnum
        AND T.relname = PGT.tablename
    ORDER BY S.relname;

    如何使用(来自postgres wiki):

    • 将其保存到文件中,例如'reset.sql'
    • 运行该文件并以不包含常用标题的方式保存其输出,然后运行该输出。例:

    例:

    1
    2
    3
    psql -Atq -f reset.sql -o temp
    psql -f temp
    rm temp

    原始文章(也有序列所有权的修复)在这里


    另一个plpgsql - 仅在max(att) > then lastval时重置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    do --check seq not in sync
    $$
    DECLARE
     _r record;
     _i BIGINT;
     _m BIGINT;
    BEGIN
      FOR _r IN (
        SELECT relname,nspname,d.refobjid::regclass, a.attname, refobjid
        FROM   pg_depend    d
        JOIN   pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
        JOIN pg_class r ON r.oid = objid
        JOIN pg_namespace n ON n.oid = relnamespace
        WHERE  d.refobjsubid > 0 AND  relkind = 'S'
       ) loop
        EXECUTE format('select last_value from %I.%I',_r.nspname,_r.relname) INTO _i;
        EXECUTE format('select max(%I) from %s',_r.attname,_r.refobjid) INTO _m;
        IF COALESCE(_m,0) > _i THEN
          raise info '%',concat('changed: ',_r.nspname,'.',_r.relname,' from:',_i,' to:',_m);
          EXECUTE format('alter sequence %I.%I restart with %s',_r.nspname,_r.relname,_m+1);
        END IF;
      END loop;

    END;
    $$
    ;

    同时注释行--execute format('alter sequence将给出列表,而不是实际重置值


    当使用实体框架创建数据库然后使用初始数据为数据库播种时,这个问题发生在我身上,这使得序列不匹配。

    我通过创建一个脚本来播种数据库后解决它:

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


    把它们放在一起

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

    将修复给定表的'id'序列(例如,通常需要使用django)。


    在我还没有尝试过代码之前:在下面我发帖
    Klaus和user457226解决方案的sql-code版本
    在我的电脑上工作[Postgres 8.3],只有一些小调整
    对于Klaus one和我的版本为user457226一个。

    克劳斯解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    --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';

    重新检查公共模式函数中的所有序列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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;

    要重新启动所有序列为1,请使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    -- 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';

    如果在为初始化加载自定义SQL数据时看到此错误,则另一种避免这种情况的方法是:

    而不是写:

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

    从初始数据中删除id(主键)

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

    这使Postgres序列保持同步!


    这个答案是毛罗的副本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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();

    我花了一个小时试图让djsnowsill的答案与使用混合案例表和列的数据库一起工作,然后由于Manuel Darveau的评论最终偶然发现了解决方案,但我想我可以让每个人都清楚一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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%';

    这有以下好处:

    • 不假设ID列以特定方式拼写。
    • 不假设所有表都有序列。
    • 适用于混合案例表/列名称。
    • 使用格式更简洁。

    要解释一下,问题是pg_get_serial_sequence需要字符串来计算你所指的内容,所以如果你这样做:

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

    这是使用格式字符串中的''%1$I''实现的,''使得撇号1$表示第一个arg,而I表示引号


    克劳斯的答案是最有用的,有点想念的人:你
    必须在select语句中添加DISTINCT。

    但是,如果您确定没有表+列名称可以等效
    对于两个不同的表,您还可以使用:

    1
    2
    3
    4
    5
    6
    7
    SELECT sequence_name, --PG_CLASS.relname, PG_ATTRIBUTE.attname
           reset_sequence(split_part(sequence_name, '_id_seq',1))
    FROM PG_CLASS
    JOIN PG_ATTRIBUTE ON PG_ATTRIBUTE.attrelid = PG_CLASS.oid
    JOIN information_schema.sequences
         ON information_schema.sequences.sequence_name = PG_CLASS.relname || '_' || PG_ATTRIBUTE.attname
    WHERE sequence_schema='public';

    这是user457226解决方案的扩展,适用于何时
    一些感兴趣的列名不是'ID'。


    1
    2
    3
    4
    5
    6
    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


    丑陋的黑客使用一些shell魔法修复它,不是一个很好的解决方案,但可能激发其他类似的问题:)

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


    尝试reindex。

    更新:正如评论中所指出的,这是对原始问题的回答。


    一种更新架构中用作ID的所有序列的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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 $$;

    SELECT setval...使得JDBC变成了bork,所以这里有一种与Java兼容的方法:

    1
    2
    3
    -- 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;';