739 votes

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

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

En d'autres termes, lorsque j'insère une nouvelle ligne, j'obtiens une erreur de clé dupliquée car la séquence impliquée dans le type de données série renvoie un numéro qui existe déjà.

Il semble être causé par l'importation/restauration qui ne maintient pas la séquence correctement.

1 votes

Je suis curieux est-ce que vous laissez tomber la base de données avant de faire une restauration ? Je me souviens vaguement que cela s'est produit, mais je peux me tromper :P

39 votes

Le wiki de PostgreSQL a une page sur Correction des séquences .

26 votes

Juste pour aider à la googlelisation, le message d'erreur envoyé ici est : "La valeur de la clé dupliquée viole la contrainte d'unicité..."

11voto

Vao Tsun Points 25992

Encore un autre plpgsql - réinitialise seulement 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, sans réinitialiser la valeur.

9voto

user457226 Points 11

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

0 votes

Il semble que cette approche présuppose les noms des colonnes et des tables, ce qui n'a pas fonctionné pour moi.

0 votes

Cela n'endommagerait-il pas les données de la base de données ?

9voto

Yehia Amer Points 306

Ce problème se produit lorsque j'utilise le cadre d'entité pour créer la base de données et ensuite ensemencer la base de données avec les données initiales, ce qui fait que la séquence ne correspond pas.

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$

2 votes

Pourquoi le MAX("Id") + 1 ça marche mieux pour moi quand la séquence est = au maximum.

0 votes

Où faut-il exécuter ce script ? Je veux dire pgAdmin ou la ligne de commande ?

0 votes

Comment puis-je exécuter ce script après un job Talend réussi ?

7voto

Ian Bytchek Points 1247

Je suppose que la situation était vraiment mauvaise à l'époque où cette question a été posée, puisque beaucoup de réponses données ici ne fonctionnent pas pour la version 9.3. Le site documentation depuis la version 8.0 apporte une réponse à cette question :

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

De même, si vous devez prendre en charge les noms de séquence sensibles à la casse, c'est ainsi que vous devez procéder :

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

6voto

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;

0 votes

Merci pour la vérification des erreurs ! C'est très appréciable, car les noms des tables/colonnes sont tronqués s'ils sont trop longs, ce qui n'est pas le cas de votre logiciel. RAISE WARNING identifié pour moi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X