Como redefinir a sequência de teclas primárias do postgres quando ela fica fora de sincronia?

523

Encontrei o problema de que minha sequência de chaves primárias não está sincronizada com as linhas da minha tabela.

Ou seja, quando insiro uma nova linha, recebo um erro de chave duplicada porque a sequência implícita no tipo de dados serial retorna um número que já existe.

Parece ser causado pela importação / restauração que não mantém a sequência corretamente.

meleyal
fonte
Estou curioso .. você está descartando o db antes de fazer uma restauração? Tenho uma lembrança fraca disso acontecendo, mas posso estar errado: P
Arthur Thomas
25
O wiki do PostgreSQL possui uma página sobre Fixing Sequences .
22812 Brad Koch
14
Só para googleability ajuda, a mensagem de erro jogado aqui é: "valor de chave duplicado viola única restrição ..."
superluminary
4
É assim que o sqlsequencereset no Django faz: SELECT setval (pg_get_serial_sequence ("<table_name>", 'id'), coalescência (max ("id"), 1), max ("id") NÃO é nulo) FROM "< nome_tabela> ";
usuário
A primeira instância do <nome da tabela> precisa ser agrupada entre aspas simples para que a função pg_get_serioal_sequence funcione: SELECT setval (pg_get_serial_sequence ('<table_name>', 'id'), coalesce (max ("id"), 1) , max ("id") NÃO é nulo) FROM "<table_name>"
nclu 27/12/18

Respostas:

715
-- Login to psql and run the following

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

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

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

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

Fonte - Fórum Ruby

meleyal
fonte
12
De qualquer forma, adicionar 1 ao MAX (id) deixará um único intervalo de número nos seus IDs, já que setval sets é o último valor da sequência, não o próximo.
Mikl
6
Seu exemplo não funcionará se não houver linhas na tabela. Portanto, o SQL fornecido a seguir é mais seguro: SELECT setval ('your_table_id_seq', coalesce ((selecione max (id) +1 na sua_table), 1), true);
Valery Viktorovsky
10
@Valery: Mas, a fim de lacunas Evitar mencionado por @mikl dois comentários acima, você precisaSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins
20
Todas as questões foram resolvidas e combinadas em uma única consulta:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi 27/10
15
Se o seu aplicativo se importar com lacunas nas seqüências, ele será quebrado. Lacunas nas sequências são normais, e pode ocorrer devido a paradas não planejadas de banco de dados, reversões de transações após erros, etc.
Craig Ringer
202

pg_get_serial_sequencepode ser usado para evitar suposições incorretas sobre o nome da sequência. Isso redefine a sequência de uma vez:

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

Ou, mais concisamente:

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

No entanto, este formulário não pode manipular tabelas vazias corretamente, pois max (id) é nulo, e você também não pode definir o valor 0 porque estaria fora do intervalo da sequência. Uma solução alternativa para isso é recorrer à ALTER SEQUENCEsintaxe ie

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

Mas ALTER SEQUENCEé de uso limitado, porque o nome da sequência e o valor de reinicialização não podem ser expressões.

Parece que a melhor solução para todos os fins é chamar setvalfalse com o terceiro parâmetro, permitindo especificar o "próximo valor a ser usado":

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

Isso marca todas as minhas caixas:

  1. evita codificar o nome da sequência real
  2. lida com tabelas vazias corretamente
  3. lida com tabelas com dados existentes e não deixa um buraco na sequência

Por fim, observe que pg_get_serial_sequencesó funciona se a sequência pertencer à coluna. Este será o caso se a coluna de incremento tiver sido definida como um serialtipo, no entanto, se a sequência foi adicionada manualmente, é necessário garantir que ALTER SEQUENCE .. OWNED BYtambém seja realizada.

ou seja, se o serialtipo foi usado para a criação da tabela, tudo deve funcionar:

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;

Mas se as sequências foram adicionadas manualmente:

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;
atrasado
fonte
11
Não há necessidade de '+1' na consulta, setval()define o valor atual e nextval()já retornará o valor atual +1.
Antony Hatchkins 9/11/12
1
Função envolvendo este método que leva um parâmetro - table_name - está na minha resposta abaixo: stackoverflow.com/a/13308052/237105
Antony Hatchkins
@AntonyHatchkins felicidades. Só vi outra repetição do +1 bug assim, finalmente golpeou que, para boa esperança I
tardate
99

A maneira mais curta e rápida :

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

tbl_idsendo a serialcoluna da tabela tbl, desenhando a partir da sequência tbl_tbl_id_seq(que é o nome automático padrão).

Se você não souber o nome da sequência anexada (que não precisa estar no formato padrão), use pg_get_serial_sequence():

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

Não há erro de um por um aqui. Por documentação:

O formulário de dois parâmetros define o last_valuecampo da sequência para o valor especificado e o is_calledcampo para true, o que significa que o próximo nextvalavançará a sequência antes de retornar um valor.

Negrito ênfase minha.

Se a tabela puder estar vazia e realmente começar a partir de 1 neste caso:

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

Não podemos simplesmente usar o formulário com 2 paremater e começar 0porque o limite inferior das seqüências é 1 por padrão (a menos que seja personalizado).

Concorrência

Ainda não há defesa contra a atividade de sequência simultânea ou gravações na tabela nas consultas acima. Se isso for relevante, você pode bloquear a tabela no modo exclusivo. Isso evita que as transações simultâneas gravem um número mais alto enquanto você tenta sincronizar. (Ele também bloqueia temporariamente gravações inofensivas que não mexem com o número máximo.)

Mas não leva em conta os clientes que podem ter buscado números de sequência antecipadamente sem nenhum bloqueio na tabela principal (o que pode acontecer). Para permitir isso, também, apenas aumente o valor atual da sequência, nunca o diminua. Pode parecer paranóico, mas isso está de acordo com a natureza das seqüências e a defesa contra problemas de concorrência.

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;
Erwin Brandstetter
fonte
Onde está a "biblioteca comunitária PADRÃO de funções essenciais"? A segunda cláusula de seleção dessa resposta em um EXECUTE format()(como @ EB.'s) é uma função essencial! Como corrigir essa falta de biblioteca padrão no PostgreSQL ????
Peter Krauss
Não importa se há um off-by-one. Lacunas nas sequências são normais. Se o seu aplicativo não pode lidar, a sua aplicação está quebrado, porque lacunas também pode surgir devido a reversões de transação, paralisações de servidores não planejadas, etc
Craig campainha
1
@ Craig: O erro de um por um que eu resolvi (e não existe) seria importante, já que arriscaríamos um erro de chave duplicada. A direção oposta de suas considerações; parece um mal-entendido.
Erwin Brandstetter
ah, faz sentido.
Craig Ringer
Isso funciona para mim
hectk
54

Isso redefinirá todas as seqüências do público, sem suposições sobre nomes de tabelas ou colunas. Testado na versão 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%';
djsnowsill
fonte
1
+1 função muito útil! Nossos nomes de sequência não correspondiam exatamente aos nomes das tabelas, então usei em substring(column_default, '''(.*)''')vez de table_name || '_' || column_name || '_seq'. Funciona perfeitamente.
31712 Chris Lercher
4
Observe que isso falhará com nomes de sequência que contenham aspas simples ou nomes de tabelas com letras maiúsculas, espaços etc. no nome. As funções quote_literale quote_ident, ou de preferência a formatfunção, devem realmente ser usadas aqui.
Craig Ringer
2
Gostaria de poder dar a isso mais de um voto ... bem feito, senhor. Funciona muito bem no Postgres 9.1 também, pelo menos para mim.
Peelman # 8/14
1
Isso é ótimo. Eu costumava substring(column_default from 'nextval\(''(.+)''::regclass\)')pegar explicitamente o nome da sequência. Funcionou como um encanto.
Matthew MacDonald
Eu estava procurando por esta solução há mais de um dia, muito obrigado, mesmo que eu usei o método sugerido por @ChrisLercher, para substituir o textosubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv
43

ALTER SEQUENCE nome_da_seção RESTART WITH (SELECT max (id) FROM nome_tabela); Não funciona

Copiado da resposta @tardate:

SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;
atrasado
fonte
8
esse é um erro de sintaxe para mim na 8.4 (em ^ (SELECT ...). RESTART WITH parece aceitar apenas um valor ordinal. Isso funciona: SELECT setval (pg_get_serial_sequence ('table_name', 'id'), (SELECT MAX ( id) FROM nome_tabela) +1);
atrasado
1
A solução de Muruges também não funciona na versão 9.4. Não entenda por que tantos votos positivos nesta resposta. ALTER SEQUENCE não permite subconsultas. A solução da @tardate funciona perfeitamente. Resposta editada para remover dados incorretos.
Vladislav Rastrusny
ALTER SEQUENCE funcionou perfeitamente para mim. Eu tinha o uso de COPY para trazer alguns dados e havia lacunas nas chaves primárias e os INSERT lançavam exceções de chave duplicada. Definir a sequência fez o truque. 9.4
user542319
22

Este comando apenas altera o valor da sequência de chaves gerada automaticamente no postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

No lugar de zero, você pode colocar qualquer número a partir do qual deseja reiniciar a sequência.

o nome da sequência padrão será "TableName_FieldName_seq". Por exemplo, se o nome da tabela for "MyTable"e o nome do campo "MyID", o nome da sequência será "MyTable_MyID_seq".

Esta é a resposta é a mesma que a resposta de @ murugesanponappan, mas há um erro de sintaxe em sua solução. você não pode usar a subconsulta (select max()...)no altercomando. Para que você precise usar um valor numérico fixo ou precise usar uma variável no lugar da subconsulta.

Haider Ali Wajihi
fonte
Esta é a solução perfeita, muito obrigado, senhor. Mas, no meu caso, tive um erro, então tive que alterá-lo para ALTER SEQUENCE "your_sequence_name" RESTART WITH 1;
Deunz
18

Redefina todas as seqüências, sem suposições sobre nomes, exceto que a chave primária de cada tabela é "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%';
EB.
fonte
Funcionou perfeitamente no meu 9.1 versão
Valentin Vasilyev
Você precisa adicionar citação se a tabela contém letras maiúsculas:pg_get_serial_sequence(''"' || tablename || '"''
Manuel Darveau
Esta é a melhor função! Você pode evitar problemas de citação (e aumentar a elegância) com o formato, algo como EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss
13

Essas funções estão repletas de perigos quando nomes de sequências, nomes de colunas, nomes de tabelas ou esquemas possuem caracteres engraçados, como espaços, sinais de pontuação e similares. Eu escrevi isso:

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

Você pode chamá-lo para uma única sequência, passando o OID para ele e retornará o número mais alto usado por qualquer tabela que tenha a sequência como padrão; ou você pode executá-lo com uma consulta como esta, para redefinir todas as seqüências no seu banco de dados:

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

Usando uma qual diferente, você pode redefinir apenas a sequência em um determinado esquema e assim por diante. Por exemplo, se você deseja ajustar sequências no esquema "público":

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

Observe que, devido à forma como setval () funciona, você não precisa adicionar 1 ao resultado.

Como observação final, tenho que avisar que alguns bancos de dados parecem ter padrões vinculados a sequências de maneiras que não permitem que os catálogos do sistema tenham informações completas sobre eles. Isso acontece quando você vê coisas assim no \ d do psql:

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

Observe que a chamada nextval () nessa cláusula padrão possui uma conversão de :: texto além da conversão de :: regclass. Eu acho que isso se deve a bancos de dados sendo pg_dump'ed de versões antigas do PostgreSQL. O que acontecerá é que a função sequence_max_value () acima ignorará essa tabela. Para corrigir o problema, você pode redefinir a cláusula DEFAULT para se referir à sequência diretamente sem a conversão:

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

Em seguida, o psql o exibe corretamente:

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

Assim que você tiver corrigido isso, a função funcionará corretamente para esta tabela e para todas as outras que possam usar a mesma sequência.

alvherre
fonte
Isso é incrível thanx! Deve-se notar que eu precisava adicionar uma conversão à atribuição (linha 21 no código da função) assim: newmax := r.max::bigint;para fazê-la funcionar corretamente para mim.
Tommy Bravo
Também tive que alterar isso: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' observe o ::bigintelenco adicionado na consulta de construção dinâmica.
Tommy Bravo
9

Outro plpgsql - redefine apenas se 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;
$$
;

comentar também a linha --execute format('alter sequencefornecerá a lista, não redefinindo o valor

Vao Tsun
fonte
8

Redefinir toda a sequência de 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';
user457226
fonte
Parece que esta abordagem fazer suposições sobre as colunas e tabelas nomes para que ele não funcionou para mim
djsnowsill
Isso não danificaria os dados no banco de dados?
zennin
8

Eu sugiro esta solução encontrada no wiki do postgres. Ele atualiza todas as seqüências de suas tabelas.

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;

Como usar (do wiki do postgres):

  • Salve isso em um arquivo, diga 'reset.sql'
  • Execute o arquivo e salve sua saída de uma maneira que não inclua os cabeçalhos usuais, depois execute essa saída. Exemplo:

Exemplo:

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

Artigo original (também com correção para propriedade da sequência) aqui

Pietro
fonte
7

Algumas respostas realmente graves aqui, suponho que costumava ser muito ruim na época em que isso foi solicitado, pois muitas respostas daqui não funcionam para a versão 9.3. A documentação desde a versão 8.0 fornece uma resposta para esta mesma pergunta:

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

Além disso, se você precisar cuidar de nomes de sequência com distinção entre maiúsculas e minúsculas, é assim que você faz:

SELECT setval('"Serial"', max(id)) FROM distributors;
Ian Bytchek
fonte
7

Esse problema ocorre comigo ao usar a estrutura da entidade para criar o banco de dados e, em seguida, propagar o banco de dados com dados iniciais, o que torna a sequência incompatível.

Eu o resolvi criando um script para ser executado após a propagação do banco de dados:

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$
Yehia Amer
fonte
1
por MAX("Id") + 1que funciona melhor para mim quando a sequência é = ao máximo.
lastlink 24/07
6

Minha versão usa a primeira, com alguma verificação de erro ...

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;
Daniel Cristian Cruz
fonte
Obrigado pela verificação de erros! Muito apreciado porque os nomes de tabela / coluna são truncados se forem muito longos, o que você RAISE WARNINGidentificou para mim.
Nicholas Riley
5

Juntando tudo

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

irá corrigir a id'sequência da tabela especificada (como geralmente é necessário no django, por exemplo).

Antony Hatchkins
fonte
4

antes, eu ainda não havia tentado o código: a seguir, eu posto a versão do código sql para as soluções Klaus e user457226 que funcionavam no meu pc [Postgres 8.3], com apenas alguns pequenos ajustes para o Klaus e para a minha versão para o usuário457226.

Solução Klaus:

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

solução user457226:

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

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

Verifique novamente toda a sequência na função de esquema pública

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;
anydasa
fonte
3

Para reiniciar toda a sequência para 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';
Stanislav Yanev
fonte
2

A resposta do Klaus é a mais útil e executada para uma pequena falha: você precisa adicionar DISTINCT na instrução select.

No entanto, se você tiver certeza de que nenhum nome de tabela + coluna pode ser equivalente a duas tabelas diferentes, também poderá usar:

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

que é uma extensão da solução user457226 para o caso em que algum nome de coluna interessado não seja 'ID'.

mauro
fonte
... é claro, também é necessária uma alteração em "reset_sequence", que está adicionando um parâmetro "columnname", para usar em vez de "id".
213 mauro
2

Se você vir esse erro ao carregar dados SQL personalizados para inicialização, outra maneira de evitar isso é:

Em vez de escrever:

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

Remova a id(chave primária) dos dados iniciais

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

Isso mantém a sequência do Postgres sincronizada!

do utilizador
fonte
2

Esta resposta é uma cópia do 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();
Baldiry
fonte
2

Passei uma hora tentando fazer com que a resposta do djsnowsill funcionasse com um banco de dados usando tabelas e colunas de casos mistos, e finalmente encontrei a solução graças a um comentário de Manuel Darveau, mas achei que poderia deixar isso mais claro para todos:

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

Isso tem o benefício de:

  • não assumindo que a coluna ID está escrita de uma maneira específica.
  • não assumindo que todas as tabelas tenham uma sequência.
  • trabalhando para nomes de tabelas / colunas de Caso Misto.
  • usando o formato para ser mais conciso.

Para explicar, o problema era que são pg_get_serial_sequencenecessárias seqüências de caracteres para descobrir o que você está se referindo; portanto, se você fizer:

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

Isso é conseguido usando ''%1$I''a string format, ''faz com que um apóstrofo 1$signifique primeiro argumento e Ientre aspas

Nintynuts
fonte
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
Михаил Шатилов
fonte
4
Embora esse código possa responder à pergunta, fornecer um contexto adicional a respeito de por que e / ou como esse código responde à pergunta melhora seu valor a longo prazo.
yeya
1

Hack feio para consertá-lo usando alguma mágica de shell, não é uma ótima solução, mas pode inspirar outras pessoas com problemas semelhantes :)

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 -
Wolph
fonte
0

Tente reindexar .

ATUALIZAÇÃO: Como apontado nos comentários, isso foi uma resposta à pergunta original.

Hank Gay
fonte
reindex não trabalho, ela só parece aumentar o índice por 1
meleyal
3
reindex não funcionou porque estava respondendo à sua pergunta original, sobre índices de banco de dados, não seqüências
Vinko Vrsalovic
0

SELECT setval... faz o JDBC bork, então aqui está uma maneira compatível com Java de fazer isso:

-- 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;';
mcandre
fonte
0

Um método para atualizar todas as sequências em seu esquema usadas como um 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 $$;
Nick Van Berckelaer
fonte
0

Basta executar o comando abaixo:

SELECT setval('my_table_seq', (SELECT max(id) FROM my_table));
Asad Rao
fonte
0

Há muitas boas respostas aqui. Eu tive a mesma necessidade depois de recarregar meu banco de dados Django.

Mas eu precisava:

  • Tudo em uma função
  • Pode corrigir um ou mais esquemas de cada vez
  • Pode consertar toda ou apenas uma mesa de cada vez
  • Também queria uma boa maneira de ver exatamente o que havia mudado ou não

Parece uma necessidade muito semelhante à solicitada pelo original.
Graças a Baldiry e Mauro me colocou no caminho certo.

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;

Em seguida, execute e veja as alterações executadas:

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

Devoluções

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
brianwaganer
fonte