¿Cómo restablecer la secuencia de teclas principal de postgres cuando se cae de la sincronización?

523

Me encontré con el problema de que mi secuencia de teclas principal no está sincronizada con las filas de mi tabla.

Es decir, cuando inserto una nueva fila obtengo un error de clave duplicada porque la secuencia implícita en el tipo de datos en serie devuelve un número que ya existe.

Parece ser causado por la importación / restauración que no mantiene la secuencia correctamente.

melancólico
fuente
Tengo curiosidad ... ¿estás dejando caer el db antes de hacer una restauración? Tengo un leve recuerdo de que esto sucedió, pero podría estar equivocado: P
Arthur Thomas
25
El wiki de PostgreSQL tiene una página sobre Secuencias de fijación .
Brad Koch
14
Solo para ayudar a googleability, el mensaje de error arrojado aquí es: "el valor clave duplicado viola la restricción única ..."
superluminary
44
Así es como sqlsequencereset en Django lo hace: SELECT setval (pg_get_serial_sequence ("<table_name>", 'id'), coalesce (max ("id"), 1), max ("id") NO ES nulo) FROM "< nombre_tabla> ";
usuario
La primera instancia de <table name> debe estar entre comillas simples para que funcione la función pg_get_serioal_sequence: SELECT setval (pg_get_serial_sequence ('<table_name>', 'id'), coalesce (max ("id"), 1) , max ("id") NO ES nulo) FROM "<table_name>"
nclu

Respuestas:

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;

Fuente - Ruby Forum

melancólico
fuente
12
En cualquier caso, agregar 1 a MAX (id) dejará un espacio de número único en sus ID, ya que lo que establece setval es el último valor de la secuencia, no el siguiente.
mikl
66
Su ejemplo no funcionará si no hay filas en la tabla. Entonces, SQL dado a continuación es más seguro: SELECT setval ('your_table_id_seq', coalesce ((select max (id) +1 from your_table), 1), true);
Valery Viktorovsky
10
@Valery: Pero para evitar las brechas mencionadas por @mikl en los dos comentarios anteriores, necesitasSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins
20
Todos los problemas resueltos y combinados en una sola consulta:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi
15
Si su aplicación se preocupa por las brechas en las secuencias, su aplicación está rota. Las lagunas en las secuencias son normales, y pueden ocurrir debido a las paradas de base de datos no planificados, rollbacks transacción después de errores, etc.
Craig Ringer
202

pg_get_serial_sequencese puede usar para evitar suposiciones incorrectas sobre el nombre de la secuencia. Esto restablece la secuencia en una sola toma:

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

O más concisamente:

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

Sin embargo, este formulario no puede manejar tablas vacías correctamente, ya que max (id) es nulo, y tampoco puede establecer el valor 0 porque estaría fuera del rango de la secuencia. Una solución para esto es recurrir a la ALTER SEQUENCEsintaxis, es decir

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

Pero ALTER SEQUENCEes de uso limitado porque el nombre de secuencia y el valor de reinicio no pueden ser expresiones.

Parece que la mejor solución para todo uso es llamar setvala falso como el tercer parámetro, lo que nos permite especificar el "siguiente valor a utilizar":

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

Esto cumple todos mis requisitos:

  1. evita codificar el nombre de secuencia real
  2. maneja las tablas vacías correctamente
  3. maneja tablas con datos existentes y no deja un hueco en la secuencia

Finalmente, tenga en cuenta que pg_get_serial_sequencesolo funciona si la secuencia es propiedad de la columna. Este será el caso si la columna de incremento se definió como un serialtipo, sin embargo, si la secuencia se agregó manualmente, es necesario asegurarse de ALTER SEQUENCE .. OWNED BYque también se realice.

es decir, si serialse utilizó el tipo para la creación de la tabla, esto debería 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;

Pero si las secuencias se agregaron 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;
tarda
fuente
11
No hay necesidad de '+1' en la consulta, setval()establece el valor actual y nextval()ya devolverá el valor actual +1.
Antony Hatchkins el
1
La función que ajusta este método que toma un parámetro, nombre_tabla, está en mi respuesta a continuación: stackoverflow.com/a/13308052/237105
Antony Hatchkins
@AntonyHatchkins aplausos. Acabo de ver otra repetición del error +1, así que finalmente aplasté eso para bien, espero
tarda
99

La forma más corta y rápida :

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

tbl_idsiendo la serialcolumna de la tabla tbl, dibujando a partir de la secuencia tbl_tbl_id_seq(que es el nombre automático predeterminado).

Si no conoce el nombre de la secuencia adjunta (que no tiene que estar en forma predeterminada), use pg_get_serial_sequence():

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

Aquí no hay ningún error off-by-one. Por documentación:

La forma de dos parámetros establece el last_valuecampo de la secuencia en el valor especificado y establece su is_calledcampo en verdadero, lo que significa que el siguiente nextvalavanzará la secuencia antes de devolver un valor.

El énfasis en negrita es mío.

Si la tabla puede estar vacía y comenzar realmente desde 1 en este caso:

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

No podemos simplemente usar la forma 2-paremater y comenzar 0porque el límite inferior de las secuencias es 1 por defecto (a menos que esté personalizado).

Concurrencia

No hay defensa contra la actividad de secuencia concurrente o escrituras en la tabla en las consultas anteriores, todavía. Si eso es relevante, puede bloquear la tabla en modo exclusivo. Evita que las transacciones simultáneas escriban un número más alto mientras intenta sincronizarse. (También bloquea temporalmente las escrituras inofensivas que no interfieren con el número máximo).

Pero aún no tiene en cuenta a los clientes que pueden haber obtenido números de secuencia por adelantado sin ningún bloqueo en la tabla principal (lo que puede suceder). Para permitir eso, también, solo aumente el valor actual de la secuencia, nunca lo disminuya. Puede parecer paranoico, pero eso está de acuerdo con la naturaleza de las secuencias y la defensa contra los problemas de concurrencia.

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
fuente
¿Dónde está la "biblioteca comunitaria estándar de funciones esenciales"? ¡La segunda cláusula selectiva de esta respuesta en un EXECUTE format()(como @ EB.'s) es una función esencial! ¿Cómo solucionar esta falta de biblioteca estándar en PostgreSQL ????
Peter Krauss
No importa si hay un off-by-one. Las brechas en las secuencias son normales. Si su aplicación no puede hacer frente, su aplicación se ha roto, debido a las lagunas también pueden surgir debido a reversiones de transacción, paradas no planificadas de servidores, etc.
Craig Ringer
1
@Craig: El error "off-by-one" al que me dirigí (y no está allí) sería importante ya que de lo contrario correríamos el riesgo de un error de clave duplicada. La dirección opuesta de tus consideraciones; Parece un malentendido.
Erwin Brandstetter
Ah, tiene sentido.
Craig Ringer
Esto funciona para mí
hectk
54

Esto restablecerá todas las secuencias del público sin hacer suposiciones sobre nombres de tablas o columnas. Probado en la versión 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
fuente
1
+1 función muy útil! Nuestros nombres de secuencia no coincidían exactamente con los nombres de las tablas, así que usé en substring(column_default, '''(.*)''')lugar de table_name || '_' || column_name || '_seq'. Funciona perfectamente.
Chris Lercher
44
Tenga en cuenta que esto fallará con los nombres de secuencia que contienen comillas simples, o nombres de tablas con mayúsculas, espacios, etc. en su nombre. Las funciones quote_literaly quote_ident, o preferiblemente la formatfunción, realmente deberían usarse aquí.
Craig Ringer
2
Ojalá pudiera darle más de un voto ... bien hecho señor. También funciona muy bien en Postgres 9.1, al menos para mí.
peelman
1
Esto es genial. Solía substring(column_default from 'nextval\(''(.+)''::regclass\)')tomar explícitamente el nombre de la secuencia. Trabajado como un encanto.
Matthew MacDonald
Estuve buscando esta solución por más de un día, muchas gracias, incluso utilicé el método sugerido por @ChrisLercher para reemplazar el textosubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv
43

ALTERAR SECUENCIA secuencia_nombre REINICIAR CON (SELECCIONAR max (id) DE tabla_nombre); No funciona

Copiado de la respuesta @tardate:

SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;
tarda
fuente
8
eso es un error de sintaxis para mí en 8.4 (en ^ (SELECCIONAR ...). RESTART WITH parece aceptar solo un valor ordinal. Esto funciona aunque: SELECT setval (pg_get_serial_sequence ('table_name', 'id'), (SELECT MAX ( id) DE nombre_tabla) +1);
tarda el
1
La solución de Muruges tampoco funciona en 9.4. No entiendo por qué tantos votos positivos en esta respuesta. ALTER SEQUENCE no permite subconsultas. La solución de @tardate funciona perfectamente. Respuesta editada para eliminar datos incorrectos.
Vladislav Rastrusny
ALTER SEQUENCE funcionó perfecto para mí. Tuve utiliza COPY para introducir algunos datos y había huecos en las claves primarias y los INSERT arrojaban excepciones de claves duplicadas. Establecer la secuencia hizo el truco. 9.4
usuario542319
22

Este comando solo cambia el valor de secuencia de teclas generado automáticamente en postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

En lugar de cero, puede poner cualquier número desde el que desea reiniciar la secuencia.

el nombre de secuencia predeterminado lo hará "TableName_FieldName_seq". Por ejemplo, si su nombre de tabla es "MyTable"y su nombre de campo es "MyID", entonces su nombre de secuencia será "MyTable_MyID_seq".

Esta respuesta es igual a la respuesta de @ murugesanponappan, pero hay un error de sintaxis en su solución. no puedes usar subconsulta (select max()...)en el altercomando. Para que tenga que usar un valor numérico fijo o necesite usar una variable en lugar de una subconsulta.

Haider Ali Wajihi
fuente
Esta es la solución perfecta muchas gracias señor. Pero en mi caso tuve un error, así que tuve que cambiarlo a ALTER SEQUENCE "your_sequence_name" RESTART WITH 1;
Deunz
18

Restablezca todas las secuencias, sin suposiciones sobre nombres, excepto que la clave principal de cada tabla es "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.
fuente
Funcionó perfectamente en mi versión 9.1
Valentin Vasilyev
pg_get_serial_sequence(''"' || tablename || '"''
Debe
Esta es la mejor función! Puede evitar problemas de cotización (y mejorar la elegancia) con el formato, algo así 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

Estas funciones están llenas de peligros cuando los nombres de secuencia, nombres de columna, nombres de tabla o nombres de esquema tienen caracteres divertidos como espacios, signos de puntuación y similares. He escrito esto:

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

Puede llamarlo para una sola secuencia pasándole el OID y devolverá el número más alto utilizado por cualquier tabla que tenga la secuencia por defecto; o puede ejecutarlo con una consulta como esta, para restablecer todas las secuencias en su base de datos:

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

Usando una calidad diferente, puede restablecer solo la secuencia en un determinado esquema, y ​​así sucesivamente. Por ejemplo, si desea ajustar secuencias en el 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';

Tenga en cuenta que debido a cómo funciona setval (), no necesita agregar 1 al resultado.

Como nota final, tengo que advertir que algunas bases de datos parecen tener valores predeterminados que se vinculan a secuencias de manera que no permiten que los catálogos del sistema tengan información completa sobre ellas. Esto sucede cuando ves cosas como esta en psql's \ d:

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

Tenga en cuenta que la llamada nextval () en esa cláusula predeterminada tiene una conversión :: text además de la conversión :: regclass. Creo que esto se debe a que las bases de datos se pg_dump'ed de versiones anteriores de PostgreSQL. Lo que sucederá es que la función secuencia_max_valor () anterior ignorará dicha tabla. Para solucionar el problema, puede redefinir la cláusula DEFAULT para referirse a la secuencia directamente sin el reparto:

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

Entonces psql lo muestra correctamente:

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

Tan pronto como haya solucionado eso, la función funciona correctamente para esta tabla, así como para todas las demás que podrían usar la misma secuencia.

alvherre
fuente
¡Esto es increíble gracias! Cabe señalar que necesitaba agregar un reparto en la asignación (línea 21 en el código de función) de esta manera: newmax := r.max::bigint;para que funcione correctamente para mí.
Tommy Bravo
Tuve que cambiar esto también: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' observe la conversión agregada ::bigintdentro de la consulta de compilación dinámica.
Tommy Bravo
9

Otro plpgsql: se restablece solo si 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;
$$
;

también comentar la línea --execute format('alter sequencedará la lista, en realidad no restablecerá el valor

Vao Tsun
fuente
8

Restablecer toda la secuencia 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';
usuario457226
fuente
Parece ser que este enfoque hacer suposiciones acerca de los nombres de columna y mesas, así que no funcionaba para mí
djsnowsill
¿Eso no dañaría los datos en la base de datos?
zennin
8

Sugiero que esta solución se encuentre en postgres wiki. Actualiza todas las secuencias de tus tablas.

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;

Cómo usar (de postgres wiki):

  • Guarde esto en un archivo, diga 'reset.sql'
  • Ejecute el archivo y guarde su salida de una manera que no incluya los encabezados habituales, luego ejecute esa salida. Ejemplo:

Ejemplo:

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

Artículo original (también con arreglo para propiedad de secuencia) aquí

Pietro
fuente
7

Algunas respuestas realmente difíciles aquí, supongo que solía ser realmente malo en el momento en que esto se ha preguntado, ya que muchas respuestas de aquí no funcionan para la versión 9.3. La documentación desde la versión 8.0 proporciona una respuesta a esta misma pregunta:

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

Además, si necesita cuidar los nombres de secuencia que distinguen entre mayúsculas y minúsculas, así es como lo hace:

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

Este problema ocurre conmigo cuando uso el marco de entidad para crear la base de datos y luego sembrar la base de datos con datos iniciales, esto hace que la secuencia no coincida.

Lo resolví creando un script para ejecutar después de sembrar la base de datos:

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
fuente
1
por qué MAX("Id") + 1funciona mejor para mí cuando la secuencia es = al máximo.
lastlink
6

Mi versión usa la primera, con algunos errores de comprobación ...

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
fuente
¡Gracias por la comprobación de errores! Muy apreciado ya que los nombres de tabla / columna se truncan si son demasiado largos, lo cual me RAISE WARNINGidentificó.
Nicholas Riley
5

Poniendolo todo junto

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

fijará la id'secuencia de la tabla dada (como suele ser necesario con django, por ejemplo).

Antony Hatchkins
fuente
4

antes aún no había probado el código: a continuación publico la versión para el código sql para las soluciones Klaus y user457226 que funcionaban en mi PC [Postgres 8.3], con solo algunos pequeños ajustes para el Klaus y para mi versión para el usuario457226 uno.

Solución de 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();

Solución 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
fuente
4

Vuelva a verificar toda la secuencia en la función de esquema público

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
fuente
3

Para reiniciar toda la secuencia a 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
fuente
2

La respuesta de Klaus es la más útil, a excepción de un pequeño error: debe agregar DISTINCT en la instrucción select.

Sin embargo, si está seguro de que ningún nombre de tabla + columna puede ser equivalente para dos tablas diferentes, también puede 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 es una extensión de la solución user457226 para el caso en que el nombre de una columna interesada no es 'ID'.

mauro
fuente
... por supuesto, también se necesita un cambio en "reset_sequence", que es agregar un parámetro "columnname", para usar en lugar de "id".
mauro
2

Si ve este error cuando está cargando datos SQL personalizados para la inicialización, otra forma de evitar esto es:

En lugar de escribir:

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

Eliminar la id(clave principal) de los datos iniciales

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

¡Esto mantiene la secuencia de Postgres sincronizada!

usuario
fuente
2

Esta respuesta es una copia de 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();
Calvo
fuente
2

Pasé una hora tratando de obtener la respuesta de djsnowsill para trabajar con una base de datos usando tablas y columnas de casos mixtos, luego finalmente me topé con la solución gracias a un comentario de Manuel Darveau, pero pensé que podría aclararlo un poco a 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%';

Esto tiene el beneficio de:

  • no suponiendo que la columna ID se deletrea de una manera particular.
  • no suponiendo que todas las tablas tengan una secuencia.
  • trabajando para nombres de tabla / columna de casos mixtos.
  • usando el formato para ser más conciso.

Para explicarlo, el problema era que pg_get_serial_sequencetomaba medidas para resolver a qué se refería, así que si lo hace:

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

Esto se logra usando ''%1$I''la cadena de formato, ''hace que un apóstrofe 1$signifique el primer argumento y las Icomillas.

Nueces
fuente
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
Михаил Шатилов
fuente
44
Si bien este código puede responder la pregunta, proporcionar un contexto adicional con respecto a por qué y / o cómo responde la pregunta mejora su valor a largo plazo.
yeya
1

Hack feo para arreglarlo usando un poco de magia de shell, no es una gran solución, pero podría inspirar a otros con problemas similares :)

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
fuente
0

Prueba reindexar .

ACTUALIZACIÓN: Como se señaló en los comentarios, esto fue en respuesta a la pregunta original.

Hank Gay
fuente
reindex no funcionó, solo parece
aumentar
3
reindex no funcionó porque estaba respondiendo a su pregunta original, sobre índices de bases de datos, no secuencias
Vinko Vrsalovic
0

SELECT setval... hace JDBC bork, así que aquí hay una forma compatible con Java de hacer esto:

-- 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
fuente
0

Un método para actualizar todas las secuencias en su esquema que se utilizan como 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
fuente
0

Simplemente ejecute debajo del comando:

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

Hay muchas buenas respuestas aquí. Tenía la misma necesidad después de volver a cargar mi base de datos Django.

Pero yo necesitaba:

  • Función todo en uno
  • Podría arreglar uno o más esquemas a la vez
  • Podría arreglar todas o solo una mesa a la vez
  • También quería una buena manera de ver exactamente lo que había cambiado o no había cambiado.

Esto parece una necesidad muy similar a lo que fue la solicitud original.
Gracias a Baldiry y Mauro me pusieron en el camino correcto.

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;

Luego, para ejecutar y ver los cambios ejecutados:

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

Devoluciones

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
fuente