Come resettare la sequenza di chiavi primarie di postgres quando non è sincronizzata?


523

Ho riscontrato il problema che la sequenza di chiavi primarie non è sincronizzata con le righe della mia tabella.

Cioè, quando inserisco una nuova riga ottengo un errore di chiave duplicata perché la sequenza implicita nel tipo di dati seriale restituisce un numero già esistente.

Sembra essere causato dall'importazione / ripristino che non mantiene correttamente la sequenza.


Sono curioso .. stai lasciando cadere il db prima di fare un ripristino? Ho un debole ricordo di ciò che sta accadendo, ma potrei sbagliarmi: P
Arthur Thomas,

25
Il wiki PostgreSQL ha una pagina su Sequenze di fissaggio .
Brad Koch,

14
Giusto per aiutare la googleability, il messaggio di errore generato qui è: "il valore chiave duplicato viola un vincolo univoco ..."
superluminario

4
Ecco come funziona sqlsequencereset in Django: SELEZIONA setval (pg_get_serial_sequence ("<table_name>", 'id'), coalesce (max ("id"), 1), max ("id") IS NOT null) FROM "< table_name> ";
utente

La prima istanza del <nome tabella> deve essere racchiusa tra virgolette singole affinché la funzione pg_get_serioal_sequence funzioni: SELECT setval (pg_get_serial_sequence ('<table_name>', 'id'), coalesce (max ("id"), 1) , max ("id") NON È
NULL

Risposte:


715
-- Login to psql and run the following

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

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

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

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

Fonte: Forum di Ruby


12
Ad ogni modo, l'aggiunta di 1 a MAX (id) lascerà un unico divario numerico nei tuoi ID, poiché ciò che imposta setval è l'ultimo valore della sequenza, non il successivo.
mikl

6
Il tuo esempio non funzionerà se non ci sono righe nella tabella. Quindi, SQL qui sotto è più sicuro: SELEZIONA setval ('your_table_id_seq', coalesce ((seleziona max (id) +1 da your_table), 1), true);
Valery Viktorovsky,

10
@Valery: Ma per evitare le lacune menzionate da @mikl due commenti sopra, è necessarioSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins,

20
Tutti i problemi risolti e combinati in un'unica query:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi,

15
Se l'applicazione si preoccupa degli spazi vuoti nelle sequenze, l'applicazione viene interrotta. Le lacune nelle sequenze sono normali e possono verificarsi a causa di arresti del database non pianificati, rollback delle transazioni dopo errori, ecc.
Craig Ringer,

202

pg_get_serial_sequencepuò essere utilizzato per evitare ipotesi errate sul nome della sequenza. Questo reimposta la sequenza in un colpo solo:

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

O più concisamente:

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

Tuttavia, questo modulo non è in grado di gestire correttamente le tabelle vuote, poiché max (id) è nullo e non è possibile impostare setval 0 perché non rientra nell'intervallo della sequenza. Una soluzione alternativa per questo è ricorrere alla ALTER SEQUENCEsintassi cioè

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

Ma ALTER SEQUENCEè di utilità limitata perché il nome della sequenza e il valore di riavvio non possono essere espressioni.

Sembra che la migliore soluzione per tutti gli usi sia chiamare setvalcon false come terzo parametro, permettendoci di specificare il "prossimo valore da usare":

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

Questo spunta tutte le mie caselle:

  1. evita di codificare il nome effettivo della sequenza
  2. gestisce correttamente i tavoli vuoti
  3. gestisce le tabelle con dati esistenti e non lascia un buco nella sequenza

Infine, nota che pg_get_serial_sequencefunziona solo se la sequenza è di proprietà della colonna. Ciò avverrà se la colonna di incremento è stata definita come un serialtipo, tuttavia se la sequenza è stata aggiunta manualmente è necessario assicurarsi che ALTER SEQUENCE .. OWNED BYvenga eseguita anche.

cioè se il serialtipo è stato usato per la creazione di tabelle, tutto dovrebbe funzionare:

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;

Ma se le sequenze sono state aggiunte 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;

11
Non è necessario in "+1" nella query, setval()imposta il valore corrente e nextval()restituirà già il valore corrente +1.
Antony Hatchkins,

1
Funzione di chiudere questo metodo che accetta un parametro - table_name - è nella mia risposta qui sotto: stackoverflow.com/a/13308052/237105
Antony Hatchkins

@AntonyHatchkins evviva. Ho appena visto un'altra ripetizione del bug +1 così finalmente schiacciato che per bene spero
tardato il

99

Il modo più breve e veloce :

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

tbl_idessendo la serialcolonna della tabella tbl, attingendo dalla sequenza tbl_tbl_id_seq(che è il nome automatico predefinito).

Se non conosci il nome della sequenza allegata (che non deve essere nella forma predefinita), usa pg_get_serial_sequence():

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

Non ci sono errori off-by-one qui. Per documentazione:

Il modulo a due parametri imposta il last_valuecampo della sequenza sul valore specificato e imposta il suo is_calledcampo su true, il che significa che il successivo nextvalfarà avanzare la sequenza prima di restituire un valore.

Enorme enfasi sulla mia.

Se la tabella può essere vuota e iniziare effettivamente da 1 in questo caso:

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

Non possiamo semplicemente usare il modulo 2-paremater e iniziare 0perché il limite inferiore delle sequenze è 1 per impostazione predefinita (a meno che non sia personalizzato).

Concorrenza

Non vi è ancora alcuna difesa da attività di sequenza simultanee o scritture nella tabella nelle query precedenti. Se questo è rilevante, potresti bloccare la tabella in modalità esclusiva. Mantiene le transazioni simultanee dalla scrittura di un numero maggiore mentre si sta tentando di sincronizzarsi. (Inoltre, blocca temporaneamente le scritture innocue che non interferiscono con il numero massimo.)

Tuttavia, non prende in considerazione i client che potrebbero aver recuperato i numeri di sequenza in anticipo senza alcun blocco nella tabella principale, ma (cosa che può accadere). Per consentire anche questo, aumentare solo il valore corrente della sequenza, non diminuirlo mai. Può sembrare paranoico, ma questo è in accordo con la natura delle sequenze e la difesa da problemi di concorrenza.

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;

Dove si trova la "biblioteca-comunità STANDARD di funzioni essenziali"? La seconda clausola select di questa risposta in un EXECUTE format()(come @ EB.'s) è una funzione essenziale! Come risolvere questa mancanza di libreria standard in PostgreSQL ????
Peter Krauss,

Non importa se c'è un off-by-one. Le lacune nelle sequenze sono normali. Se la tua app non riesce a farcela, la tua app viene interrotta, perché possono verificarsi delle lacune a causa di rollback delle transazioni, arresti del server non pianificati, ecc.
Craig Ringer,

1
@Craig: l'errore off-by-one che ho affrontato (e non c'è) sarebbe importante poiché altrimenti rischiamo un errore chiave duplicato. La direzione opposta delle tue considerazioni; sembra un malinteso.
Erwin Brandstetter,

ah, ha senso.
Craig Ringer,

Questo funziona per me
ettk

54

Ciò ripristinerà tutte le sequenze dal pubblico senza fare ipotesi sui nomi di tabelle o colonne. Testato sulla versione 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%';

1
+1 funzione molto utile! I nomi delle nostre sequenze non corrispondevano esattamente ai nomi delle tabelle, quindi l'ho usato al substring(column_default, '''(.*)''')posto di table_name || '_' || column_name || '_seq'. Funziona perfettamente.
Chris Lercher,

4
Si noti che ciò non riuscirà con nomi di sequenza contenenti virgolette singole o nomi di tabelle con maiuscole, spazi, ecc. Nel loro nome. Le funzioni quote_literale quote_ident, o preferibilmente la formatfunzione, dovrebbero davvero essere usate qui.
Craig Ringer,

2
Vorrei poter dare più di un voto ... ben fatto signore. Funziona benissimo anche su Postgres 9.1, almeno per me.
peelman,

1
Questo è fantastico Ero solito substring(column_default from 'nextval\(''(.+)''::regclass\)')prendere esplicitamente il nome della sequenza. Ha funzionato come un fascino.
Matthew MacDonald,

Stavo cercando questa soluzione da più di un giorno, grazie mille, anche io ho usato il metodo suggerito da @ChrisLercher, per sostituire il testosubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv

43

ALTER SEQUENCE sequenza_name RIAVVIA CON (SELEZIONA max (id) DA table_name); Non funziona

Copiato dalla risposta @tardate:

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

8
questo è un errore di sintassi per me in 8.4 (at ^ (SELECT ...). RESTART WITH sembra accettare solo un valore ordinale. Funziona però: SELECT setval (pg_get_serial_sequence ('table_name', 'id'), (SELECT MAX ( id) FROM table_name) +1);
tardate

1
La soluzione di Muruges non funziona neanche in 9.4. Non capisco perché così tanti voti su questa risposta. ALTER SEQUENCE non consente le subquery. La soluzione di @tardate funziona perfettamente. Risposta modificata per rimuovere dati errati.
Vladislav Rastrusny,

ALTER SEQUENCE ha funzionato perfettamente per me. Avevo usato COPY per portare alcuni dati e c'erano delle lacune nelle chiavi primarie e le INSERT stavano lanciando eccezioni di chiavi duplicate. L'impostazione della sequenza ha funzionato. 9.4
user542319

22

Questo comando cambia solo il valore della sequenza di chiavi generata automaticamente in postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

Al posto di zero è possibile inserire qualsiasi numero da cui si desidera riavviare la sequenza.

sarà il nome della sequenza predefinita "TableName_FieldName_seq". Ad esempio, se il nome della tabella è "MyTable"e il nome del campo è "MyID", il nome della sequenza sarà "MyTable_MyID_seq".

Questa è la risposta è la risposta di @ murugesanponappan, ma c'è un errore di sintassi nella sua soluzione. non è possibile utilizzare la sub query (select max()...)nel altercomando. Pertanto, è necessario utilizzare un valore numerico fisso oppure è necessario utilizzare una variabile al posto della query secondaria.


Questa è la soluzione perfetta grazie mille signore. Ma nel mio caso ho avuto un errore, quindi ho dovuto cambiarlo in ALTER SEQUENCE "your_sequence_name" RIAVVIA CON 1;
Deunz,

18

Ripristina tutte le sequenze, nessuna ipotesi sui nomi tranne che la chiave primaria di ogni tabella è "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%';

Ha funzionato perfettamente sulla mia versione 9.1
Valentin Vasilyev il

Devi aggiungere un preventivo se la tabella contiene pg_get_serial_sequence(''"' || tablename || '"''
lettere

Questa è la migliore funzione! Puoi evitare problemi di citazione (e migliorare l'eleganza) con il formato, qualcosa del genere EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss,

13

Queste funzioni sono piene di pericoli quando nomi di sequenza, nomi di colonne, nomi di tabelle o nomi di schemi hanno caratteri divertenti come spazi, segni di punteggiatura e simili. Ho scritto questo:

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

Puoi chiamarlo per una singola sequenza passandogli l'OID e restituirà il numero più alto usato da qualsiasi tabella che ha la sequenza come predefinita; oppure puoi eseguirlo con una query come questa, per ripristinare tutte le sequenze nel tuo database:

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

Utilizzando una qual diversa è possibile reimpostare solo la sequenza in un determinato schema e così via. Ad esempio, se si desidera regolare le sequenze nello schema "pubblico":

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

Si noti che a causa di come funziona setval (), non è necessario aggiungere 1 al risultato.

Come nota di chiusura, devo avvertire che alcuni database sembrano avere impostazioni predefinite che si collegano alle sequenze in modi che non consentono ai cataloghi di sistema di avere informazioni complete su di essi. Questo succede quando vedi cose come queste in psql's \ d:

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

Notare che la chiamata nextval () in quella clausola predefinita ha un cast :: text oltre al cast :: regclass. Penso che ciò sia dovuto al fatto che i database sono stati pg_dump dalle vecchie versioni di PostgreSQL. Quello che accadrà è che la funzione sequence_max_value () sopra ignorerà una tale tabella. Per risolvere il problema, puoi ridefinire la clausola DEFAULT per fare riferimento direttamente alla sequenza senza il cast:

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

Quindi psql lo visualizza correttamente:

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

Una volta risolto il problema, la funzione funziona correttamente per questa tabella e per tutti gli altri che potrebbero utilizzare la stessa sequenza.


Grazie mille! Va notato che dovevo aggiungere un cast all'assegnazione (riga 21 nel codice funzione) in questo modo: newmax := r.max::bigint;per farlo funzionare correttamente per me.
Tommy Bravo

Ho dovuto cambiare anche questo: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' notare il ::bigintcast aggiunto all'interno della query di creazione dinamica.
Tommy Bravo

9

Ancora un altro plpgsql - ripristina solo se max(att) > then lastval

do --check seq not in sync
$$
declare
 _r record;
 _i bigint;
 _m bigint;
begin
  for _r in (
    SELECT relname,nspname,d.refobjid::regclass, a.attname, refobjid
    FROM   pg_depend    d
    JOIN   pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
    JOIN pg_class r on r.oid = objid
    JOIN pg_namespace n on n.oid = relnamespace
    WHERE  d.refobjsubid > 0 and  relkind = 'S'
   ) loop
    execute format('select last_value from %I.%I',_r.nspname,_r.relname) into _i;
    execute format('select max(%I) from %s',_r.attname,_r.refobjid) into _m;
    if coalesce(_m,0) > _i then
      raise info '%',concat('changed: ',_r.nspname,'.',_r.relname,' from:',_i,' to:',_m);
      execute format('alter sequence %I.%I restart with %s',_r.nspname,_r.relname,_m+1);
    end if;
  end loop;

end;
$$
;

anche commentando la riga --execute format('alter sequenceverrà visualizzato l'elenco, non reimpostando effettivamente il valore


8

Ripristina tutta la sequenza dal pubblico

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

Sembra che questo approccio faccia ipotesi sui nomi delle colonne e delle tabelle, quindi non ha funzionato per me
djsnowsill

Non danneggerebbe i dati nel database?
zennin,

8

Suggerisco questa soluzione trovata sul wiki di Postgres. Aggiorna tutte le sequenze delle tue tabelle.

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;

Come usare (dalla wiki di postgres):

  • Salvalo in un file, dì "reset.sql"
  • Esegui il file e salva l'output in un modo che non includa le normali intestazioni, quindi esegui l'output. Esempio:

Esempio:

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

Articolo originale (anche con correzione per la proprietà della sequenza) qui


7

Alcune risposte davvero hardcore qui, presumo che fosse davvero pessimo nel momento in cui questo è stato chiesto, dal momento che molte risposte da qui non funzionano per la versione 9.3. La documentazione dalla versione 8.0 fornisce una risposta a questa domanda:

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

Inoltre, se devi occuparti dei nomi delle sequenze con distinzione tra maiuscole e minuscole, è così che lo fai:

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

7

Questo problema si verifica con me quando si utilizza il framework di entità per creare il database e quindi eseguire il seeding del database con i dati iniziali, questo rende la sequenza non corrispondente.

Ho risolto creando uno script da eseguire dopo il seeding del database:

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$

1
perché MAX("Id") + 1funziona meglio per me quando la sequenza è = al massimo.
lastlink

6

La mia versione usa la prima, con qualche controllo errori ...

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;

Grazie per il controllo degli errori! Molto apprezzato come i nomi di tabella / colonna vengono troncati se sono troppo lunghi, che hai RAISE WARNINGidentificato per me.
Nicholas Riley,

5

Mettere tutto insieme

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

risolverà la id'sequenza della tabella data (come di solito necessario con django per esempio).


4

prima non avevo ancora provato il codice: di seguito inserisco la versione per il codice sql sia per Klaus che per le soluzioni user457226 che hanno funzionato sul mio pc [Postgres 8.3], con solo alcuni piccoli aggiustamenti per quello di Klaus e della mia versione per l'utente457226 uno.

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

soluzione 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

Ricontrolla tutta la sequenza nella funzione dello schema pubblico

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;

3

Per riavviare tutta la sequenza su 1 utilizzare:

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

2

La risposta di Klaus è la più utile, eseguita per una piccola mancanza: devi aggiungere DISTINCT nell'istruzione selezionata.

Tuttavia, se sei sicuro che nessun nome di tabella + colonna può essere equivalente per due diverse tabelle, puoi anche usare:

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

che è un'estensione della soluzione user457226 per il caso in cui un nome di colonna interessato non è "ID".


... ovviamente, è necessaria anche una modifica di "reset_sequence", ovvero l'aggiunta di un parametro "columnname", da utilizzare al posto di "id".
mauro

2

Se visualizzi questo errore quando carichi dati SQL personalizzati per l'inizializzazione, un altro modo per evitarlo è:

Invece di scrivere:

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

Rimuovere la id(chiave primaria) dai dati iniziali

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

Ciò mantiene sincronizzata la sequenza di Postgres!


2

Questa risposta è una copia di 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

Ho impiegato un'ora a cercare di ottenere la risposta di djsnowsill per lavorare con un database utilizzando tabelle e colonne Mixed Case, quindi alla fine mi sono imbattuto nella soluzione grazie a un commento di Manuel Darveau, ma ho pensato di renderlo un po 'più chiaro per tutti:

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

Questo ha il vantaggio di:

  • non supponendo che la colonna ID sia scritta in un modo particolare.
  • non supponendo che tutte le tabelle abbiano una sequenza.
  • funzionante per nomi di tabelle / colonne con maiuscole / minuscole.
  • usando il formato per essere più concisi.

Per spiegare, il problema era che ci pg_get_serial_sequencevogliono delle stringhe per capire a cosa ti riferisci, quindi se lo fai:

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

Questo si ottiene usando ''%1$I''la stringa di formato, ''rende un apostrofo 1$significa primo argomento e Isignifica tra virgolette


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

4
Mentre questo codice può rispondere alla domanda, fornendo un contesto aggiuntivo riguardo al perché e / o al modo in cui questo codice risponde alla domanda migliora il suo valore a lungo termine.
yeya,

1

Brutto hack per risolverlo usando un po 'di magia della shell, non un'ottima soluzione ma potrebbe ispirare altri con problemi simili :)

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 -

0

Prova reindex .

AGGIORNAMENTO: Come sottolineato nei commenti, questo era in risposta alla domanda originale.


reindex non ha funzionato, sembra solo incrementare l'indice di 1
meleyal

3
reindex non ha funzionato perché rispondeva alla tua domanda originale, sugli indici del database, non sulle sequenze
Vinko Vrsalovic,

0

SELECT setval... rende JDBC bork, quindi ecco un modo compatibile Java per farlo:

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

Un metodo per aggiornare tutte le sequenze nello schema utilizzate come 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 $$;

0

Esegui appena sotto il comando:

SELECT setval('my_table_seq', (SELECT max(id) FROM my_table));

0

Ci sono molte buone risposte qui. Ho avuto lo stesso bisogno dopo aver ricaricato il mio database Django.

Ma avevo bisogno di:

  • Funzione All in one
  • Potrebbe risolvere uno o più schemi alla volta
  • Potrebbe risolvere tutto o solo una tabella alla volta
  • Volevo anche un modo carino per vedere esattamente cosa era cambiato o non cambiato

Sembra una necessità molto simile a quella richiesta per l'originale.
Grazie a Baldiry e Mauro mi ha portato sulla strada giusta.

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;

Quindi per eseguire e visualizzare le modifiche eseguite:

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

ritorna

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.