วิธีรีเซ็ตลำดับคีย์หลักของ postgres เมื่อมันขาดการซิงค์?


523

ฉันพบปัญหาว่าลำดับคีย์หลักของฉันไม่ซิงค์กับแถวของตาราง

นั่นคือเมื่อฉันแทรกแถวใหม่ฉันได้รับข้อผิดพลาดที่สำคัญซ้ำเพราะลำดับที่บอกเป็นนัยในอนุกรมประเภทข้อมูลส่งคืนหมายเลขที่มีอยู่แล้ว

ดูเหมือนว่าจะเกิดจากการนำเข้า / คืนไม่รักษาลำดับอย่างถูกต้อง


ฉันอยากรู้อยากเห็น .. คุณวางฐานข้อมูลไว้ก่อนที่จะทำการกู้คืนหรือไม่? ฉันมีความทรงจำเล็กน้อยเกี่ยวกับสิ่งที่เกิดขึ้นนี้ แต่ฉันอาจจะผิด: P
Arthur Thomas

25
PostgreSQL วิกิพีเดียมีหน้าอยู่ในลำดับการแก้ไข
แบรดโคช์ส

14
เพียงเพื่อช่วย googleability ข้อความแสดงข้อผิดพลาดที่เกิดขึ้นที่นี่คือ: "ค่าคีย์ที่ซ้ำกันละเมิดข้อ จำกัด ที่ไม่ซ้ำกัน ... "
superluminary

4
นี่คือลักษณะที่ sqlsequencereset ใน Django ทำ: SELECT setval (pg_get_serial_sequence ("<table_name>", 'id'), รวมตัวกัน (max ("id"), 1), max ("id") ไม่เป็นโมฆะ) จาก "< table_name> ";
ผู้ใช้

อินสแตนซ์แรกของ <table name> จำเป็นต้องถูกห่อด้วยเครื่องหมายคำพูดเดี่ยวสำหรับฟังก์ชัน pg_get_serioal_sequence เพื่อให้ทำงานได้: SELECT setval (pg_get_serial_sequence ('<table_name>', 'id'), รวมตัวกัน (max ("id"), 1) , สูงสุด ("id") ไม่เป็นโมฆะ) จาก "<table_name>"
nclu

คำตอบ:


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;

แหล่งที่มา - ฟอรั่มทับทิม


12
การเพิ่ม 1 ถึง MAX (id) จะทำให้เกิดช่องว่างตัวเลขเดียวใน ID ของคุณเนื่องจากชุด setval คือค่าสุดท้ายของลำดับไม่ใช่ลำดับถัดไป
mikl

6
ตัวอย่างของคุณจะไม่ทำงานหากไม่มีแถวในตาราง ดังนั้น SQL ที่ได้รับจึงมีความปลอดภัยมากขึ้น: SELECT setval ('your_table_id_seq', รวมตัวกัน ((เลือก max (id) +1 จาก your_table), 1), จริง);
Valery Viktorovsky

10
@Valery: แต่เพื่อหลีกเลี่ยงช่องว่างที่ถูกกล่าวถึงโดย @mikl สองความคิดเห็นข้างต้นคุณต้องSELECT setval('your_table_id_seq', coalesce((select max(id)+1 from your_table), 1), false);
Antony Hatchkins

20
ปัญหาทั้งหมดได้รับการแก้ไขและรวมเข้าเป็นข้อความค้นหาเดียว:SELECT setval('your_seq',(SELECT GREATEST(MAX(your_id)+1,nextval('your_seq'))-1 FROM your_table))
Frunsi

15
หากแอปพลิเคชันของคุณใส่ใจเกี่ยวกับช่องว่างในลำดับแอปพลิเคชันของคุณจะไม่ทำงาน ช่องว่างในลำดับเป็นปกติและสามารถเกิดขึ้นเนื่องจากการปิดซ่อมฐานข้อมูลที่ไม่ได้วางแผน rollbacks ธุรกรรมหลังจากข้อผิดพลาดอื่น ๆ
เครก Ringer

202

pg_get_serial_sequenceสามารถใช้เพื่อหลีกเลี่ยงการสันนิษฐานที่ไม่ถูกต้องเกี่ยวกับชื่อลำดับ นี่เป็นการรีเซ็ตลำดับในหนึ่งช็อต:

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

หรือรัดกุมมากขึ้น:

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

อย่างไรก็ตามแบบฟอร์มนี้ไม่สามารถจัดการกับตารางที่ว่างเปล่าได้อย่างถูกต้องเนื่องจาก max (id) เป็นโมฆะและคุณไม่สามารถตั้งค่า 0 เพราะมันจะอยู่นอกช่วงของลำดับ วิธีหนึ่งในการแก้ไขปัญหานี้คือหันไปใช้ALTER SEQUENCEไวยากรณ์เช่น

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

แต่ALTER SEQUENCEมีการใช้งานอย่าง จำกัด เนื่องจากชื่อลำดับและค่าเริ่มต้นไม่สามารถเป็นนิพจน์ได้

ดูเหมือนว่าทางออกที่ดีที่สุดคือการโทรsetvalด้วย false เป็นพารามิเตอร์ที่ 3 ทำให้เราสามารถระบุ "ค่าต่อไปที่จะใช้":

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

นี่เป็นการทำเครื่องหมายที่ช่องทั้งหมดของฉัน:

  1. หลีกเลี่ยงการเข้ารหัสชื่อลำดับจริงอย่างหนัก
  2. จัดการตารางว่างอย่างถูกต้อง
  3. จัดการตารางที่มีข้อมูลที่มีอยู่และไม่ปล่อยให้รูอยู่ในลำดับ

สุดท้ายโปรดทราบว่าpg_get_serial_sequenceจะใช้งานได้ก็ต่อเมื่อลำดับนั้นเป็นของคอลัมน์ กรณีนี้จะเกิดขึ้นหากมีการเพิ่มคอลัมน์การเพิ่มเป็นserialประเภทอย่างไรก็ตามหากมีการเพิ่มลำดับด้วยตนเองจำเป็นต้องALTER SEQUENCE .. OWNED BYดำเนินการด้วยเช่นกัน

นั่นคือถ้าserialประเภทถูกนำมาใช้สำหรับการสร้างตารางทั้งหมดนี้จะทำงาน:

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;

แต่ถ้าเพิ่มลำดับด้วยตนเอง:

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' ในแบบสอบถามsetval()ตั้งค่าปัจจุบันและnextval()จะส่งคืนมูลค่าปัจจุบัน +1 แล้ว
Antony Hatchkins

1
ฟังก์ชั่นการตัดเมธอดนี้ที่รับหนึ่งพารามิเตอร์ - table_name - อยู่ในคำตอบของฉันด้านล่าง: stackoverflow.com/a/13308052/237105
Antony Hatchkins

@AntonyHatchkins ส่งเสียงเชียร์ เพิ่งเห็นการทำซ้ำของข้อผิดพลาด 1 อีกดังนั้นในที่สุด swatted ว่าสำหรับฉันหวังดี
tardate

99

วิธีที่สั้นและเร็วที่สุด :

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

tbl_idเป็นserialคอลัมน์ของตารางtblวาดจากลำดับtbl_tbl_id_seq(ซึ่งเป็นชื่ออัตโนมัติเริ่มต้น)

หากคุณไม่ทราบชื่อของลำดับที่แนบมา (ซึ่งไม่จำเป็นต้องอยู่ในรูปแบบเริ่มต้น) ให้ใช้pg_get_serial_sequence():

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

ไม่มีข้อผิดพลาดแบบออฟไลน์ที่นี่ ตามเอกสาร:

ฟอร์มสองพารามิเตอร์ตั้งค่าlast_valueเขตข้อมูลของลำดับเป็นค่าที่ระบุและตั้งค่าis_calledฟิลด์เป็นจริงซึ่งหมายความว่าลำดับ ต่อไปnextvalจะเลื่อนลำดับก่อนที่จะส่งคืนค่า

เหมืองเน้นหนัก

หากตารางว่างเปล่าและเริ่มต้นจาก 1 ในกรณีนี้จริง:

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

เราไม่สามารถใช้รูปแบบ 2-paremater และเริ่มต้นด้วย0เนื่องจากขอบเขตล่างของลำดับคือ1โดยค่าเริ่มต้น (ยกเว้นกำหนดเอง)

เห็นพ้องด้วย

ยังไม่มีการป้องกันกิจกรรมลำดับพร้อมกันหรือเขียนลงในตารางในแบบสอบถามด้านบน หากมีความเกี่ยวข้องคุณอาจล็อคตารางในโหมดเอกสิทธิ์ มันทำให้การทำธุรกรรมพร้อมกันจากการเขียนตัวเลขที่สูงขึ้นในขณะที่คุณพยายามที่จะซิงค์ (นอกจากนี้ยังบล็อกการเขียนที่ไม่เป็นอันตรายและไม่ยุ่งกับจำนวนสูงสุดชั่วคราว)

แต่มันไม่ได้คำนึงถึงลูกค้าที่อาจมีการเรียกหมายเลขลำดับล่วงหน้าโดยไม่มีการล็อคใด ๆ บนตารางหลัก แต่ (ซึ่งอาจเกิดขึ้นได้) หากต้องการอนุญาตให้เพิ่มค่าปัจจุบันของลำดับเท่านั้นอย่าลดลง มันอาจดูหวาดระแวง แต่ก็สอดคล้องกับลักษณะของลำดับและป้องกันปัญหาที่เกิดขึ้นพร้อมกัน

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;

ที่ไหน "ห้องสมุดชุมชนมาตรฐานของฟังก์ชั่นสำคัญ"? ประโยคที่เลือกข้อที่สองของคำตอบนี้ในEXECUTE format()(เช่น @ EB. ) คือฟังก์ชั่นสำคัญ! วิธีแก้ไขการขาดไลบรารี่มาตรฐานใน PostgreSQL ????
Peter Krauss

ไม่สำคัญว่าจะมีคนออกไป ช่องว่างในลำดับนั้นเป็นเรื่องปกติ หากแอปของคุณไม่สามารถรับมือได้แอปของคุณจะเสียเนื่องจากช่องว่างอาจเกิดขึ้นได้เนื่องจากการทำธุรกรรมย้อนกลับการปิดเซิร์ฟเวอร์ที่ไม่ได้วางแผน ฯลฯ
Craig Ringer

1
@Craig: ข้อผิดพลาดแบบออฟไลน์ที่ฉันพูดถึง (และไม่มีอยู่) จะมีความสำคัญเนื่องจากเราต้องการเสี่ยงข้อผิดพลาดที่สำคัญซ้ำซ้อน ทิศทางที่ตรงข้ามกับข้อควรพิจารณาของคุณ ดูเหมือนจะเข้าใจผิด
Erwin Brandstetter

อาทำให้รู้สึก
Craig Ringer

สิ่งนี้ใช้ได้กับฉัน
เฮก

54

สิ่งนี้จะรีเซ็ตลำดับทั้งหมดจากสาธารณะโดยไม่มีสมมติฐานเกี่ยวกับชื่อตารางหรือคอลัมน์ ทดสอบกับรุ่น 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 ฟังก์ชั่นที่มีประโยชน์มาก! ชื่อลำดับของเราไม่ตรงกับชื่อตารางตรงดังนั้นผมจึงใช้แทนsubstring(column_default, '''(.*)''') table_name || '_' || column_name || '_seq'ทำงานได้อย่างสมบูรณ์แบบ
Chris Lercher

4
โปรดทราบว่าสิ่งนี้จะล้มเหลวด้วยชื่อลำดับที่มีเครื่องหมายคำพูดเดี่ยวหรือชื่อตารางที่มีตัวพิมพ์ใหญ่เว้นวรรคและอื่น ๆ ในชื่อของพวกเขา quote_literalและquote_identฟังก์ชั่นหรือเฉพาะอย่างยิ่งformatฟังก์ชั่นควรจะใช้ที่นี่
Craig Ringer

2
หวังว่าฉันจะให้มากกว่านี้หนึ่งคะแนน ... คุณทำได้ดีมาก ใช้งานได้ดีกับ Postgres 9.1 เช่นกันสำหรับฉันอย่างน้อย
ปอกเปลือก

1
มันเยี่ยมมาก ฉันเคยsubstring(column_default from 'nextval\(''(.+)''::regclass\)')คว้าชื่อลำดับอย่างชัดเจน ทำงานเหมือนจับใจ
Matthew MacDonald

ฉันค้นหาโซลูชันนี้มานานกว่าหนึ่งวันแล้วขอบคุณมากแม้ฉันจะใช้วิธีการที่ @ChrisLercher แนะนำเพื่อแทนที่ข้อความsubstring(column_default, '''(.*)''') instead of table_name || '_' || column_name || '_seq'
Sushin Pv

43

เปลี่ยนลำดับ sequence_name รีสตาร์ทด้วย (เลือก max (id) จาก table_name); ใช้งานไม่ได้

คัดลอกมาจาก @tardate คำตอบ:

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

8
นั่นเป็นข้อผิดพลาดทางไวยากรณ์สำหรับฉันใน 8.4 (ที่ ^ (SELECT ... ) RESTART WITH ดูเหมือนจะยอมรับเฉพาะค่าเลขลำดับเท่านั้นซึ่งใช้งานได้: SELECT setval (pg_get_serial_sequence ('table_name', 'id'), (SELECT MAX () id) จาก table_name) +1);
tardate

1
โซลูชันของ Muruges ไม่ทำงานใน 9.4 เช่นกัน ไม่เข้าใจว่าทำไม upvotes มากสำหรับคำตอบนี้ ALTER SEQUENCE ไม่อนุญาตให้ใช้แบบสอบถามย่อย Solution by @tardate ทำงานได้อย่างสมบูรณ์แบบ แก้ไขคำตอบเพื่อลบข้อมูลที่ไม่ถูกต้อง
Vladislav Rastrusny

ALTER SEQUENCE ทำงานได้สมบูรณ์แบบสำหรับฉัน ฉันใช้ COPY เพื่อนำข้อมูลมาบางส่วนและมีช่องว่างในคีย์หลักและ INSERT กำลังขว้างข้อยกเว้นคีย์ซ้ำ การตั้งค่าลำดับได้หลอกลวง 9.4
user542319

22

คำสั่งนี้ใช้สำหรับเปลี่ยนลำดับของคีย์ที่สร้างโดยอัตโนมัติใน postgresql เท่านั้น

ALTER SEQUENCE "your_sequence_name" RESTART WITH 0;

แทนที่ศูนย์คุณสามารถใส่หมายเลขใด ๆ ที่คุณต้องการรีสตาร์ทลำดับ

"TableName_FieldName_seq"ชื่อลำดับเริ่มต้นจะ ตัวอย่างเช่นหากชื่อตารางของคุณคือ"MyTable"และชื่อเขตข้อมูล"MyID"ของคุณแล้วชื่อลำดับของคุณจะเป็น"MyTable_MyID_seq"แล้วชื่อลำดับของคุณจะ

นี่เป็นคำตอบเดียวกับคำตอบของ @ murugesanponappan แต่มีข้อผิดพลาดทางไวยากรณ์ในโซลูชันของเขา คุณไม่สามารถใช้แบบสอบถามย่อย(select max()...)ในalterคำสั่ง เพื่อว่าคุณจะต้องใช้ค่าตัวเลขคงที่หรือคุณต้องใช้ตัวแปรแทนที่แบบสอบถามย่อย


นี่คือทางออกที่สมบูรณ์แบบขอบคุณมากครับ แต่ในกรณีของฉันฉันมีข้อผิดพลาดดังนั้นฉันจึงต้องเปลี่ยนเป็น ALTER SEQUENCE "your_sequence_name" RESTART WITH 1;
Deunz

18

รีเซ็ตลำดับทั้งหมดโดยไม่มีข้อสมมติฐานเกี่ยวกับชื่อยกเว้นว่าคีย์หลักของแต่ละตารางคือ "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%';

ทำงานอย่างสมบูรณ์แบบกับรุ่น 9.1 ของฉัน
Valentin Vasilyev

คุณต้องเพิ่มpg_get_serial_sequence(''"' || tablename || '"''
เครื่องหมาย

นี่คือฟังก์ชั่นที่ดีที่สุด! คุณสามารถหลีกเลี่ยงปัญหาเกี่ยวกับคำพูด (และเพิ่มความสง่างาม) ด้วยรูปแบบเช่น EXECUTE format( 'SELECT setval(pg_get_serial_sequence(%L, %L), coalesce(max(id),0) + 1, false) FROM %I;', $1,$2,$1 );
Peter Krauss

13

ฟังก์ชั่นเหล่านี้เต็มไปด้วยอันตรายเมื่อชื่อลำดับชื่อคอลัมน์ชื่อตารางหรือชื่อสคีมีตัวละครตลกเช่นช่องว่างเครื่องหมายวรรคตอนและชอบ ฉันได้เขียนสิ่งนี้:

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

คุณสามารถเรียกมันเป็นลำดับเดียวโดยผ่าน OID แล้วมันจะคืนค่าจำนวนสูงสุดที่ใช้โดยตารางใด ๆ ที่มีลำดับเป็นค่าเริ่มต้น หรือคุณสามารถรันด้วยเคียวรี่เช่นนี้เพื่อรีเซ็ตลำดับทั้งหมดในฐานข้อมูลของคุณ:

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

การใช้คุณสมบัติอื่นคุณสามารถรีเซ็ตเฉพาะลำดับในสคีมาบางอย่างและอื่น ๆ ตัวอย่างเช่นหากคุณต้องการปรับลำดับในสคีมา "สาธารณะ":

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

โปรดทราบว่าเนื่องจาก setval () ทำงานอย่างไรคุณไม่จำเป็นต้องเพิ่ม 1 ในผลลัพธ์

ในฐานะที่เป็นบันทึกปิดฉันต้องเตือนว่าฐานข้อมูลบางอย่างดูเหมือนจะมีค่าเริ่มต้นที่เชื่อมโยงไปยังลำดับในวิธีที่ไม่อนุญาตให้แคตตาล็อกระบบมีข้อมูลทั้งหมดของพวกเขา สิ่งนี้จะเกิดขึ้นเมื่อคุณเห็นสิ่งต่าง ๆ ใน psql's \ d:

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

โปรดทราบว่าการเรียก nextval () ในส่วนคำสั่งเริ่มต้นนั้นมีการส่งข้อความ :: นอกเหนือจากการโยน :: regclass ฉันคิดว่านี่เป็นเพราะฐานข้อมูลถูก pg_dump'ed จากเวอร์ชันเก่า PostgreSQL สิ่งที่จะเกิดขึ้นก็คือฟังก์ชั่น sequence_max_value () ด้านบนจะไม่สนใจตารางดังกล่าว เพื่อแก้ไขปัญหาคุณสามารถนิยาม DEFAULT clause เพื่ออ้างถึงลำดับโดยตรงโดยไม่ต้องใช้ cast:

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

จากนั้น psql จะแสดงอย่างถูกต้อง:

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

ทันทีที่คุณแก้ไขฟังก์ชันจะทำงานอย่างถูกต้องสำหรับตารางนี้และอื่น ๆ ทั้งหมดที่อาจใช้ลำดับเดียวกัน


มันน่าอัศจรรย์มาก! ควรสังเกตว่าฉันต้องการเพิ่ม cast ที่มอบหมาย (บรรทัดที่ 21 ในโค้ดฟังก์ชัน) ดังนี้: newmax := r.max::bigint;เพื่อให้ทำงานได้อย่างถูกต้องสำหรับฉัน
Tommy Bravo

ต้องเปลี่ยนสิ่งนี้เช่นกัน: 'SELECT max(' || quote_ident(colname) || ') FROM ' => 'SELECT max(' || quote_ident(colname) || '::bigint) FROM ' สังเกตเห็นการเพิ่มที่::bigintส่งไปภายในเคียวรีบิลด์แบบไดนามิก
Tommy Bravo

9

อีกหนึ่ง plpgsql - รีเซ็ตเฉพาะในกรณีที่ 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;
$$
;

นอกจากนี้การแสดงความคิดเห็นบรรทัด--execute format('alter sequenceจะให้รายการไม่รีเซ็ตค่าจริง


8

รีเซ็ตลำดับทั้งหมดจากสาธารณะ

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

ดูเหมือนว่าวิธีนี้จะทำให้สมมติฐานเกี่ยวกับชื่อคอลัมน์และตารางดังนั้นมันจึงไม่ได้ผลสำหรับฉัน
djsnowsill

จะไม่เกิดความเสียหายกับข้อมูลในฐานข้อมูลหรือไม่
zennin

8

ฉันแนะนำวิธีแก้ปัญหานี้ที่พบใน postgres wiki มันปรับปรุงลำดับทั้งหมดของตารางของคุณ

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;

วิธีใช้ (จาก postgres wiki):

  • บันทึกลงในไฟล์พูด 'reset.sql'
  • เรียกใช้ไฟล์และบันทึกเอาต์พุตในวิธีที่ไม่มีส่วนหัวตามปกติจากนั้นเรียกใช้เอาต์พุตนั้น ตัวอย่าง:

ตัวอย่าง:

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

บทความต้นฉบับ (รวมถึงการแก้ไขสำหรับการเป็นเจ้าของลำดับ) ที่นี่


7

บางคำตอบที่ไม่ยอมใครง่ายๆจริงๆที่นี่ฉันคิดว่ามันแย่มากในช่วงเวลาที่ถูกถามเพราะคำตอบมากมายจากที่นี่ไม่ได้ผลสำหรับเวอร์ชั่น 9.3 เอกสารตั้งแต่รุ่น 8.0 ให้คำตอบกับคำถามมากนี้:

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

นอกจากนี้หากคุณต้องการดูแลชื่อตามลำดับตัวพิมพ์เล็กและตัวพิมพ์เล็กคุณจะต้องทำดังนี้

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

7

ปัญหานี้เกิดขึ้นกับฉันเมื่อใช้เฟรมเวิร์กเอนทิตีเพื่อสร้างฐานข้อมูลและจากนั้นเริ่มต้นฐานข้อมูลด้วยข้อมูลเริ่มต้นสิ่งนี้ทำให้ลำดับไม่ตรงกัน

ฉันแก้ไขได้โดยการสร้างสคริปต์ให้ทำงานหลังจากการสร้างฐานข้อมูล:

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
ทำไมMAX("Id") + 1มันถึงดีที่สุดสำหรับฉันเมื่อลำดับคือ = ถึงสูงสุด
lastlink

6

เวอร์ชันของฉันใช้อันแรกโดยมีการตรวจสอบข้อผิดพลาด ...

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;

ขอบคุณสำหรับการตรวจสอบข้อผิดพลาด! ได้รับความนิยมอย่างมากเมื่อชื่อตาราง / คอลัมน์ถูกตัดทอนถ้ามันยาวเกินไปซึ่งคุณRAISE WARNINGระบุไว้สำหรับฉัน
Nicholas Riley

5

วางมันทั้งหมดเข้าด้วยกัน

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

จะแก้ไข ' id'ลำดับของตารางที่กำหนด (ตามปกติจำเป็นกับ django เป็นต้น)


4

ก่อนที่ฉันจะยังไม่ได้ลองใช้รหัส: ในบทความต่อไปนี้ฉันโพสต์เวอร์ชั่นของ sql-code สำหรับทั้ง Klaus และ user457226 ซึ่งทำงานบนพีซีของฉัน [Postgres 8.3] โดยมีการปรับแต่งเล็กน้อยสำหรับ Klaus one และเวอร์ชันของฉัน สำหรับผู้ใช้ 457226 หนึ่ง

สารละลายคลอส:

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:

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

ตรวจสอบลำดับทั้งหมดอีกครั้งในฟังก์ชั่นสคีมาสาธารณะ

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

ในการรีสตาร์ทลำดับทั้งหมดเป็น 1 ให้ใช้:

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

คำตอบของ Klaus นั้นมีประโยชน์มากที่สุดดำเนินการเพื่อพลาดไปเล็กน้อย: คุณต้องเพิ่ม DISTINCT ในคำสั่ง select

อย่างไรก็ตามหากคุณแน่ใจว่าไม่มีชื่อตาราง + คอลัมน์ใดเทียบเท่ากับสองตารางที่แตกต่างกันคุณสามารถใช้:

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

ซึ่งเป็นส่วนขยายของโซลูชัน user457226 สำหรับกรณีเมื่อบางชื่อคอลัมน์ที่สนใจไม่ใช่ 'ID'


... แน่นอนต้องมีการเปลี่ยนแปลง "reset_sequence" ซึ่งก็คือการเพิ่มพารามิเตอร์ "columnname" เพื่อใช้แทน "id"
mauro

2

หากคุณเห็นข้อผิดพลาดนี้เมื่อคุณโหลดข้อมูล SQL แบบกำหนดเองสำหรับการเริ่มต้นวิธีอื่นในการหลีกเลี่ยงคือ:

แทนที่จะเขียน:

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

ลบid(คีย์หลัก) ออกจากข้อมูลเริ่มต้น

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

นี่จะทำให้ลำดับ Postgres ซิงค์กัน!


2

คำตอบนี้เป็นสำเนาจาก 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

ฉันใช้เวลาหนึ่งชั่วโมงเพื่อหาคำตอบของ djsnowsill ในการทำงานกับฐานข้อมูลโดยใช้ Mixed Case tables และคอลัมน์จากนั้นในที่สุดก็สะดุดกับคำตอบจากความเห็นของ Manuel Darveau แต่ฉันคิดว่าฉันสามารถทำให้ทุกคนชัดเจนขึ้น:

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

นี่คือประโยชน์ของ:

  • การสมมติว่าคอลัมน์ ID สะกดไม่ถูกต้อง
  • ไม่ได้สมมติว่าทุกตารางมีลำดับ
  • ทำงานกับชื่อตาราง / คอลัมน์แบบผสมกรณี
  • ใช้รูปแบบให้กระชับยิ่งขึ้น

ในการอธิบายปัญหาคือการpg_get_serial_sequenceใช้สายอักขระในการอธิบายสิ่งที่คุณอ้างถึงดังนั้นหากคุณ:

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

นี่คือความสำเร็จโดยใช้''%1$I''ในรูปแบบสตริง''ทำให้เครื่องหมายวรรคตอน1$หมายถึงหาเรื่องแรกและIหมายถึงในคำพูด


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
ในขณะที่รหัสนี้อาจตอบคำถาม แต่มีบริบทเพิ่มเติมเกี่ยวกับสาเหตุและ / หรือวิธีการที่รหัสนี้ตอบคำถามช่วยปรับปรุงมูลค่าระยะยาว
yeya

1

การแฮ็กที่น่าเกลียดเพื่อแก้ไขโดยใช้เวทย์มนตร์เชลล์ไม่ใช่ทางออกที่ดี แต่อาจเป็นแรงบันดาลใจให้ผู้อื่นที่มีปัญหาคล้ายกัน :)

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

ลองทำดัชนีใหม่ใหม่

UPDATE: ตามที่ระบุไว้ในความคิดเห็นนี่คือการตอบคำถามเดิม


reindex ไม่ทำงานดูเหมือนว่าจะเพิ่มดัชนีภายในวันที่ 1
meleyal

3
reindex ไม่ทำงานเพราะมันเป็นการตอบคำถามเดิมของคุณเกี่ยวกับดัชนีฐานข้อมูลไม่ใช่ลำดับ
Vinko Vrsalovic

0

SELECT setval... ทำให้ JDBC เป็นบอร์กดังนั้นนี่เป็นวิธีที่เข้ากันได้กับ Java ในการทำสิ่งนี้:

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

วิธีการอัปเดตลำดับทั้งหมดในสคีมาของคุณที่ใช้เป็น ID:

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


0

มีคำตอบที่ดีมากมายที่นี่ ฉันมีความต้องการเดียวกันหลังจากที่โหลดฐานข้อมูล Django ของฉันใหม่

แต่ฉันต้องการ:

  • ทั้งหมดในฟังก์ชั่นเดียว
  • สามารถแก้ไขสกีมาอย่างน้อยหนึ่งรายการในเวลาเดียวกัน
  • สามารถแก้ไขทั้งหมดหรือเพียงหนึ่งตารางในเวลา
  • ยังต้องการวิธีที่ดีในการดูสิ่งที่เปลี่ยนแปลงหรือไม่เปลี่ยนแปลง

ดูเหมือนว่าความต้องการจะคล้ายกันมากกับสิ่งที่ถามเดิม
ขอบคุณ Baldiry และ Mauro ทำให้ฉันไปถูกทาง

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;

จากนั้นไปที่ดำเนินการและดูการเปลี่ยนแปลงที่ดำเนินการ:

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

ผลตอบแทน

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.