Wie kann die Primärschlüsselfolge von Postgres zurückgesetzt werden, wenn sie nicht mehr synchron ist?


523

Ich bin auf das Problem gestoßen, dass meine Primärschlüsselfolge nicht mit meinen Tabellenzeilen synchronisiert ist.

Das heißt, wenn ich eine neue Zeile einfüge, wird ein doppelter Schlüsselfehler angezeigt, da die im seriellen Datentyp implizierte Sequenz eine bereits vorhandene Nummer zurückgibt.

Dies scheint darauf zurückzuführen zu sein, dass beim Importieren / Wiederherstellen die Sequenz nicht ordnungsgemäß beibehalten wird.


Ich bin neugierig ... lassen Sie die Datenbank fallen, bevor Sie eine Wiederherstellung durchführen? Ich habe eine schwache Erinnerung an dieses Ereignis, aber ich könnte mich irren: P
Arthur Thomas

25
Das PostgreSQL-Wiki enthält eine Seite zum Fixieren von Sequenzen .
Brad Koch

14
Nur um die Googleabilität zu verbessern, lautet die hier ausgegebene Fehlermeldung: "Doppelter Schlüsselwert verletzt eindeutige Einschränkung ..."
Superluminary

4
So macht es sqlsequencereset in Django: SELECT setval (pg_get_serial_sequence ("<Tabellenname>", 'id'), Koaleszenz (max ("id"), 1), max ("id") IST NICHT null) FROM "< Tabellenname> ";
Benutzer

Die erste Instanz des <Tabellennamens> muss in einfache Anführungszeichen gesetzt werden, damit die Funktion pg_get_serioal_sequence funktioniert: SELECT setval (pg_get_serial_sequence ('<Tabellenname>', 'id'), Coalesce (max ("id"), 1) , max ("id") IST NICHT null) FROM "<Tabellenname>"
nclu

Antworten:


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;

Quelle - Ruby Forum


12
In jedem Fall hinterlässt das Hinzufügen von 1 zu MAX (id) eine einzelne Zahlenlücke in Ihren IDs, da die festgelegten Werte der letzte Wert der Sequenz und nicht der nächste sind.
Mikl

6
Ihr Beispiel funktioniert nicht, wenn die Tabelle keine Zeilen enthält. Das unten angegebene SQL ist also sicherer: SELECT setval ('your_table_id_seq', coalesce ((wähle max (id) +1 aus your_table), 1), true);
Valery Viktorovsky

10
@Valery: Aber um Lücken zu vermeiden, die von @mikl zwei Kommentare oben erwähnt wurden, brauchen SieSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins

20
Alle Probleme gelöst und in einer einzigen Abfrage zusammengefasst:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi

15
Wenn sich Ihre Anwendung um Lücken in Sequenzen kümmert, ist Ihre Anwendung fehlerhaft. Lücken in Sequenzen sind normal und können aufgrund ungeplanter Datenbankabschaltungen, Transaktions-Rollbacks nach Fehlern usw. auftreten
Craig Ringer

202

pg_get_serial_sequencekann verwendet werden, um falsche Annahmen über den Sequenznamen zu vermeiden. Dies setzt die Sequenz auf einmal zurück:

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

Oder genauer:

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

Dieses Formular kann jedoch leere Tabellen nicht korrekt verarbeiten, da max (id) null ist und Sie auch nicht 0 setzen können, da es außerhalb des Bereichs der Sequenz liegen würde. Eine Problemumgehung besteht darin, auf die ALTER SEQUENCESyntax zurückzugreifen, d. H.

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

Dies ALTER SEQUENCEist jedoch von begrenztem Nutzen, da der Sequenzname und der Neustartwert keine Ausdrücke sein können.

Es scheint, dass die beste Allzwecklösung darin besteht, setvalfalse als dritten Parameter aufzurufen , um den "nächsten zu verwendenden Wert" anzugeben:

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

Dies erfüllt alle meine Kriterien:

  1. vermeidet die Hardcodierung des tatsächlichen Sequenznamens
  2. behandelt leere Tabellen korrekt
  3. behandelt Tabellen mit vorhandenen Daten und hinterlässt keine Lücke in der Sequenz

Beachten Sie schließlich, dass dies pg_get_serial_sequencenur funktioniert, wenn die Sequenz der Spalte gehört. Dies ist der Fall, wenn die inkrementierende Spalte als serialTyp definiert wurde. Wenn die Sequenz jedoch manuell hinzugefügt wurde, muss sichergestellt werden, dass sie ALTER SEQUENCE .. OWNED BYauch ausgeführt wird.

Wenn also der serialTyp für die Tabellenerstellung verwendet wurde, sollte dies alles funktionieren:

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;

Aber wenn Sequenzen manuell hinzugefügt wurden:

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
'+1' in der Abfrage ist nicht erforderlich, setval()setzt den aktuellen Wert und nextval()gibt bereits den aktuellen Wert +1 zurück.
Antony Hatchkins

1
Die Funktion, die diese Methode umschließt, die einen Parameter - Tabellenname - verwendet, ist in meiner Antwort unten aufgeführt: stackoverflow.com/a/13308052/237105
Antony Hatchkins

@ AnthonyHatchkins Prost. Gerade gesehen , eine weitere Wiederholung des 1 Fehler so schließlich swatted , dass für eine gute ich die Hoffnung
tardate

99

Der kürzeste und schnellste Weg:

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

tbl_idAls serialTabellenspalte tblwird aus der Sequenz gezeichnet tbl_tbl_id_seq(dies ist der automatische Standardname).

Wenn Sie den Namen der angehängten Sequenz nicht kennen (die nicht in der Standardform vorliegen muss), verwenden Sie pg_get_serial_sequence():

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

Hier gibt es keinen Fehler nach dem anderen. Pro Dokumentation:

Das Zwei-Parameter-Formular setzt das last_valueFeld der Sequenz auf den angegebenen Wert und das is_calledFeld auf true, was bedeutet, dass das nächste nextvaldie Sequenz vor der Rückgabe eines Werts vorverlegt.

Meine kühne Betonung.

Wenn die Tabelle leer sein kann und in diesem Fall tatsächlich bei 1 beginnen soll:

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

Wir können nicht einfach das 2-Paremater-Formular verwenden und damit beginnen, 0da die untere Grenze der Sequenzen standardmäßig 1 ist (sofern nicht angepasst).

Parallelität

Es gibt noch keine Verteidigung gegen gleichzeitige Sequenzaktivitäten oder Schreibvorgänge in die Tabelle in den obigen Abfragen. Wenn dies relevant ist, können Sie die Tabelle im exklusiven Modus sperren . Es verhindert, dass gleichzeitige Transaktionen eine höhere Zahl schreiben, während Sie versuchen, eine Synchronisierung durchzuführen. (Außerdem werden harmlose Schreibvorgänge vorübergehend blockiert, ohne die maximale Anzahl zu beeinträchtigen.)

Es werden jedoch keine Clients berücksichtigt, die möglicherweise zuvor Sequenznummern ohne Sperren für die Haupttabelle abgerufen haben (was passieren kann). Um dies ebenfalls zu berücksichtigen, erhöhen Sie nur den aktuellen Wert der Sequenz, verringern Sie ihn niemals. Es mag paranoid erscheinen, aber das stimmt mit der Art der Sequenzen und der Verteidigung gegen Parallelitätsprobleme überein.

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;

Wo ist die "STANDARD Community-Bibliothek der essentiellen Funktionen"? Die zweite Auswahlklausel dieser Antwort in einem EXECUTE format()(wie @ EB.'s) ist eine wesentliche Funktion! Wie kann dieser Mangel an Standardbibliothek in PostgreSQL behoben werden?
Peter Krauss

Es spielt keine Rolle, ob es einen Off-by-One gibt. Lücken in Sequenzen sind normal. Wenn Ihre App nicht zurechtkommt, ist Ihre App defekt, da Lücken auch aufgrund von Transaktions-Rollbacks, ungeplanten Server-Abschaltungen usw. entstehen können
Craig Ringer

1
@Craig: Der Fehler, den ich angesprochen habe (und der nicht vorhanden ist), wäre von Bedeutung, da wir sonst einen doppelten Schlüsselfehler riskieren würden. Die entgegengesetzte Richtung Ihrer Überlegungen; scheint ein Missverständnis.
Erwin Brandstetter

ah, macht Sinn.
Craig Ringer

Das funktioniert bei mir
Hektk

54

Dadurch werden alle öffentlichen Sequenzen zurückgesetzt, ohne dass Annahmen über Tabellen- oder Spaltennamen getroffen werden. Getestet auf 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%';

1
+1 sehr nützliche Funktion! Unsere Sequenznamen stimmten nicht genau mit den Tabellennamen überein, daher habe ich substring(column_default, '''(.*)''')stattdessen verwendet table_name || '_' || column_name || '_seq'. Funktioniert perfekt.
Chris Lercher

4
Beachten Sie, dass dies bei Sequenznamen mit einfachen Anführungszeichen oder bei Tabellennamen mit Großbuchstaben, Leerzeichen usw. im Namen fehlschlägt. Die quote_literalund quote_ident-Funktionen oder vorzugsweise die formatFunktion sollten hier wirklich verwendet werden.
Craig Ringer

2
Ich wünschte, ich könnte dies mehr als eine Stimme geben ... gut gemacht, Sir. Funktioniert auch auf Postgres 9.1 hervorragend, zumindest für mich.
Peelman

1
Das ist toll. Ich habe substring(column_default from 'nextval\(''(.+)''::regclass\)')den Sequenznamen explizit abgerufen. Lief wie am Schnürchen.
Matthew MacDonald

Ich habe seit mehr als einem Tag nach dieser Lösung gesucht. Vielen Dank, auch ich habe die von @ChrisLercher vorgeschlagene Methode verwendet, um den Text zu ersetzensubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv

43

ALTER SEQUENCE Sequenzname RESTART WITH (SELECT max (id) FROM Tabellenname); Funktioniert nicht

Von @tardate Antwort kopiert:

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

8
Das ist ein Syntaxfehler für mich in 8.4 (bei ^ (SELECT ...). RESTART WITH scheint nur einen Ordnungswert zu akzeptieren. Dies funktioniert jedoch: SELECT setval (pg_get_serial_sequence ('table_name', 'id'), (SELECT MAX ( id) FROM table_name) +1);
verspätet

1
Muruges 'Lösung funktioniert auch in 9.4 nicht. Verstehe nicht, warum so viele positive Stimmen zu dieser Antwort abgegeben wurden. ALTER SEQUENCE erlaubt keine Unterabfragen. Die Lösung von @tardate funktioniert einwandfrei. Antwort bearbeitet, um falsche Daten zu entfernen.
Vladislav Rastrusny

ALTER SEQUENCE hat perfekt für mich funktioniert. Ich hatte COPY verwendet, um einige Daten einzubringen, und es gab Lücken in den Primärschlüsseln, und INSERT's lösten Ausnahmen für doppelte Schlüssel aus. Das Einstellen der Sequenz hat den Trick getan. 9.4
user542319

22

Dieser Befehl ändert nur den automatisch generierten Schlüsselsequenzwert in postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

Anstelle von Null können Sie eine beliebige Zahl eingeben, von der aus Sie die Sequenz neu starten möchten.

Standardsequenzname wird "TableName_FieldName_seq". Wenn beispielsweise Ihr Tabellenname "MyTable"und Ihr Feldname lautet "MyID", lautet Ihr Sequenzname "MyTable_MyID_seq".

Diese Antwort entspricht der Antwort von @ murugesanponappan, es liegt jedoch ein Syntaxfehler in seiner Lösung vor. Sie können keine Unterabfrage (select max()...)im alterBefehl verwenden. Damit Sie entweder einen festen numerischen Wert verwenden müssen oder anstelle einer Unterabfrage eine Variable verwenden müssen.


Dies ist die perfekte Lösung. Vielen Dank, Sir. Aber in meinem Fall hatte ich einen Fehler, also musste ich ihn in ALTER SEQUENCE "your_sequence_name" ändern. RESTART WITH 1;
Deunz

18

Alle Sequenzen zurücksetzen, keine Annahmen über Namen, außer dass der Primärschlüssel jeder Tabelle "id" ist:

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

Funktionierte perfekt auf meiner 9.1-Version
Valentin Vasilyev

Sie müssen ein Zitat hinzufügen, wenn die Tabelle Großbuchstaben enthält:pg_get_serial_sequence(''"' || tablename || '"''
Manuel Darveau

Das ist die beste Funktion! Sie können Zitatprobleme vermeiden (und Eleganz verbessern) mit Format, so etwas wie EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss

13

Diese Funktionen sind mit Gefahren behaftet, wenn Sequenznamen, Spaltennamen, Tabellennamen oder Schemanamen lustige Zeichen wie Leerzeichen, Satzzeichen und dergleichen enthalten. Ich habe folgendes geschrieben:

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

Sie können es für eine einzelne Sequenz aufrufen, indem Sie die OID übergeben. Es wird die höchste Nummer zurückgegeben, die von einer Tabelle verwendet wird, in der die Sequenz standardmäßig verwendet wird. oder Sie können es mit einer Abfrage wie dieser ausführen, um alle Sequenzen in Ihrer Datenbank zurückzusetzen:

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

Mit einem anderen Qual können Sie nur die Sequenz in einem bestimmten Schema zurücksetzen und so weiter. Wenn Sie beispielsweise Sequenzen im "öffentlichen" Schema anpassen möchten:

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

Beachten Sie, dass Sie aufgrund der Funktionsweise von setval () keine 1 zum Ergebnis hinzufügen müssen.

Abschließend muss ich warnen, dass einige Datenbanken Standardeinstellungen zu haben scheinen, die mit Sequenzen verknüpft sind, sodass die Systemkataloge nicht über vollständige Informationen verfügen. Dies passiert, wenn Sie solche Dinge in psqls \ d sehen:

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

Beachten Sie, dass der Aufruf von nextval () in dieser Standardklausel zusätzlich zur Umwandlung von :: regclass eine :: text-Umwandlung enthält. Ich denke, das liegt daran, dass Datenbanken aus alten PostgreSQL-Versionen pg_dump'ed werden. Was passieren wird ist, dass die obige Funktion sequence_max_value () eine solche Tabelle ignoriert. Um das Problem zu beheben, können Sie die DEFAULT-Klausel neu definieren, um direkt auf die Sequenz ohne die Besetzung zu verweisen:

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

Dann zeigt psql es richtig an:

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

Sobald Sie dies behoben haben, funktioniert die Funktion für diese Tabelle sowie für alle anderen, die möglicherweise dieselbe Sequenz verwenden, ordnungsgemäß.


Das ist unglaublich danke! Es sollte beachtet werden, dass ich bei der Zuweisung (Zeile 21 im Funktionscode) einen Cast wie folgt hinzufügen musste: newmax := r.max::bigint;damit es für mich richtig funktioniert.
Tommy Bravo

Musste dies ebenfalls ändern: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' Beachten Sie die hinzugefügte ::bigintBesetzung in der dynamisch erstellten Abfrage.
Tommy Bravo

9

Noch ein plpgsql - wird nur zurückgesetzt, wenn 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;
$$
;

Wenn Sie die Zeile auch kommentieren, --execute format('alter sequencewird die Liste angezeigt, ohne dass der Wert zurückgesetzt wird


8

Setzen Sie alle Sequenzen von public zurück

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

Es scheint, dass dieser Ansatz Annahmen über die Spalten- und Tabellennamen macht, so dass es für mich nicht funktioniert hat
djsnowsill

Würde das nicht Daten in der Datenbank beschädigen?
Zennin

8

Ich schlage vor, diese Lösung im Postgres-Wiki zu finden. Es aktualisiert alle Sequenzen Ihrer Tabellen.

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;

Verwendung (aus dem Postgres-Wiki):

  • Speichern Sie dies in einer Datei, sagen Sie 'reset.sql'
  • Führen Sie die Datei aus und speichern Sie ihre Ausgabe auf eine Weise, die nicht die üblichen Header enthält. Führen Sie dann diese Ausgabe aus. Beispiel:

Beispiel:

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

Originalartikel (auch mit Fix für Sequenzbesitz) hier


7

Einige wirklich harte Antworten hier, ich gehe davon aus, dass es zu der Zeit, als dies gefragt wurde, wirklich schlecht war, da viele Antworten von hier für Version 9.3 nicht funktionieren. Die Dokumentation seit Version 8.0 bietet eine Antwort auf genau diese Frage:

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

Wenn Sie sich um Sequenznamen kümmern müssen, bei denen zwischen Groß- und Kleinschreibung unterschieden wird, gehen Sie folgendermaßen vor:

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

7

Dieses Problem tritt bei mir auf, wenn das Entity Framework zum Erstellen der Datenbank verwendet und die Datenbank dann mit Anfangsdaten versehen wird. Dadurch stimmt die Sequenz nicht überein.

Ich habe es gelöst, indem ich ein Skript erstellt habe, das nach dem Seeding der Datenbank ausgeführt werden soll:

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
warum das MAX("Id") + 1bei mir am besten funktioniert wenn die sequenz = maximal ist.
Lastlink

6

Meine Version verwendet die erste, mit einigen Fehlerprüfungen ...

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;

Vielen Dank für die Fehlerprüfung! Sehr geschätzt, da die Tabellen- / Spaltennamen abgeschnitten werden, wenn sie zu lang sind, was Sie RAISE WARNINGfür mich identifiziert haben.
Nicholas Riley

5

Alles zusammenfügen

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

wird die id'Reihenfolge der angegebenen Tabelle festlegen (wie dies normalerweise bei Django erforderlich ist).


4

bevor ich den Code noch nicht ausprobiert hatte: Im Folgenden poste ich die Version für den SQL-Code für Klaus- und user457226-Lösungen, die auf meinem PC [Postgres 8.3] funktionierten, mit nur ein paar kleinen Anpassungen für den Klaus und meine Version für den Benutzer457226 eine.

Klaus Lösung:

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

user457226 Lösung:

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

Überprüfen Sie alle Sequenzen in der öffentlichen Schemafunktion erneut

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

Um alle Sequenzen auf 1 neu zu starten, verwenden Sie:

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

Die Klaus-Antwort ist die nützlichste, die für einen kleinen Fehler ausgeführt wird: Sie müssen DISTINCT in die select-Anweisung einfügen.

Wenn Sie jedoch sicher sind, dass keine Tabellen- und Spaltennamen für zwei verschiedene Tabellen gleichwertig sein können, können Sie auch Folgendes verwenden:

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

Dies ist eine Erweiterung der user457226-Lösung für den Fall, dass ein interessierter Spaltenname nicht 'ID' ist.


... natürlich ist auch eine Änderung in "reset_sequence" erforderlich, dh das Hinzufügen eines "Spaltennamens" -Parameters anstelle von "id".
Mauro

2

Wenn dieser Fehler beim Laden benutzerdefinierter SQL-Daten zur Initialisierung angezeigt wird, können Sie dies auf folgende Weise vermeiden:

Anstatt zu schreiben:

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

Entfernen Sie den id(Primärschlüssel) aus den Anfangsdaten

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

Dies hält die Postgres-Sequenz synchron!


2

Diese Antwort ist eine Kopie von 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

Ich habe eine Stunde lang versucht, die Antwort von djsnowsill für die Arbeit mit einer Datenbank mithilfe von Tabellen und Spalten mit gemischten Groß- und Kleinschreibung zu finden, und bin dann dank eines Kommentars von Manuel Darveau schließlich auf die Lösung gestoßen, aber ich dachte, ich könnte es für alle etwas klarer machen:

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

Dies hat den Vorteil von:

  • Wenn Sie nicht davon ausgehen, dass die ID-Spalte auf eine bestimmte Weise geschrieben ist.
  • Nicht vorausgesetzt, alle Tabellen haben eine Sequenz.
  • Arbeiten für Tabellen- / Spaltennamen in gemischten Fällen.
  • Verwenden Sie das Format, um präziser zu sein.

Um dies zu erklären, bestand das Problem darin, dass pg_get_serial_sequenceZeichenfolgen erforderlich sind, um herauszufinden, worauf Sie sich beziehen. Wenn Sie also Folgendes tun:

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

Dies wird erreicht, indem ''%1$I''in der Formatzeichenfolge verwendet wird, ''ein Apostroph 1$bedeutet erstes Argument und Ibedeutet in Anführungszeichen


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
Während dieser Code die Frage möglicherweise beantwortet, verbessert die Bereitstellung eines zusätzlichen Kontexts darüber, warum und / oder wie dieser Code die Frage beantwortet, ihren langfristigen Wert.
Yeya

1

Hässlicher Hack, um es mit etwas Shell-Magie zu beheben, keine großartige Lösung, könnte aber andere mit ähnlichen Problemen inspirieren :)

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

Versuchen Sie es erneut .

UPDATE: Wie in den Kommentaren erwähnt, war dies eine Antwort auf die ursprüngliche Frage.


Reindex hat nicht funktioniert, es scheint nur den Index um 1 zu
erhöhen

3
Reindex hat nicht funktioniert, weil es Ihre ursprüngliche Frage zu Datenbankindizes und nicht zu Sequenzen
beantwortet hat

0

SELECT setval... macht JDBC bork, also hier ist eine Java-kompatible Möglichkeit, dies zu tun:

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

Eine Methode zum Aktualisieren aller Sequenzen in Ihrem Schema, die als ID verwendet werden:

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

Führen Sie einfach den folgenden Befehl aus:

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

0

Hier gibt es viele gute Antworten. Ich hatte das gleiche Bedürfnis nach dem Neuladen meiner Django-Datenbank.

Aber ich brauchte:

  • Alles in einer Funktion
  • Könnte ein oder mehrere Schemas gleichzeitig reparieren
  • Könnte alle oder nur eine Tabelle gleichzeitig reparieren
  • Wollte auch eine schöne Möglichkeit, genau zu sehen, was sich geändert hatte oder nicht

Dies scheint dem Bedarf der ursprünglichen Anfrage sehr ähnlich zu sein.
Dank Baldiry und Mauro bin ich auf dem richtigen Weg.

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;

Um die Änderungen auszuführen und zu sehen, führen Sie sie aus:

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

Kehrt zurück

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.