web-dev-qa-db-fra.com

Comment réinitialiser la séquence de clé primaire de postgres quand elle est désynchronisée?

J'ai rencontré le problème que ma séquence de clé principale n'est pas synchronisée avec les lignes de ma table.

C'est-à-dire que lorsque j'insère une nouvelle ligne, une erreur de clé dupliquée apparaît, car la séquence implicite dans le type de données série renvoie un nombre déjà existant.

Cela semble être dû à une importation/restauration qui ne maintient pas la séquence correctement.

470
meleyal
-- Login to psql and run the following

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

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

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

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

Source - Ruby Forum

656
meleyal

pg_get_serial_sequence peut être utilisé pour éviter toute hypothèse incorrecte sur le nom de la séquence. Cela réinitialise la séquence en un seul coup:

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

Ou plus concement:

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

Toutefois, ce formulaire ne peut pas gérer correctement les tables vides, car max (id) est null et vous ne pouvez pas non plus définir la valeur 0 car il serait en dehors de la plage de la séquence. Une solution consiste à recourir à la syntaxe _ALTER SEQUENCE_, c.-à-d.

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

Mais _ALTER SEQUENCE_ est d'une utilité limitée car le nom de séquence et la valeur de reprise ne peuvent pas être des expressions.

Il semble que la meilleure solution polyvalente consiste à appeler setval avec false en tant que troisième paramètre, ce qui permet de spécifier la "prochaine valeur à utiliser":

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

Cela coche toutes mes cases:

  1. évite de coder en dur le nom de la séquence réelle
  2. gère correctement les tables vides
  3. gère les tables avec les données existantes et ne laisse pas de trou dans la séquence

Enfin, notez que _pg_get_serial_sequence_ ne fonctionne que si la séquence appartient à la colonne. Ce sera le cas si la colonne d'incrémentation a été définie en tant que type serial. Toutefois, si la séquence a été ajoutée manuellement, il est nécessaire de s'assurer que _ALTER SEQUENCE .. OWNED BY_ est également exécuté.

c'est-à-dire que si le type serial a été utilisé pour la création de la table, tout devrait fonctionner:

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

Mais si les séquences ont été ajoutées manuellement:

_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;
_
186
tardate

Le le plus court et le plus rapide :

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

tbl_id étant la colonne serial de la table tbl, puisée dans la séquence tbl_tbl_id_seq (nom automatique par défaut).

Si vous ne connaissez pas le nom de la séquence attachée (qui ne doit pas obligatoirement être sous la forme par défaut), utilisez pg_get_serial_sequence() :

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

Il n'y a pas d'erreur par un ici. Par documentation:

La forme à deux paramètres définit le champ last_value de la séquence sur la valeur spécifiée et définit son champ is_called sur true, ce qui signifie que le suivant nextval fera avancer la séquence avant de renvoyer une valeur.

Gras accent mien.

Si la table peut être vide et pour réellement commencer à 1 dans ce cas:

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

Nous ne pouvons pas simplement utiliser la forme à deux partenaires et commencer par 0 parce que la limite inférieure des séquences est 1 par défaut (sauf personnalisation).

Simultanéité

Il n'y a pas encore de défense contre l'activité de séquence simultanée ou l'écriture dans la table dans les requêtes ci-dessus. Si cela est pertinent, vous pouvez verrouiller la table en mode exclusif. Il empêche les transactions simultanées d'écrire un nombre plus élevé pendant que vous essayez d'être synchronisé. (Il bloque également temporairement les écritures inoffensives sans toucher au nombre maximal.)

Mais cela ne prend pas en compte les clients qui ont peut-être déjà récupéré des numéros de séquence sans aucun verrou sur la table principale (ce qui peut arriver). Pour permettre cela aussi, seulement augmente la valeur actuelle de la séquence, ne la diminue jamais. Cela peut sembler paranoïaque, mais cela correspond à la nature des séquences et à la défense contre les problèmes de concurrence.

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;
78
Erwin Brandstetter

Cela réinitialisera toutes les séquences du public en ne faisant aucune hypothèse sur les noms de table ou de colonne. Testé sur la version 8.4

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text, sequence_name text) RETURNS "pg_catalog"."void" AS 

    $body$  
      DECLARE 
      BEGIN 

      EXECUTE 'SELECT setval( ''' || sequence_name  || ''', ' || '(SELECT MAX(' || columnname || ') FROM ' || tablename || ')' || '+1)';



      END;  

    $body$  LANGUAGE 'plpgsql';


    select table_name || '_' || column_name || '_seq', reset_sequence(table_name, column_name, table_name || '_' || column_name || '_seq') from information_schema.columns where column_default like 'nextval%';
50
djsnowsill

ALTER SEQUENCE nom_séquence RESTART WITH (SELECT max (id) FROM nom_table); Ça ne marche pas.

Copié de la réponse @tardate:

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

Cette commande ne modifie que la valeur de la séquence de clé générée automatiquement dans postgresql

_ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;
_

Au lieu de zéro, vous pouvez mettre n'importe quel nombre à partir duquel vous voulez redémarrer la séquence.

le nom de séquence par défaut sera _"TableName_FieldName_seq"_. Par exemple, si votre nom de table est _"MyTable"_ et votre nom de champ est _"MyID"_, votre nom de séquence sera alors "MyTable_MyID_seq".

Cette réponse est identique à la réponse de @ murugesanponappan, mais sa solution comporte une erreur de syntaxe. vous ne pouvez pas utiliser la sous-requête _(select max()...)_ dans la commande alter. Vous devez donc utiliser une valeur numérique fixe ou une variable à la place d'une sous-requête.

19
Haider Ali Wajihi

Réinitialiser toutes les séquences, pas de suppositions sur les noms sauf que la clé primaire de chaque table est "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%';
16
EB.

Ces fonctions présentent des risques lorsque les noms de séquence, noms de colonne, noms de table ou noms de schéma comportent des caractères amusants tels que des espaces, des signes de ponctuation, etc. J'ai écrit ceci:

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

Vous pouvez l'appeler pour une seule séquence en lui passant le OID et il renverra le nombre le plus élevé utilisé par toute table ayant la séquence par défaut; ou vous pouvez l'exécuter avec une requête comme celle-ci, pour réinitialiser toutes les séquences de votre base de données:

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

En utilisant un qual différent, vous pouvez réinitialiser uniquement la séquence d'un certain schéma, et ainsi de suite. Par exemple, si vous souhaitez ajuster les séquences dans le schéma "public":

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

Notez qu'en raison du fonctionnement de setval (), vous n'avez pas besoin d'ajouter 1 au résultat.

En guise de conclusion, je dois avertir que certaines bases de données semblent avoir des valeurs par défaut reliées à des séquences de manière à ne pas laisser les catalogues système en avoir une information complète. Cela se produit lorsque vous voyez des choses comme celles-ci dans\d:

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

Notez que l'appel nextval () dans cette clause default a un transtypage :: text en plus du transclass :: regclass. Je pense cela est dû aux bases de données pg_dump'ed des anciennes versions de PostgreSQL. Qu'est-ce qui se passera est que la fonction sequence_max_value () ci-dessus ignorera une telle table. Pour résoudre le problème, vous pouvez redéfinir la clause DEFAULT pour faire référence à la séquence directement sans la conversion:

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

Ensuite, psql l'affiche correctement:

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

Dès que vous avez corrigé cela, la fonction fonctionne correctement pour cette table ainsi que pour toutes les autres qui pourraient utiliser la même séquence.

12
alvherre

Réinitialiser toutes les séquences du 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';
7
user457226

Je suggère cette solution trouvée sur le wiki postgres. Il met à jour toutes les séquences de vos tables.

SELECT 'SELECT SETVAL(' ||
       quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
       ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
       quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
FROM pg_class AS S,
     pg_depend AS D,
     pg_class AS T,
     pg_attribute AS C,
     pg_tables AS PGT
WHERE S.relkind = 'S'
    AND S.oid = D.objid
    AND D.refobjid = T.oid
    AND D.refobjid = C.attrelid
    AND D.refobjsubid = C.attnum
    AND T.relname = PGT.tablename
ORDER BY S.relname;

Comment utiliser (à partir du wiki postgres):

  • Enregistrez ceci dans un fichier, dites 'reset.sql'
  • Exécutez le fichier et enregistrez sa sortie de manière à ne pas inclure les en-têtes habituels, puis exécutez cette sortie. Exemple:

Exemple:

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

Article original (également avec correctif pour la propriété de la séquence) ici

6
Pietro

Encore un autre plpgsql - réinitialise que 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;
$$
;

commentant également la ligne --execute format('alter sequence donnera la liste, pas en réinitialisant la valeur

6
Vao Tsun

Ce problème survient lorsque j'utilise un cadre d'entité pour créer la base de données, puis ensemencer la base de données avec les données initiales, ce qui rend la séquence incohérente.

Je l'ai résolu en créant un script à exécuter après l'ensemencement de la base de données:

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$
6
Yehia Amer

Certaines réponses vraiment difficiles ici, je suppose que c'était très mauvais à peu près au moment où cela a été demandé, car beaucoup de réponses d'ici ne fonctionnent pas pour la version 9.3. La documentation depuis la version 8.0 apporte une réponse à cette question:

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

En outre, si vous devez vous occuper des noms de séquence sensibles à la casse, procédez comme suit:

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

Ma version utilise la première, avec quelques erreurs de vérification ...

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

Mettre tous ensemble

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

corrigera la séquence 'id' de la table donnée (comme cela est généralement nécessaire avec Django par exemple).

5
Antony Hatchkins

avant, je n'avais pas encore essayé le code: dans ce qui suit, je publie la version pour le code SQL pour les solutions Klaus et user457226 qui fonctionnait sur mon ordinateur [Postgres 8.3], avec juste quelques ajustements pour ceux de Klaus et de ma version. pour le user457226 un.

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

solution 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';
4
mauro

Revérifier toutes les séquences dans la fonction de schéma public

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;
4
anydasa

Pour relancer toute séquence à 1, utilisez:

-- 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';
3
Stanislav Yanev

Si vous voyez cette erreur lorsque vous chargez des données SQL personnalisées pour l'initialisation, un autre moyen d'éviter cela est:

Au lieu d'écrire:

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

Supprimer la id (clé primaire) des données initiales

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

Ceci maintient la séquence de Postgres synchronisée!

2
user

Cette réponse est une copie 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();
2
Baldiry

J'ai passé une heure à essayer d'obtenir que la réponse de djsnowsill fonctionne avec une base de données utilisant des tables et des colonnes de cas mixtes, puis je suis tombé sur la solution grâce à un commentaire de Manuel Darveau, mais je pensais pouvoir le rendre un peu plus clair pour tout le monde:

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

Cela a l'avantage de:

  • ne pas supposer que la colonne ID est orthographiée d’une manière particulière.
  • ne pas supposer que toutes les tables ont une séquence.
  • travaillant pour les noms de table/colonne de cas mixtes.
  • en utilisant le format pour être plus concis.

Pour expliquer, le problème était que pg_get_serial_sequence prend des chaînes pour comprendre de quoi vous parlez, alors si vous le faites:

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

Ceci est réalisé en utilisant ''%1$I'' dans la chaîne de format, '' crée une apostrophe 1$ signifie premier argument, et I signifie entre guillemets

2
Nintynuts

La réponse de Klaus est la plus utile, à part une petite erreur: vous devez ajouter DISTINCT dans une instruction select.

Cependant, si vous êtes certain qu'aucun nom de table + colonne ne peut être équivalent pour deux tables différentes, vous pouvez également utiliser:

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

qui est une extension de la solution user457226 pour le cas où un nom de colonne intéressé n'est pas 'ID'.

1
mauro

Le bidouillage laid pour résoudre ce problème en utilisant une certaine magie de Shell, ce n'est pas une bonne solution mais pourrait inspirer d'autres avec des problèmes similaires :)

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 -
1
Wolph
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

SELECT setval... fait JDBC bork, alors voici une méthode compatible avec Java:

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

Essayez reindex .

UPDATE: Comme indiqué dans les commentaires, cela répondait à la question initiale.

0
Hank Gay

Une méthode pour mettre à jour toutes les séquences de votre schéma utilisées comme identifiant:

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