Jak zresetować sekwencję klucza głównego postgres, gdy nie synchronizuje się?


522

Wystąpił problem polegający na tym, że moja sekwencja kluczy podstawowych nie jest zsynchronizowana z wierszami tabeli.

Oznacza to, że po wstawieniu nowego wiersza pojawia się duplikat błędu klucza, ponieważ sekwencja sugerowana w typie danych szeregowych zwraca liczbę, która już istnieje.

Wydaje się, że jest to spowodowane importem / przywróceniem niewłaściwego utrzymywania sekwencji.


Jestem ciekawy ... czy upuszczasz db przed przywróceniem? Mam nikłe wspomnienie tego wydarzenia, ale mogę się mylić: P
Arthur Thomas

25
Wiki PostgreSQL ma stronę o Sekwencjach Naprawiania .
Brad Koch,

14
Aby ułatwić wyszukiwarkę Google, wyświetlony został komunikat o błędzie: „zduplikowana wartość klucza narusza unikalne ograniczenie ...”
superluminar

4
W ten sposób robi to funkcja sqlsequencereset w Django: WYBIERZ setval (pg_get_serial_sequence („<nazwa tabeli>>,„ id ”), coalesce (max („ id ”), 1), max („ id ”) NIE JEST zerowy) OD„ < nazwa_tabeli> ";
użytkownik

Pierwsze wystąpienie <nazwa tabeli> musi być zawinięte w pojedyncze cudzysłowy, aby funkcja pg_get_serioal_sequence mogła działać: SELECT setval (pg_get_serial_sequence ('<nazwa tabeli>>,' id '), coalesce (max ("id"), 1) , max („id”) NIE JEST zerowy) OD „<nazwa_tabeli>”
nclu

Odpowiedzi:


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;

Źródło - Ruby Forum


12
W każdym razie dodanie 1 do MAX (id) pozostawi jedną przerwę liczbową w twoich identyfikatorach, ponieważ zestaw setval jest ostatnią wartością sekwencji, a nie następną.
mikl

6
Twój przykład nie będzie działać, jeśli w tabeli nie ma wierszy. Zatem podane poniżej SQL jest bezpieczniejsze: SELECT setval ('twoja_tabela_id_seq', coalesce ((wybierz max (id) +1 z twojej_tabeli), 1), prawda);
Valery Viktorovsky

10
@Valery: Ale aby uniknąć luk wymienionych przez @mikl dwa komentarze powyżej, potrzebujeszSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins

20
Wszystkie problemy rozwiązane i połączone w jedno zapytanie:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi

15
Jeśli aplikacja dba o luki w sekwencjach, aplikacja jest zepsuta. Luki w sekwencjach są normalne i mogą wystąpić z powodu nieplanowanych wyłączeń bazy danych, wycofywania transakcji po błędach itp.
Craig Ringer

202

pg_get_serial_sequencemożna użyć, aby uniknąć niepoprawnych założeń dotyczących nazwy sekwencji. Spowoduje to zresetowanie sekwencji w jednym ujęciu:

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

Lub bardziej zwięźle:

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

Jednak ta forma nie może poprawnie obsługiwać pustych tabel, ponieważ max (id) ma wartość null i nie można ustawić wartości 0, ponieważ byłoby to poza zakresem sekwencji. Jednym z obejść tego problemu jest skorzystanie ze ALTER SEQUENCEskładni, tj

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

Ma jednak ALTER SEQUENCEograniczone zastosowanie, ponieważ nazwa sekwencji i wartość restartu nie mogą być wyrażeniami.

Wydaje się, że najlepszym rozwiązaniem uniwersalnym jest wywoływanie setvalz fałszem jako trzecim parametrem, co pozwala nam określić „następną wartość do użycia”:

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

To zaznacza wszystkie moje pola:

  1. unika zakodowania rzeczywistej nazwy sekwencji
  2. poprawnie obsługuje puste tabele
  3. obsługuje tabele z istniejącymi danymi i nie pozostawia dziury w sekwencji

Na koniec zauważ, że pg_get_serial_sequencedziała tylko wtedy, gdy sekwencja jest własnością kolumny. Będzie tak w przypadku, gdy kolumna inkrementująca została zdefiniowana jako serialtyp, jednak jeśli sekwencja została dodana ręcznie, należy upewnić ALTER SEQUENCE .. OWNED BYsię, że również zostanie wykonana.

tzn. jeśli serialdo utworzenia tabeli użyto typu, wszystko powinno działać:

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;

Ale jeśli sekwencje zostały dodane ręcznie:

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
W zapytaniu „+1” nie ma potrzeby setval()ustawiania bieżącej wartości i nextval()już zwróci bieżącą wartość +1.
Antony Hatchkins,

1
Funkcja owijania tej metody, która przyjmuje jeden parametr - nazwa_tabeli - znajduje się w mojej odpowiedzi poniżej: stackoverflow.com/a/13308052/237105
Antony Hatchkins

@AntonyHatchkins pozdrawia. Właśnie zobaczyłem kolejną powtórkę błędu +1, więc w końcu uderzyłem, że na dobre mam nadzieję
opóźnienie

98

Najkrótszy i najszybszy sposób:

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

tbl_idbędąc serialkolumną tabeli tbl, rysując z sekwencji tbl_tbl_id_seq(która jest domyślną nazwą automatyczną).

Jeśli nie znasz nazwy dołączonej sekwencji (która nie musi być w domyślnej formie), użyj pg_get_serial_sequence():

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

Nie ma tutaj błędu off-by-one. Według dokumentacji:

Formularz dwuparametrowy ustawia pole sekwencji last_valuena określoną wartość i ustawia jej is_calledpole na true, co oznacza, że następny nextvalprzesunie sekwencję przed zwróceniem wartości.

Odważny nacisk moje.

Jeśli tabela może być pusta i zacząć od 1 w tym przypadku:

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

Nie możemy po prostu użyć postaci 2-paremater i zacząć od, 0ponieważ dolna granica sekwencji wynosi domyślnie 1 (chyba że niestandardowe).

Konkurencja

Nie ma jeszcze obrony przed równoczesną aktywnością sekwencji lub zapisuje do tabeli w powyższych zapytaniach. Jeśli to istotne, możesz zablokować tabelę w trybie wyłączności. Uniemożliwia jednoczesnym transakcjom zapisywanie większej liczby podczas próby synchronizacji. (Tymczasowo blokuje również nieszkodliwe zapisy, nie mieszając się z maksymalną liczbą).

Ale nie bierze pod uwagę klientów, którzy mogli wcześniej pobrać numery sekwencyjne bez żadnych blokad głównego stołu (co może się zdarzyć). Aby na to pozwolić, zwiększaj tylko bieżącą wartość sekwencji, nigdy jej nie zmniejszaj. Może się to wydawać paranoikiem, ale jest to zgodne z naturą sekwencji i obrony przed problemami współbieżności.

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;

Gdzie „STANDARDOWA biblioteka społecznościowa o podstawowych funkcjach”? Druga klauzula selekcji tej odpowiedzi w EXECUTE format()(jak @ EB.) Jest istotną funkcją! Jak naprawić ten brak standardowej biblioteki w PostgreSQL ????
Peter Krauss

Nie ma znaczenia, czy jest coś wyjątkowego. Luki w sekwencji są normalne. Jeśli aplikacja nie jest w stanie sobie z tym poradzić, aplikacja jest zepsuta, ponieważ luki mogą również wynikać z wycofywania transakcji, nieplanowanych wyłączeń serwerów itp.
Craig Ringer

1
@Craig: Błąd „jeden po drugim”, do którego się odniosłem (i którego nie ma), miałby znaczenie, ponieważ w przeciwnym razie ryzykowalibyśmy duplikatem błędu klucza. Przeciwny kierunek waszych rozważań; wydaje się nieporozumieniem.
Erwin Brandstetter,

ah, ma sens.
Craig Ringer

To działa dla mnie
hektk

54

Spowoduje to zresetowanie wszystkich sekwencji z publicznego, nie przyjmując żadnych założeń dotyczących nazw tabel lub kolumn. Testowany na wersji 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 bardzo przydatna funkcja! Nasze nazwy sekwencji nie pasowały dokładnie do nazw tabel, więc użyłem substring(column_default, '''(.*)''')zamiast table_name || '_' || column_name || '_seq'. Działa świetnie.
Chris Lercher

4
Zauważ, że to się nie powiedzie w przypadku nazw sekwencji zawierających pojedyncze cudzysłowy lub nazw tabel zawierających wielkie litery, spacje itp. W ich nazwie. quote_literalI quote_identfunkcje, lub korzystniej formatfunkcja powinna być naprawdę tu stosowane.
Craig Ringer

2
Żałuję, że nie mogę oddać więcej niż jednego głosu ... proszę pana. Działa świetnie również na Postgres 9.1, przynajmniej dla mnie.
peelman

1
To jest świetne. Kiedyś substring(column_default from 'nextval\(''(.+)''::regclass\)')jawnie chwycić nazwę sekwencji. Działa jak urok.
Matthew MacDonald

Szukałem tego rozwiązania przez ponad dzień, bardzo dziękuję, nawet użyłem metody zaproponowanej przez @ChrisLercher, aby zastąpić tekstsubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv

43

ALTER SEQUENCE nazwa_sekwencji RESTART Z (WYBIERZ max (id) FROM nazwa_tabeli); Nie działa

Skopiowano z opóźnionej odpowiedzi:

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

8
to dla mnie błąd składniowy w 8.4 (w ^ (WYBIERZ ...). RESTART Z wydaje się akceptować tylko wartość porządkową. Działa to jednak: WYBIERZ setval (pg_get_serial_sequence ('nazwa_tabeli', 'id'), (SELECT MAX ( id) FROM nazwa_tabeli) +1);
opóźnienie

1
Rozwiązanie Murugesa również nie działa w wersji 9.4. Nie rozumiem, dlaczego tyle głosów poparcia dla tej odpowiedzi. ALTER SEQUENCE nie zezwala na podzapytania. Rozwiązanie @tardate działa idealnie. Edytowana odpowiedź, aby usunąć nieprawidłowe dane.
Vladislav Rastrusny

ALTER SEQUENCE działał idealnie dla mnie. Użyłem funkcji COPY do wprowadzenia niektórych danych, w kluczach głównych były luki, a INSERT zgłaszał wyjątki od duplikatów kluczy. Ustawienie sekwencji załatwiło sprawę. 9.4
user542319,

22

To polecenie służy tylko do zmiany automatycznie generowanej wartości sekwencji kluczy w postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

Zamiast zera możesz podać dowolną liczbę, od której chcesz zrestartować sekwencję.

domyślna nazwa sekwencji to "TableName_FieldName_seq". Na przykład, jeśli nazwa tabeli to "MyTable"i nazwa pola to "MyID", nazwa sekwencji będzie"MyTable_MyID_seq" .

To odpowiedź jest taka sama jak odpowiedź @ murugesanponappan, ale w jego rozwiązaniu występuje błąd składniowy. nie można użyć zapytania podrzędnego (select max()...)w alterpoleceniu. Tak więc albo musisz użyć stałej wartości liczbowej, albo musisz użyć zmiennej zamiast zapytania podrzędnego.


To idealne rozwiązanie, dziękuję bardzo, proszę pana. Ale w moim przypadku wystąpił błąd, więc musiałem go zmienić na ALTER SEQUENCE „your_sequence_name” RESTART WITH 1;
Deunz,

18

Zresetuj wszystkie sekwencje, bez założeń dotyczących nazw, z wyjątkiem tego, że kluczem podstawowym każdej tabeli jest „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%';

Doskonale działał na mojej wersji 9.1
Valentin Vasilyev

Musisz dodać cytat, jeśli tabela zawiera wielkie litery:pg_get_serial_sequence(''"' || tablename || '"''
Manuel Darveau

To najlepsza funkcja! Możesz uniknąć problemów z wyceną (i zwiększyć elegancję) dzięki formatowi, coś w stylu EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss

13

Funkcje te są obarczone niebezpieczeństwem, gdy nazwy sekwencji, nazwy kolumn, nazwy tabel lub nazwy schematów mają zabawne znaki, takie jak spacje, znaki interpunkcyjne i tym podobne. Napisałem to:

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

Możesz wywołać ją dla pojedynczej sekwencji, przekazując jej identyfikator OID, a ona zwróci najwyższy numer używany przez dowolną tabelę, która ma domyślną sekwencję; lub możesz uruchomić go z takim zapytaniem, aby zresetować wszystkie sekwencje w bazie danych:

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

Używając innej jakości możesz zresetować tylko sekwencję w określonym schemacie i tak dalej. Na przykład, jeśli chcesz dostosować sekwencje w schemacie „publicznym”:

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

Zauważ, że z powodu działania setval () nie musisz dodawać 1 do wyniku.

Na zakończenie muszę ostrzec, że niektóre bazy danych wydają się mieć domyślne ustawienia łączące z sekwencjami w sposób, który nie pozwala katalogom systemowym mieć pełnych informacji na ich temat. Dzieje się tak, gdy widzisz takie rzeczy w psql \ d:

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

Zauważ, że wywołanie nextval () w tej domyślnej klauzuli ma rzutowanie :: text oprócz rzutowania :: regclass. Myślę, że jest to spowodowane tym, że bazy danych są pg_dump'ed ze starych wersji PostgreSQL. To, co się stanie, to funkcja powyższej tabeli sekwencja_max_value () zignoruje taką tabelę. Aby rozwiązać problem, możesz ponownie zdefiniować klauzulę DOMYŚLNĄ, aby odwoływała się do sekwencji bezpośrednio bez rzutowania:

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

Następnie psql wyświetla go poprawnie:

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

Jak tylko to naprawisz, funkcja działa poprawnie dla tej tabeli, jak również dla wszystkich innych, które mogą używać tej samej sekwencji.


To niesamowite, dzięki! Należy zauważyć, że musiałem dodać rzutowanie przy przypisaniu (wiersz 21 w kodzie funkcji) w następujący sposób: newmax := r.max::bigint;aby działało poprawnie dla mnie.
Tommy Bravo

Musiałem to również zmienić: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' zauważ dodaną ::bigintobsadę w zapytaniu budowanym dynamicznie.
Tommy Bravo

9

Jeszcze inny plpgsql - resetuje tylko jeśli 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;
$$
;

komentowanie wiersza --execute format('alter sequencespowoduje wyświetlenie listy, a nie zresetowanie wartości


8

Zresetuj całą sekwencję z publicznego

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

Wygląda na to, że takie podejście zakłada założenia dotyczące nazw kolumn i tabel, więc nie zadziałało
djsnowsill

Czy nie uszkodzi to danych w bazie danych?
zennin

8

Sugeruję to rozwiązanie znalezione na wiki Postgres. Aktualizuje wszystkie sekwencje twoich tabel.

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;

Jak korzystać (z wiki postgres):

  • Zapisz to w pliku, powiedz „reset.sql”
  • Uruchom plik i zapisz dane wyjściowe w sposób, który nie obejmuje zwykłych nagłówków, a następnie uruchom dane wyjściowe. Przykład:

Przykład:

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

Artykuł oryginalny (również z poprawką dotyczącą własności sekwencji) tutaj


7

Oto niektóre naprawdę hardcorowe odpowiedzi, zakładam, że kiedyś było bardzo źle, kiedy o to pytano, ponieważ wiele odpowiedzi tutaj nie działa w wersji 9.3. Dokumentacja od wersji 8.0 daje odpowiedź do tego samego pytania:

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

Ponadto, jeśli musisz zająć się rozróżnianiem wielkości liter w nazwach sekwencji, możesz to zrobić w następujący sposób:

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

7

Ten problem występuje ze mną, gdy używam struktury encji do utworzenia bazy danych, a następnie zapełniam bazę danych danymi początkowymi, co powoduje niedopasowanie sekwencji.

Rozwiązałem go, tworząc skrypt do uruchomienia po zainicjowaniu bazy danych:

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
dlaczego MAX("Id") + 1działa najlepiej dla mnie, gdy sekwencja jest = do maksimum.
lastlink

6

Moja wersja używa pierwszej, z pewną kontrolą błędów ...

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;

Dziękujemy za sprawdzenie błędu! Bardzo doceniane, ponieważ nazwy tabel / kolumn są obcinane, jeśli są zbyt długie, co RAISE WARNINGdla mnie zidentyfikowano.
Nicholas Riley

5

Kładąc wszystko razem

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

naprawi id'sekwencję podanej tabeli (jak to zwykle konieczne na przykład w przypadku django).


4

zanim jeszcze nie wypróbowałem kodu: poniżej zamieszczam wersję kodu SQL dla rozwiązań Klausa i user457226, które działały na moim komputerze [Postgres 8.3], z niewielkimi modyfikacjami dla Klausa i mojej wersji dla użytkownika 457226 jeden.

Rozwiązanie 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();

rozwiązanie 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

Sprawdź ponownie całą sekwencję w funkcji schematu publicznego

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

Aby ponownie uruchomić wszystkie sekwencje do 1, użyj:

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

Odpowiedź Klausa jest najbardziej użyteczna, wykonywana za krótką chwilę: musisz dodać DISTINCT w instrukcji select.

Jeśli jednak masz pewność, że żadna nazwa tabeli + kolumny nie może być równoważna dla dwóch różnych tabel, możesz również użyć:

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

który jest rozszerzeniem rozwiązania user457226 dla przypadku, gdy nazwa zainteresowanej kolumny nie jest „ID”.


... oczywiście potrzebna jest również zmiana parametru „reset_sequence”, czyli dodanie parametru „nazwa kolumny”, który ma być używany zamiast „id”.
mauro,

2

Jeśli zobaczysz ten błąd podczas ładowania niestandardowych danych SQL do inicjalizacji, możesz tego uniknąć inaczej:

Zamiast pisać:

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

Usuń id(klucz podstawowy) z danych początkowych

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

To utrzymuje synchronizację sekwencji Postgres!


2

Ta odpowiedź jest kopią z 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

Spędziłem godzinę próbując uzyskać odpowiedź djsnowsill do pracy z bazą danych przy użyciu tabel i kolumn Mixed Case, a potem w końcu natknąłem się na rozwiązanie dzięki komentarzowi Manuela Darveau, ale pomyślałem, że mógłbym to zrobić dla wszystkich nieco jaśniej:

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

Ma to tę zaletę, że:

  • nie zakładając, że kolumna identyfikatora jest zapisana w określony sposób.
  • nie zakładając, że wszystkie tabele mają sekwencję.
  • pracuje dla nazw tabel / kolumn o różnej wielkości liter.
  • używając formatu, aby być bardziej zwięzłym.

Aby wyjaśnić, problem polegał na tym, że pg_get_serial_sequenceciągi znaków opracowują to, o czym mówisz, więc jeśli to zrobisz:

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

Uzyskuje się to za pomocą ''%1$I''ciągu formatu, ''apostrof 1$oznacza pierwszy argument i Ioznacza w cudzysłowie


1

Brzydki hack, aby naprawić to za pomocą magii powłoki, nie jest to świetne rozwiązanie, ale może zainspirować innych z podobnymi problemami :)

pg_dump -s <DATABASE> | grep 'CREATE TABLE' | awk '{print "SELECT setval(#" $3 "_id_seq#, (SELECT MAX(id) FROM " $3 "));"}' | sed "s/#/'/g" | psql <DATABASE> -f -

1
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
Chociaż ten kod może odpowiedzieć na pytanie, zapewnienie dodatkowego kontekstu dotyczącego tego, dlaczego i / lub jak ten kod odpowiada na pytanie, poprawia jego długoterminową wartość.
yeya,

0

Spróbuj ponownie .

AKTUALIZACJA: Jak wskazano w komentarzach, była to odpowiedź na pierwotne pytanie.


reindex nie działał, wydaje się jedynie zwiększać indeks o 1
meleyal

3
reindex nie działał, ponieważ odpowiadał na twoje pierwotne pytanie, dotyczące indeksów baz danych, a nie sekwencji
Vinko Vrsalovic

0

SELECT setval... sprawia, że ​​JDBC jest bork, więc oto sposób na zrobienie tego:

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

Metoda aktualizacji wszystkich sekwencji w schemacie używanych jako identyfikator:

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

Po prostu uruchom poniżej polecenia:

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

0

Tutaj jest wiele dobrych odpowiedzi. Miałem tę samą potrzebę po przeładowaniu bazy danych Django.

Ale potrzebowałem:

  • Wszystko w jednej funkcji
  • Można naprawić jeden lub więcej schematów jednocześnie
  • Można naprawić wszystkie lub tylko jeden stół na raz
  • Chciałem także ciekawy sposób, aby zobaczyć dokładnie, co się zmieniło lub nie zmieniło

Wydaje się, że jest to bardzo podobna potrzeba do pierwotnej prośby.
Dzięki Baldiry i Mauro wprowadziłem mnie na właściwy tor.

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;

Następnie, aby wykonać i zobaczyć zmiany uruchom:

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

Zwroty

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.