Comment réinitialiser la séquence de clés primaires de postgres lorsqu'elle n'est plus synchronisée?

523

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

Autrement dit, lorsque j'insère une nouvelle ligne, j'obtiens une erreur de clé en double car la séquence impliquée dans le type de données série renvoie un nombre qui existe déjà.

Cela semble provenir de l'import / restauration qui ne maintient pas la séquence correctement.

meleyal
la source
Je suis curieux .. supprimez-vous la base de données avant de faire une restauration? J'ai un léger souvenir de ce qui se passe, mais je peux me tromper: P
Arthur Thomas
25
Le wiki PostgreSQL a une page sur la correction des séquences .
Brad Koch
14
Juste pour faciliter la googleability, le message d'erreur affiché ici est: "la valeur de la clé en double viole la contrainte unique ..."
superluminary
4
Voici comment sqlsequencereset dans Django: SELECT setval (pg_get_serial_sequence ("<table_name>", 'id'), coalesce (max ("id"), 1), max ("id") N'EST PAS nul) FROM "< nom_table> ";
utilisateur
La première instance de <nom de la table> doit être entourée de guillemets simples pour que la fonction pg_get_serioal_sequence fonctionne: SELECT setval (pg_get_serial_sequence ('<table_name>', 'id'), coalesce (max ("id"), 1) , max ("id") N'EST PAS nul) FROM "<table_name>"
nclu

Réponses:

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;

Source - Ruby Forum

meleyal
la source
12
Quoi qu'il en soit, l'ajout de 1 à MAX (id) laissera un écart de nombre unique dans vos ID, car ce que setval définit est la dernière valeur de la séquence, pas la suivante.
mikl
6
Votre exemple ne fonctionnera pas s'il n'y a pas de lignes dans le tableau. Donc, le SQL ci-dessous est plus sûr: SELECT setval ('your_table_id_seq', coalesce ((sélectionnez max (id) +1 dans your_table), 1), true);
Valery Viktorovsky
10
@Valery: Mais pour éviter les lacunes mentionnées par @mikl deux commentaires ci-dessus, vous avez besoinSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins
20
Tous les problèmes résolus et combinés en une seule requête:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi
15
Si votre application se soucie des lacunes dans les séquences, votre application est interrompue. Les lacunes dans les séquences sont normales et peuvent se produire en raison d'arrêts imprévus de bases de données, de restaurations de transactions après des erreurs, etc.
Craig Ringer
202

pg_get_serial_sequencepeut être utilisé pour éviter toute hypothèse incorrecte sur le nom de la séquence. Cela réinitialise la séquence en une seule fois:

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

Ou plus précisément:

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

Cependant, ce formulaire ne peut pas gérer correctement les tables vides, car max (id) est nul, et vous ne pouvez pas non plus définir val 0 car il serait hors de portée de la séquence. Une solution pour cela consiste à recourir à la ALTER SEQUENCEsyntaxe, à savoir

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

Mais ALTER SEQUENCEson utilité est limitée car le nom de séquence et la valeur de redémarrage ne peuvent pas être des expressions.

Il semble que la meilleure solution polyvalente soit d'appeler setvalavec false comme 3e paramètre, ce qui nous 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 des données existantes et ne laisse pas de trou dans la séquence

Enfin, notez que cela pg_get_serial_sequencene fonctionne que si la séquence appartient à la colonne. Ce sera le cas si la colonne d'incrémentation a été définie comme un serialtype, mais si la séquence a été ajoutée manuellement, il est nécessaire de s'assurer qu'elle ALTER SEQUENCE .. OWNED BYest également effectuée.

c'est-à-dire si le serialtype a été utilisé pour la création de table, cela 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 des 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;
tardive
la source
11
Il n'y a pas besoin de '+1' dans la requête, setval()définit la valeur actuelle et nextval()renverra déjà la valeur actuelle +1.
Antony Hatchkins
1
La fonction enveloppant cette méthode qui prend un paramètre - nom_table - est dans ma réponse ci-dessous: stackoverflow.com/a/13308052/237105
Antony Hatchkins
@AntonyHatchkins cheers. Je viens de voir une autre répétition du bogue +1, donc j'ai finalement écrasé cela pour de bon, j'espère
tard
99

Le moyen le plus court et le plus rapide :

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

tbl_idétant la serialcolonne du tableau tbl, tirée de la séquence tbl_tbl_id_seq(qui est le nom automatique par défaut).

Si vous ne connaissez pas le nom de la séquence jointe (qui ne doit pas nécessairement ê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 off-by-one ici. Par documentation:

Le formulaire à deux paramètres définit le last_valuechamp de la séquence sur la valeur spécifiée et définit son is_calledchamp sur true, ce qui signifie que le suivant nextvalfera avancer la séquence avant de renvoyer une valeur.

Accentuation sur moi.

Si la table peut être vide et commencer à partir de 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 le formulaire à 2 paramètres et commencer par 0parce que la limite inférieure des séquences est 1 par défaut (sauf si personnalisé).

Accès simultané

Il n'y a pas encore de défense contre l'activité de séquence simultanée ou d'é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 de vous synchroniser. (Il bloque également temporairement les écritures inoffensives ne jouant pas avec le nombre maximal.)

Mais il ne prend pas en compte les clients qui peuvent avoir récupéré à l'avance des numéros de séquence sans verrou sur la table principale (ce qui peut arriver). Pour permettre cela aussi, augmentez uniquement la valeur actuelle de la séquence, ne la diminuez jamais. Cela peut sembler paranoïaque, mais cela est conforme à 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;
Erwin Brandstetter
la source
Où est la "bibliothèque communautaire STANDARD des fonctions essentielles"? La deuxième clause de sélection de cette réponse dans un EXECUTE format()(comme @ EB.'s) est une fonction essentielle! Comment corriger ce manque de bibliothèque standard dans PostgreSQL ????
Peter Krauss
Peu importe s'il y a un hors-par-un. Les lacunes dans les séquences sont normales. Si votre application ne peut pas y faire face, votre application est cassée, car des lacunes peuvent également survenir en raison des annulations de transactions, des arrêts imprévus du serveur, etc.
Craig Ringer
1
@Craig: L'erreur off-by-one que j'ai corrigée (et qui n'est pas là) importerait car nous risquerions autrement une erreur de clé en double. La direction opposée de vos considérations; semble être un malentendu.
Erwin Brandstetter
ah, c'est logique.
Craig Ringer
Cela fonctionne pour moi
hectk
54

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%';
djsnowsill
la source
1
+1 fonction très utile! Nos noms de séquence ne correspondaient pas exactement aux noms de table, j'ai donc utilisé à la substring(column_default, '''(.*)''')place de table_name || '_' || column_name || '_seq'. Marche parfaitement.
Chris Lercher
4
Notez que cela échouera avec les noms de séquence contenant des guillemets simples ou les noms de table avec des majuscules, des espaces, etc. dans leur nom. Les fonctions quote_literalet quote_ident, ou de préférence la formatfonction, devraient vraiment être utilisées ici.
Craig Ringer
2
J'aimerais pouvoir donner plus d'un vote à ce membre ... bien joué monsieur. Fonctionne également très bien sur Postgres 9.1, du moins pour moi.
peelman
1
C'est bien. J'avais l'habitude substring(column_default from 'nextval\(''(.+)''::regclass\)')de saisir explicitement le nom de la séquence. A fonctionné comme un charme.
Matthew MacDonald
Je cherchais cette solution depuis plus d'un jour maintenant, merci beaucoup, même j'ai utilisé la méthode suggérée par @ChrisLercher, pour remplacer le textesubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv
43

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;
tardive
la source
8
c'est une erreur de syntaxe pour moi en 8.4 (à ^ (SELECT ...). RESTART WITH semble n'accepter qu'une valeur ordinale. Cela fonctionne cependant: SELECT setval (pg_get_serial_sequence ('table_name', 'id'), (SELECT MAX ( id) FROM table_name) +1);
tardate
1
La solution de Muruges ne fonctionne pas non plus en 9.4. Je ne comprends pas pourquoi tant de votes positifs sur cette réponse. ALTER SEQUENCE n'autorise pas les sous-requêtes. La solution de @tardate fonctionne parfaitement. Réponse modifiée pour supprimer les données incorrectes.
Vladislav Rastrusny
ALTER SEQUENCE a parfaitement fonctionné pour moi. J'avais utilisé COPY pour apporter des données et il y avait des lacunes dans les clés primaires et INSERT lançaient des exceptions de clés en double. Définir la séquence a fait l'affaire. 9.4
user542319
22

Cette commande ne modifie que la valeur de séquence de touches 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 souhaitez 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", alors votre nom de séquence sera "MyTable_MyID_seq".

Cette réponse est la même que la réponse de @ murugesanponappan, mais il y a une erreur de syntaxe dans sa solution. vous ne pouvez pas utiliser de sous-requête (select max()...)dans la altercommande. De sorte que soit vous devez utiliser une valeur numérique fixe, soit vous devez utiliser une variable à la place de la sous-requête.

Haider Ali Wajihi
la source
Ceci est la solution parfaite merci beaucoup monsieur. Mais dans mon cas, j'ai eu une erreur, j'ai donc dû la changer en ALTER SEQUENCE "your_sequence_name" RESTART WITH 1;
Deunz
18

Réinitialiser toutes les séquences, pas d'hypothèses 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%';
EB.
la source
A parfaitement fonctionné sur ma version 9.1
Valentin Vasilyev
Vous devez ajouter un devis si le tableau contient des majuscules:pg_get_serial_sequence(''"' || tablename || '"''
Manuel Darveau
C'est la meilleure fonction! Vous pouvez éviter les problèmes de devis (et améliorer l'élégance) avec le format, quelque chose comme EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss
13

Ces fonctions sont lourdes de dangers lorsque les noms de séquence, les noms de colonne, les noms de table ou les noms de schéma ont 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 l'OID et il renverra le nombre le plus élevé utilisé par n'importe quelle table qui a 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 dans un certain schéma, etc. Par exemple, si vous souhaitez ajuster des 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.

Pour terminer, je dois avertir que certaines bases de données semblent avoir des valeurs par défaut liées aux séquences de manière à ne pas laisser les catalogues système en avoir toutes les informations. Cela se produit lorsque vous voyez des choses comme celle-ci dans \ d de psql:

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 par défaut a un cast :: text en plus du cast :: regclass. Je pense que cela est dû au fait que les bases de données sont pg_dump'ed d'anciennes versions de PostgreSQL. Ce qui se passera, c'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 le cast:

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.

alvherre
la source
C'est incroyable merci! Il convient de noter que je devais ajouter un cast à l'affectation (ligne 21 dans le code de fonction) comme ceci: newmax := r.max::bigint;pour le faire fonctionner correctement pour moi.
Tommy Bravo
A dû également changer cela: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' remarquez le ::bigintcast ajouté dans la requête de construction dynamique.
Tommy Bravo
9

Encore un autre plpgsql - ne se 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;
$$
;

commenter la ligne --execute format('alter sequencedonnera également la liste, sans réinitialiser la valeur

Vao Tsun
la source
8

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';
user457226
la source
Il semble que cette approche fasse des hypothèses sur les noms des colonnes et des tables, donc cela n'a pas fonctionné pour moi
djsnowsill
Cela n'endommagerait-il pas les données de la base de données?
zennin
8

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-le dans un fichier, dites «reset.sql»
  • Exécutez le fichier et enregistrez sa sortie d'une manière qui n'inclut pas 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 d'origine (également avec correctif pour la propriété de la séquence) ici

Pietro
la source
7

Quelques réponses vraiment hardcore ici, je suppose que c'était vraiment mauvais 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 fournit une réponse à cette question même:

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

De plus, si vous devez prendre soin des noms de séquence sensibles à la casse, c'est comme ça que vous le faites:

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

Ce problème se produit avec moi lorsque j'utilise le framework d'entité pour créer la base de données et ensuite amorcer la base de données avec les données initiales, cela crée une incompatibilité de séquence.

Je l'ai résolu en créant un script à exécuter après l'amorçage 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$
Yehia Amer
la source
1
pourquoi MAX("Id") + 1cela fonctionne le mieux pour moi lorsque la séquence est = au maximum.
lastlink
6

Ma version utilise la première, avec quelques vérifications d'erreurs ...

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
la source
Merci pour la vérification d'erreur! Très apprécié car les noms de table / colonne sont tronqués s'ils sont trop longs, ce que vous avez RAISE WARNINGidentifié pour moi.
Nicholas Riley
5

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

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

Antony Hatchkins
la source
4

avant je n'avais pas encore essayé le code: dans ce qui suit je poste la version du code sql pour les solutions Klaus et user457226 qui fonctionnaient sur mon pc [Postgres 8.3], avec juste quelques petits ajustements pour le Klaus one et de ma version pour l'utilisateur457226 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';
mauro
la source
4

Revérifiez 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;
anydasa
la source
3

Pour redémarrer toutes les séquences à 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';
Stanislav Yanev
la source
2

La réponse de Klaus est la plus utile, sauf pour un petit manque: vous devez ajouter DISTINCT dans l'instruction select.

Cependant, si vous êtes sûr 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'.

mauro
la source
... bien sûr, une modification de "reset_sequence" est également nécessaire, c'est-à-dire l'ajout d'un paramètre "columnname", à utiliser à la place de "id".
mauro
2

Si vous voyez cette erreur lorsque vous chargez des données SQL personnalisées pour l'initialisation, une autre façon 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),

Cela permet de synchroniser la séquence Postgres!

utilisateur
la source
2

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();
Baldiry
la source
2

J'ai passé une heure à essayer d'obtenir la réponse de djsnowsill pour travailler avec une base de données à l'aide de tables et de colonnes à cas mixtes, puis j'ai finalement trouvé la solution grâce à un commentaire de Manuel Darveau, mais j'ai pensé que je pourrais 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 supposant pas que toutes les tables ont une séquence.
  • travailler pour les noms de table / colonne de cas mixtes.
  • en utilisant le format pour être plus concis.

Pour expliquer, le problème était qu'il pg_get_serial_sequencefallait des chaînes pour comprendre à quoi vous faites référence, donc 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, ''fait une apostrophe 1$signifie premier argument, et Isignifie entre guillemets

Nintynuts
la source
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
Михаил Шатилов
la source
4
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire concernant pourquoi et / ou comment ce code répond à la question améliore sa valeur à long terme.
yeya
1

Hack laid pour le réparer en utilisant de la magie du shell, pas une excellente solution mais pourrait inspirer d'autres personnes 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 -
Wolph
la source
0

Essayez de réindexer .

MISE À JOUR: Comme indiqué dans les commentaires, c'était en réponse à la question initiale.

Hank Gay
la source
la réindexation n'a pas fonctionné, elle ne semble augmenter l'index que de 1
meleyal
3
la réindexation n'a pas fonctionné car elle répondait à votre question d'origine, sur les index de base de données, pas sur les séquences
Vinko Vrsalovic
0

SELECT setval... rend JDBC bork, voici donc une façon compatible Java de le faire:

-- 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
la source
0

Une méthode pour mettre à jour toutes les séquences de votre schéma qui sont utilisées comme 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
la source
0

Exécutez simplement la commande ci-dessous:

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

Il y a beaucoup de bonnes réponses ici. J'avais le même besoin après avoir rechargé ma base de données Django.

Mais j'avais besoin de:

  • Fonction tout en un
  • Pourrait corriger un ou plusieurs schémas à la fois
  • Pourrait réparer tout ou une seule table à la fois
  • Je voulais aussi une belle façon de voir exactement ce qui avait changé ou pas changé

Ce besoin semble très similaire à celui de la demande initiale.
Merci à Baldiry et Mauro m'a mis sur la bonne voie.

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;

Ensuite, pour exécuter et voir les modifications s'exécuter:

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

Retour

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
la source