Bagaimana cara mereset urutan kunci utama postgres ketika tidak sinkron?

523

Saya mengalami masalah bahwa urutan kunci utama saya tidak sinkron dengan baris tabel saya.

Yaitu, ketika saya menyisipkan baris baru saya mendapatkan kesalahan kunci duplikat karena urutan yang tersirat dalam datatype serial mengembalikan nomor yang sudah ada.

Tampaknya disebabkan oleh impor / mengembalikan tidak mempertahankan urutan dengan benar.

meleyal
sumber
Saya ingin tahu .. apakah Anda menjatuhkan db sebelum Anda mengembalikan? Saya memiliki ingatan samar tentang hal ini terjadi, tetapi saya bisa saja salah: P
Arthur Thomas
25
Wiki PostgreSQL memiliki halaman tentang Memperbaiki Urutan .
Brad Koch
14
Hanya untuk membantu googleability, pesan kesalahan yang dilemparkan di sini adalah: "duplikat nilai kunci melanggar batasan unik ..."
superluminary
4
Ini adalah bagaimana sql berikutnyaencereset di Django melakukannya: SELECT setval (pg_get_serial_ followingence ("<table_name>", 'id'), coalesce (max ("id"), 1), max ("id") BUKAN null) DARI "< table_name> ";
pengguna
Contoh pertama dari <nama tabel> perlu dibungkus dengan tanda kutip tunggal agar fungsi pg_get_serioal_ berikutnyaence berfungsi: SELECT setval (pg_get_serial_ followingence ('<table_name>', 'id'), coalesce (maks ("id"), 1) , maks ("id") BUKAN nol) DARI "<table_name>"
nclu

Jawaban:

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;

Sumber - Forum Ruby

meleyal
sumber
12
Bagaimanapun, menambahkan 1 ke MAX (id) akan meninggalkan celah nomor tunggal di ID Anda, karena set setval adalah nilai terakhir dari urutan, bukan yang berikutnya.
mikl
6
Contoh Anda tidak akan berfungsi jika tidak ada baris dalam tabel. Jadi, SQL yang diberikan di bawah ini lebih aman: SELECT setval ('your_table_id_seq', coalesce ((pilih maks (id) +1 dari your_table), 1), true);
Valery Viktorovsky
10
@Valery: Tetapi untuk menghindari kesenjangan yang disebutkan oleh @mikl dua komentar di atas, Anda perluSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins
20
Semua masalah diselesaikan dan digabungkan menjadi satu permintaan:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi
15
Jika aplikasi Anda peduli dengan kesenjangan dalam urutan, aplikasi Anda rusak. Kesenjangan dalam urutan adalah normal, dan dapat terjadi karena penutupan basis data yang tidak direncanakan, pengembalian transaksi setelah kesalahan, dll.
Craig Ringer
202

pg_get_serial_sequencedapat digunakan untuk menghindari asumsi yang salah tentang nama urutan. Ini mengatur ulang urutan dalam satu pemotretan:

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

Atau lebih ringkas:

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

Namun formulir ini tidak dapat menangani tabel kosong dengan benar, karena maks (id) adalah nol, dan Anda juga tidak dapat menetapkan 0 karena itu akan berada di luar kisaran urutan. Salah satu solusi untuk ini adalah dengan menggunakanALTER SEQUENCE sintaks yaitu

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

Tetapi ALTER SEQUENCEpenggunaannya terbatas karena nama urutan dan nilai restart tidak dapat berupa ekspresi.

Tampaknya solusi serba guna terbaik adalah memanggil setvaldengan false sebagai parameter ke-3, memungkinkan kami menentukan "nilai selanjutnya yang akan digunakan":

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

Ini mencentang semua kotak saya:

  1. menghindari pengodean nama urutan yang sebenarnya
  2. menangani tabel kosong dengan benar
  3. menangani tabel dengan data yang ada, dan tidak meninggalkan lubang dalam urutan

Akhirnya, perhatikan bahwa pg_get_serial_sequencehanya berfungsi jika urutan dimiliki oleh kolom. Ini akan menjadi kasus jika kolom tambahan didefinisikan sebagai serialtipe, namun jika urutan ditambahkan secara manual maka perlu untuk memastikanALTER SEQUENCE .. OWNED BY juga dilakukan.

yaitu jika serialjenis digunakan untuk pembuatan tabel, ini semua harus bekerja:

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;

Tetapi jika urutan ditambahkan secara manual:

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;
tardate
sumber
11
Tidak perlu di '+1' dalam kueri, setval()tetapkan nilai saat ini, dan nextval()sudah akan mengembalikan nilai saat ini +1.
Antony Hatchkins
1
Fungsi membungkus metode ini yang mengambil satu parameter - table_name - ada dalam jawaban saya di bawah ini: stackoverflow.com/a/13308052/237105
Antony Hatchkins
@AntonyHatchkins bersorak. Baru saja melihat pengulangan bug +1 sehingga akhirnya menepuk itu untuk kebaikan saya harap
tardate
99

Cara terpendek dan tercepat :

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

tbl_idmenjadi serialkolom tabel tbl, menggambar dari urutan tbl_tbl_id_seq(yang merupakan nama otomatis default).

Jika Anda tidak tahu nama urutan terlampir (yang tidak harus dalam bentuk default), gunakan pg_get_serial_sequence():

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

Tidak ada kesalahan satu-persatu di sini. Per dokumentasi:

Bentuk dua parameter mengatur bidang urutan last_valueke nilai yang ditentukan dan menetapkan is_calledbidangnya ke true, yang berarti bahwa berikutnya nextvalakan memajukan urutan sebelum mengembalikan nilai.

Penekanan berani saya.

Jika tabel dapat kosong dan mulai dari 1 dalam kasus ini:

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

Kita tidak bisa hanya menggunakan bentuk 2-paremater dan mulai dengan 0karena batas urutan yang lebih rendah adalah 1 secara default (kecuali disesuaikan).

Konkurensi

Tidak ada pertahanan terhadap aktivitas urutan bersamaan atau menulis ke tabel dalam pertanyaan di atas, belum. Jika itu relevan, Anda dapat mengunci tabel dalam mode eksklusif. Itu menjaga transaksi bersamaan dari menulis angka yang lebih tinggi saat Anda mencoba untuk menyinkronkan. (Ini juga untuk sementara memblokir penulisan yang tidak berbahaya yang tidak mengacaukan jumlah maksimum.)

Tapi itu tidak memperhitungkan klien yang mungkin telah mengambil nomor urut sebelumnya tanpa kunci di tabel utama, (yang dapat terjadi). Untuk memungkinkannya juga, hanya meningkatkan nilai urutan saat ini, jangan pernah menguranginya. Ini mungkin tampak paranoid, tapi itu sesuai dengan sifat dari sekuens dan bertahan terhadap masalah konkurensi.

BEGIN;

LOCK TABLE tbl IN EXCLUSIVE MODE;

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

COMMIT;
Erwin Brandstetter
sumber
Di mana "STANDAR komunitas-perpustakaan fungsi penting"? Klausa pilih kedua dari jawaban ini dalam EXECUTE format()(seperti @ EB.) Adalah fungsi penting! Bagaimana cara memperbaiki kekurangan perpustakaan standar ini di PostgreSQL ????
Peter Krauss
Tidak masalah jika ada yang off-by-one. Kesenjangan dalam urutan adalah normal. Jika aplikasi Anda tidak dapat mengatasinya, aplikasi Anda rusak, karena kesenjangan juga dapat timbul karena kembalikan transaksi, penutupan server yang tidak direncanakan, dll.
Craig Ringer
1
@Craig: Kesalahan off-by-one yang saya bahas (dan tidak ada) akan menjadi masalah karena kami akan mengambil risiko kesalahan duplikat kunci sebaliknya. Arah berlawanan dari pertimbangan Anda; sepertinya kesalahpahaman.
Erwin Brandstetter
ah, masuk akal.
Craig Ringer
Ini bekerja untuk saya
hektar
54

Ini akan mengatur ulang semua urutan dari publik tanpa membuat asumsi tentang nama tabel atau kolom. Diuji pada versi 8.4

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text, sequence_name text) RETURNS "pg_catalog"."void" AS 

    $body$  
      DECLARE 
      BEGIN 

      EXECUTE 'SELECT setval( ''' || sequence_name  || ''', ' || '(SELECT MAX(' || columnname || ') FROM ' || tablename || ')' || '+1)';



      END;  

    $body$  LANGUAGE 'plpgsql';


    select table_name || '_' || column_name || '_seq', reset_sequence(table_name, column_name, table_name || '_' || column_name || '_seq') from information_schema.columns where column_default like 'nextval%';
djsnowsill
sumber
1
+1 fungsi yang sangat berguna! Nama urutan kami tidak sama persis dengan nama tabel, jadi saya menggunakan substring(column_default, '''(.*)''')bukan table_name || '_' || column_name || '_seq'. Bekerja dengan sempurna.
Chris Lercher
4
Perhatikan bahwa ini akan gagal dengan nama urutan yang mengandung tanda kutip tunggal, atau nama tabel dengan huruf besar, spasi, dll dalam namanya. Fungsi quote_literaldan quote_ident, atau lebih baik formatfungsi, harus benar-benar digunakan di sini.
Craig Ringer
2
Seandainya saya bisa memberikan ini lebih dari satu suara ... dilakukan dengan baik tuan. Sangat bagus untuk Postgres 9.1, setidaknya untuk saya.
peelman
1
Ini bagus. Saya dulu substring(column_default from 'nextval\(''(.+)''::regclass\)')secara eksplisit mengambil nama urutan. Bekerja seperti pesona.
Matthew MacDonald
Saya sedang mencari solusi ini selama lebih dari satu hari sekarang, Terima kasih banyak, bahkan saya menggunakan metode yang disarankan oleh @ChrisLercher, untuk mengganti tekssubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv
43

ALTER SEQUENCE sequence_name RESTART WITH (SELECT max (id) FROM table_name); Tidak bekerja

Disalin dari @tardate answer:

SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;
tardate
sumber
8
itu kesalahan sintaks untuk saya di 8,4 (at ^ (SELECT ...). RESTART WITH tampaknya hanya menerima nilai ordinal. Ini berfungsi meskipun: SELECT setval (pg_get_serial_afterence ('table_name', 'id'), (SELECT MAX ( id) FROM table_name) +1);
tardate
1
Solusi Muruges tidak bekerja di 9.4 juga. Tidak mengerti mengapa begitu banyak jawaban dalam jawaban ini. ALTER SEQUENCE tidak mengizinkan subqueries. Solusi oleh @tardate bekerja dengan sempurna. Jawaban yang diedit untuk menghapus data yang salah.
Vladislav Rastrusny
ALTER SEQUENCE bekerja sempurna untuk saya. Saya telah menggunakan COPY untuk membawa beberapa data dan ada celah di kunci primer dan INSERT melempar pengecualian kunci duplikat. Mengatur urutan melakukan trik. 9,4
user542319
22

Perintah ini hanya untuk mengubah nilai urutan kunci yang dibuat secara otomatis di postgresql

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

Di tempat nol Anda dapat menempatkan nomor dari mana Anda ingin memulai kembali urutan.

nama urutan default akan "TableName_FieldName_seq". Misalnya, jika nama tabel Anda "MyTable"dan nama bidang Anda "MyID", maka nama urutan Anda akan "MyTable_MyID_seq".

Jawaban ini sama dengan jawaban @ murugesanponappan, tetapi ada kesalahan sintaksis dalam solusinya. Anda tidak dapat menggunakan sub kueri (select max()...)dalam alterperintah. Sehingga Anda harus menggunakan nilai angka tetap atau Anda perlu menggunakan variabel sebagai pengganti sub kueri.

Haider Ali Wajihi
sumber
Ini solusi sempurna terima kasih banyak pak. Tetapi dalam kasus saya, saya memiliki kesalahan, jadi saya harus mengubahnya ke ALTER SEQUENCE "your_ followingence_name" RESTART WITH 1;
Deunz
18

Setel ulang semua urutan, tidak ada asumsi tentang nama kecuali bahwa kunci utama setiap tabel adalah "id":

CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text, columnname text)
RETURNS "pg_catalog"."void" AS
$body$
DECLARE
BEGIN
    EXECUTE 'SELECT setval( pg_get_serial_sequence(''' || tablename || ''', ''' || columnname || '''),
    (SELECT COALESCE(MAX(id)+1,1) FROM ' || tablename || '), false)';
END;
$body$  LANGUAGE 'plpgsql';

select table_name || '_' || column_name || '_seq', reset_sequence(table_name, column_name) from information_schema.columns where column_default like 'nextval%';
EB.
sumber
Bekerja dengan sempurna pada versi 9.1 saya
Valentin Vasilyev
Anda perlu menambahkan kutipan jika tabel berisi huruf besar:pg_get_serial_sequence(''"' || tablename || '"''
Manuel Darveau
Ini adalah fungsi terbaik! Anda dapat menghindari masalah kutipan (dan meningkatkan keanggunan) dengan format, sesuatu seperti EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss
13

Fungsi-fungsi ini penuh dengan bahaya ketika nama urutan, nama kolom, nama tabel atau nama skema memiliki karakter lucu seperti spasi, tanda baca, dan sejenisnya. Saya telah menulis ini:

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

Anda dapat memanggilnya untuk urutan tunggal dengan melewatkannya OID dan itu akan mengembalikan angka tertinggi yang digunakan oleh tabel apa pun yang memiliki urutan sebagai default; atau Anda dapat menjalankannya dengan kueri seperti ini, untuk mengatur ulang semua urutan dalam basis data Anda:

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

Dengan menggunakan qual yang berbeda, Anda hanya dapat mereset urutan dalam skema tertentu, dan seterusnya. Misalnya, jika Anda ingin menyesuaikan urutan dalam skema "publik":

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

Perhatikan bahwa karena cara kerja setval (), Anda tidak perlu menambahkan 1 ke hasilnya.

Sebagai catatan penutup, saya harus memperingatkan bahwa beberapa database tampaknya memiliki default yang menghubungkan ke urutan dengan cara yang tidak membiarkan katalog sistem memiliki informasi lengkap tentang mereka. Ini terjadi ketika Anda melihat hal-hal seperti ini di psql's:

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

Perhatikan bahwa panggilan nextval () dalam klausa default memiliki cast :: text di samping :: regclass cast. Saya pikir ini disebabkan oleh database yang pg_dump'ed dari versi PostgreSQL lama. Apa yang akan terjadi adalah bahwa fungsi sequence_max_value () di atas akan mengabaikan tabel seperti itu. Untuk memperbaiki masalah, Anda dapat mendefinisikan kembali klausa DEFAULT untuk merujuk ke urutan langsung tanpa pemain:

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

Kemudian psql menampilkannya dengan benar:

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

Segera setelah Anda memperbaikinya, fungsi berfungsi dengan benar untuk tabel ini serta semua yang lain yang mungkin menggunakan urutan yang sama.

alvherre
sumber
Ini luar biasa thanx! Perlu dicatat bahwa saya perlu menambahkan gips di tugas (baris 21 dalam kode fungsi) seperti ini: newmax := r.max::bigint;untuk membuatnya bekerja dengan benar untuk saya.
Tommy Bravo
Harus mengubah ini juga: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' perhatikan ::bigintpemain yang ditambahkan dalam kueri build secara dinamis.
Tommy Bravo
9

Namun lain plpgsql - me-reset hanya jika 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;
$$
;

juga mengomentari baris --execute format('alter sequenceakan memberikan daftar, tidak benar-benar mengatur ulang nilainya

Vao Tsun
sumber
8

Setel ulang semua urutan dari publik

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';
pengguna457226
sumber
Tampaknya pendekatan ini membuat asumsi tentang nama kolom dan tabel sehingga tidak berfungsi untuk saya
djsnowsill
Bukankah itu merusak data dalam database?
zennin
8

Saya menyarankan solusi ini ditemukan di postgres wiki. Ini memperbarui semua urutan tabel Anda.

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;

Cara menggunakan (dari postgres wiki):

  • Simpan ini ke file, katakan 'reset.sql'
  • Jalankan file dan simpan outputnya dengan cara yang tidak termasuk header biasa, kemudian jalankan output itu. Contoh:

Contoh:

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

Artikel asli (juga dengan perbaikan untuk kepemilikan urutan) di sini

Pietro
sumber
7

Beberapa jawaban yang sangat hardcore di sini, saya berasumsi dulu benar-benar buruk di sekitar waktu ketika ini telah ditanyakan, karena banyak jawaban dari sini tidak bekerja untuk versi 9.3. The dokumentasi sejak versi 8.0 menyediakan jawaban untuk pertanyaan ini sangat:

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

Juga, jika Anda perlu mengurus nama urutan case-sensitive, itulah cara Anda melakukannya:

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

Masalah ini terjadi dengan saya ketika menggunakan kerangka entitas untuk membuat database dan kemudian menyemai database dengan data awal, ini membuat urutan ketidakcocokan.

Saya mengatasinya dengan Membuat skrip untuk dijalankan setelah seeding basis data:

DO
$do$
DECLARE tablename text;
BEGIN
    -- change the where statments to include or exclude whatever tables you need
    FOR tablename IN SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE' AND table_name != '__EFMigrationsHistory'
        LOOP
            EXECUTE format('SELECT setval(pg_get_serial_sequence(''"%s"'', ''Id''), (SELECT MAX("Id") + 1 from "%s"))', tablename, tablename);
    END LOOP;
END
$do$
Yehia Amer
sumber
1
mengapa MAX("Id") + 1ini bekerja paling baik untuk saya ketika urutan = ke max.
lastlink
6

Versi saya menggunakan yang pertama, dengan beberapa pemeriksaan kesalahan ...

BEGIN;
CREATE OR REPLACE FUNCTION reset_sequence(_table_schema text, _tablename text, _columnname text, _sequence_name text)
RETURNS pg_catalog.void AS
$BODY$
DECLARE
BEGIN
 PERFORM 1
 FROM information_schema.sequences
 WHERE
  sequence_schema = _table_schema AND
  sequence_name = _sequence_name;
 IF FOUND THEN
  EXECUTE 'SELECT setval( ''' || _table_schema || '.' || _sequence_name  || ''', ' || '(SELECT MAX(' || _columnname || ') FROM ' || _table_schema || '.' || _tablename || ')' || '+1)';
 ELSE
  RAISE WARNING 'SEQUENCE NOT UPDATED ON %.%', _tablename, _columnname;
 END IF;
END; 
$BODY$
 LANGUAGE 'plpgsql';

SELECT reset_sequence(table_schema, table_name, column_name, table_name || '_' || column_name || '_seq')
FROM information_schema.columns
WHERE column_default LIKE 'nextval%';

DROP FUNCTION reset_sequence(_table_schema text, _tablename text, _columnname text, _sequence_name text) ;
COMMIT;
Daniel Cristian Cruz
sumber
Terima kasih atas pengecekan kesalahan! Sangat dihargai karena nama tabel / kolom terpotong jika terlalu panjang, yang Anda RAISE WARNINGidentifikasi untuk saya.
Nicholas Riley
5

Menyatukan semuanya

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

akan memperbaiki ' id'urutan tabel yang diberikan (seperti yang biasanya diperlukan dengan Django misalnya).

Antony Hatchkins
sumber
4

sebelum saya belum mencoba kode tersebut: berikut ini saya memposting versi untuk kode sql untuk solusi Klaus dan user457226 yang bekerja pada pc saya [Postgres 8.3], dengan hanya beberapa penyesuaian kecil untuk Klaus satu dan versi saya untuk yang pengguna457226.

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

solusi user457226:

--drop function IF EXISTS reset_sequence (text,text) RESTRICT;
CREATE OR REPLACE FUNCTION "reset_sequence" (tablename text,columnname text) RETURNS bigint --"pg_catalog"."void"
AS
$body$
  DECLARE seqname character varying;
          c integer;
  BEGIN
    select tablename || '_' || columnname || '_seq' into seqname;
    EXECUTE 'SELECT max("' || columnname || '") FROM "' || tablename || '"' into c;
    if c is null then c = 0; end if;
    c = c+1; --because of substitution of setval with "alter sequence"
    --EXECUTE 'SELECT setval( "' || seqname || '", ' || cast(c as character varying) || ', false)'; DOES NOT WORK!!!
    EXECUTE 'alter sequence ' || seqname ||' restart with ' || cast(c as character varying);
    RETURN nextval(seqname)-1;
  END;
$body$ LANGUAGE 'plpgsql';

select sequence_name, PG_CLASS.relname, PG_ATTRIBUTE.attname,
       reset_sequence(PG_CLASS.relname,PG_ATTRIBUTE.attname)
from PG_CLASS
join PG_ATTRIBUTE on PG_ATTRIBUTE.attrelid = PG_CLASS.oid
join information_schema.sequences
     on information_schema.sequences.sequence_name = PG_CLASS.relname || '_' || PG_ATTRIBUTE.attname || '_seq'
where sequence_schema='public';
mauro
sumber
4

Periksa kembali semua urutan dalam fungsi skema publik

CREATE OR REPLACE FUNCTION public.recheck_sequence (
)
RETURNS void AS
$body$
DECLARE
  _table_name VARCHAR;
  _column_name VARCHAR;  
  _sequence_name VARCHAR;
BEGIN
  FOR _table_name IN SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public' LOOP
    FOR _column_name IN SELECT column_name FROM information_schema.columns WHERE table_name = _table_name LOOP
        SELECT pg_get_serial_sequence(_table_name, _column_name) INTO _sequence_name;
        IF _sequence_name IS NOT NULL THEN 
            EXECUTE 'SELECT setval('''||_sequence_name||''', COALESCE((SELECT MAX('||quote_ident(_column_name)||')+1 FROM '||quote_ident(_table_name)||'), 1), FALSE);';
        END IF;
    END LOOP;   
  END LOOP;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;
anydasa
sumber
3

Untuk memulai kembali semua urutan ke 1 gunakan:

-- Create Function
CREATE OR REPLACE FUNCTION "sy_restart_seq_to_1" (
    relname TEXT
)
RETURNS "pg_catalog"."void" AS
$BODY$

DECLARE

BEGIN
    EXECUTE 'ALTER SEQUENCE '||relname||' RESTART WITH 1;';
END;
$BODY$

LANGUAGE 'plpgsql';

-- Use Function
SELECT 
    relname
    ,sy_restart_seq_to_1(relname)
FROM pg_class
WHERE relkind = 'S';
Stanislav Yanev
sumber
2

Jawaban Klaus adalah yang paling berguna, dan jangan lewatkan: Anda harus menambahkan DISTINCT dalam pernyataan pilih.

Namun, jika Anda yakin bahwa tidak ada nama tabel + kolom yang bisa sama dengan dua tabel yang berbeda, Anda juga dapat menggunakan:

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

yang merupakan perpanjangan dari solusi user457226 untuk kasus ketika beberapa nama kolom yang tertarik bukan 'ID'.

mauro
sumber
... tentu saja, juga diperlukan perubahan "reset_afterence", yaitu menambahkan parameter "nama kolom", untuk digunakan alih-alih "id".
mauro
2

Jika Anda melihat kesalahan ini ketika Anda memuat data SQL khusus untuk inisialisasi, cara lain untuk menghindari ini adalah:

Alih-alih menulis:

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

Hapus id(kunci utama) dari data awal

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

Ini menjaga urutan Postgres tetap sinkron!

pengguna
sumber
2

Jawaban ini adalah salinan dari mauro.

drop function IF EXISTS rebuilt_sequences() RESTRICT;
CREATE OR REPLACE FUNCTION  rebuilt_sequences() RETURNS integer as
$body$
  DECLARE sequencedefs RECORD; c integer ;
  BEGIN
    FOR sequencedefs IN Select
      DISTINCT(constraint_column_usage.table_name) as tablename,
      constraint_column_usage.column_name as columnname,
      replace(replace(columns.column_default,'''::regclass)',''),'nextval(''','') as sequencename
      from information_schema.constraint_column_usage, information_schema.columns
      where constraint_column_usage.table_schema ='public' AND 
      columns.table_schema = 'public' AND columns.table_name=constraint_column_usage.table_name
      AND constraint_column_usage.column_name = columns.column_name
      AND columns.column_default is not null 
      ORDER BY sequencename
   LOOP    
      EXECUTE 'select max('||sequencedefs.columnname||') from ' || sequencedefs.tablename INTO c;
      IF c is null THEN c = 0; END IF;
      IF c is not null THEN c = c+ 1; END IF;
      EXECUTE 'alter sequence ' || sequencedefs.sequencename ||' minvalue '||c ||' start ' || c ||' restart  with ' || c;
   END LOOP;

   RETURN 1; END;
$body$ LANGUAGE plpgsql;

select rebuilt_sequences();
Baldiry
sumber
2

Saya menghabiskan satu jam mencoba untuk mendapatkan jawaban djsnowsill untuk bekerja dengan database menggunakan tabel dan kolom Mixed Case, kemudian akhirnya menemukan solusi berkat komentar dari Manuel Darveau, tapi saya pikir saya bisa membuatnya sedikit lebih jelas untuk semua orang:

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

Ini memiliki manfaat:

  • tidak menganggap kolom ID dieja dengan cara tertentu.
  • tidak menganggap semua tabel memiliki urutan.
  • bekerja untuk nama tabel campuran / kolom.
  • menggunakan format agar lebih ringkas.

Untuk menjelaskan, masalahnya adalah pg_get_serial_sequencemembutuhkan string untuk mengetahui apa yang Anda maksud, jadi jika Anda melakukannya:

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

Ini dicapai menggunakan ''%1$I''string format, ''membuat tanda kutip 1$berarti arg pertama, dan Iberarti dalam tanda kutip

Nintynuts
sumber
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
Михаил Шатилов
sumber
4
Sementara kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang mengapa dan / atau bagaimana kode ini menjawab pertanyaan meningkatkan nilai jangka panjangnya.
yeya
1

Peretasan yang jelek untuk memperbaikinya menggunakan beberapa shell magic, bukan solusi yang hebat tetapi mungkin menginspirasi orang lain dengan masalah yang sama :)

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

Coba pengindeksan ulang .

UPDATE: Seperti yang ditunjukkan dalam komentar, ini adalah jawaban untuk pertanyaan awal.

Hank Gay
sumber
reindex tidak berfungsi, sepertinya hanya menambah indeks dengan 1
meleyal
3
reindex tidak berfungsi karena menjawab pertanyaan awal Anda, tentang indeks basis data, bukan urutan
Vinko Vrsalovic
0

SELECT setval... membuat JDBC bork, jadi inilah cara yang kompatibel dengan Java untuk melakukan ini:

-- work around JDBC 'A result was returned when none was expected.'
-- fix broken nextval due to poorly written 20140320100000_CreateAdminUserRoleTables.sql
DO 'BEGIN PERFORM setval(pg_get_serial_sequence(''admin_user_role_groups'', ''id''), 1 + COALESCE(MAX(id), 0), FALSE) FROM admin_user_role_groups; END;';
mcandre
sumber
0

Metode untuk memperbarui semua urutan dalam skema Anda yang digunakan sebagai ID:

DO $$ DECLARE
  r RECORD;
BEGIN
FOR r IN (SELECT tablename, pg_get_serial_sequence(tablename, 'id') as sequencename
          FROM pg_catalog.pg_tables
          WHERE schemaname='YOUR_SCHEMA'
          AND tablename IN (SELECT table_name 
                            FROM information_schema.columns 
                            WHERE table_name=tablename and column_name='id')
          order by tablename)
LOOP
EXECUTE
        'SELECT setval(''' || r.sequencename || ''', COALESCE(MAX(id), 1), MAX(id) IS NOT null)
         FROM ' || r.tablename || ';';
END LOOP;
END $$;
Nick Van Berckelaer
sumber
0

Jalankan saja perintah di bawah ini:

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

Ada banyak jawaban bagus di sini. Saya memiliki kebutuhan yang sama setelah memuat ulang basis data Django saya.

Tetapi saya membutuhkan:

  • Semua dalam satu Fungsi
  • Dapat memperbaiki satu atau lebih skema sekaligus
  • Bisa memperbaiki semua atau hanya satu tabel dalam satu waktu
  • Juga ingin cara yang baik untuk melihat apa yang telah berubah, atau tidak berubah

Tampaknya kebutuhan ini sangat mirip dengan apa yang diminta aslinya.
Terima kasih kepada Baldiry dan Mauro membuat saya berada di jalur yang benar.

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;

Kemudian untuk Jalankan dan Lihat perubahan dijalankan:

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

Kembali

activity_id_seq                          restart at 22
api_connection_info_id_seq               restart at 4
api_user_id_seq                          restart at 1
application_contact_id_seq               restart at 20
brianwaganer
sumber