เรียงซ้อนลบเพียงครั้งเดียว


200

ฉันมีฐานข้อมูล Postgresql ที่ฉันต้องการลบแบบเรียงซ้อนสองสามอัน อย่างไรก็ตามตารางไม่ได้ถูกตั้งค่าด้วยกฎ ON DELETE CASCADE มีวิธีใดบ้างที่ฉันสามารถทำการลบและบอก Postgresql ให้เรียงมันได้เพียงครั้งเดียว? สิ่งที่เทียบเท่า

DELETE FROM some_table CASCADE;

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


โปรดดูฟังก์ชั่นที่กำหนดเองของฉันด้านล่าง เป็นไปได้ด้วยข้อ จำกัด บางอย่าง
Joe Love

คำตอบ:


175

ไม่ต้องทำเมื่อคุณเพียงแค่เขียนคำสั่งลบสำหรับตารางที่คุณต้องการเรียงซ้อน

DELETE FROM some_child_table WHERE some_fk_field IN (SELECT some_id FROM some_Table);
DELETE FROM some_table;

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

2
ง่ายปลอดภัย คุณควรเรียกใช้พวกเขาในการทำธุรกรรมเดียวถ้าคุณมีการแทรกความหนาแน่น
İsmail Yavuz

41

หากคุณต้องการจริงๆ DELETE FROM some_table CASCADE;ซึ่งหมายความว่า " ลบแถวทั้งหมดออกจากตารางsome_table " คุณสามารถใช้TRUNCATEแทนDELETEและCASCADEได้รับการสนับสนุนเสมอ อย่างไรก็ตามหากคุณต้องการใช้การลบแบบเลือกพร้อมwhereคำสั่งTRUNCATEไม่ดีพอ

ใช้กับการดูแล - สิ่งนี้จะดร็อปแถวทั้งหมดของตารางทั้งหมดที่มีข้อ จำกัด foreign key some_tableและตารางทั้งหมดที่มีข้อ จำกัด ในตารางเหล่านั้นเป็นต้น

Postgres รองรับCASCADEด้วยคำสั่ง TRUNCATE :

TRUNCATE some_table CASCADE;

นี่คือธุรกรรม (กล่าวคือสามารถย้อนกลับ) แม้ว่ามันจะไม่ได้แยกออกจากการทำธุรกรรมพร้อมกันอื่น ๆ อย่างเต็มที่และมีข้อควรระวังอื่น ๆ อีกมากมาย อ่านเอกสารเพื่อดูรายละเอียด


226
อย่างเห็นได้ชัด "ลบไม่กี่ซ้อน" ≠วางข้อมูลทั้งหมดจากตาราง ...
lensovet

33
การทำเช่นนี้จะทำให้ทุกแถวของตารางทั้งหมดมีข้อ จำกัด foreign key ใน some_table และตารางทั้งหมดที่มีข้อ จำกัด ในตารางเหล่านั้น ฯลฯ ... นี่อาจเป็นอันตรายมาก
AJP

56
ระวัง. นี่คือคำตอบที่ประมาท
Jordan Arseno

4
บางคนตั้งค่าสถานะคำตอบนี้สำหรับการลบน่าจะเป็นเพราะพวกเขาไม่เห็นด้วยกับมัน การกระทำที่ถูกต้องในกรณีนั้นคือการลงคะแนนไม่ใช่ตั้งค่าสถานะ
Wai Ha Lee

7
เขามีคำเตือนอยู่ด้านบน หากคุณเลือกที่จะเพิกเฉยต่อสิ่งนี้จะไม่มีใครช่วยคุณได้ ฉันคิดว่าผู้ใช้ "copyPaste" ของคุณเป็นอันตรายที่แท้จริงที่นี่
BluE

28

ฉันเขียนฟังก์ชัน (เรียกซ้ำ) เพื่อลบแถวใด ๆ ตามคีย์หลัก ฉันเขียนสิ่งนี้เพราะฉันไม่ต้องการสร้างข้อ จำกัด ของฉันในฐานะ "เมื่อลบแบบเรียงซ้อน" ฉันต้องการลบชุดข้อมูลที่ซับซ้อน (ในฐานะ DBA) แต่ไม่อนุญาตให้โปรแกรมเมอร์ของฉันสามารถเรียงซ้อนการลบโดยไม่ต้องคำนึงถึงผลกระทบทั้งหมด ฉันยังคงทดสอบฟังก์ชั่นนี้ดังนั้นอาจมีข้อบกพร่องอยู่ในนั้น - แต่โปรดอย่าลองถ้าฐานข้อมูลของคุณมีคีย์หลักหลายคอลัมน์ นอกจากนี้ปุ่มทั้งหมดจะต้องสามารถแสดงในรูปแบบสตริง แต่สามารถเขียนในลักษณะที่ไม่มีข้อ จำกัด ฉันใช้ฟังก์ชั่นนี้มากกระจัดกระจายอยู่แล้วฉันให้คุณค่าข้อมูลของฉันมากเกินไปเพื่อเปิดใช้งานข้อ จำกัด แบบต่อเรียงบนทุกสิ่ง โดยทั่วไปฟังก์ชั่นนี้จะถูกส่งผ่านในสคีมาชื่อตารางและค่าหลัก (ในรูปแบบสตริง) และมันจะเริ่มต้นด้วยการหากุญแจต่างประเทศใด ๆ ในตารางนั้นและตรวจสอบให้แน่ใจว่าไม่มีข้อมูล - ถ้าเป็นเช่นนั้นมันจะเรียกตัวเองซ้ำแล้วซ้ำอีกในข้อมูลที่พบ มันใช้อาร์เรย์ของข้อมูลที่ทำเครื่องหมายไว้แล้วเพื่อลบเพื่อป้องกันการวนซ้ำไม่สิ้นสุด โปรดทดสอบและแจ้งให้เราทราบว่ามันทำงานอย่างไรสำหรับคุณ หมายเหตุ: มันช้าไปหน่อย ฉันเรียกมันว่าอย่างนั้น select delete_cascade('public','my_table','1');

create or replace function delete_cascade(p_schema varchar, p_table varchar, p_key varchar, p_recursion varchar[] default null)
 returns integer as $$
declare
    rx record;
    rd record;
    v_sql varchar;
    v_recursion_key varchar;
    recnum integer;
    v_primary_key varchar;
    v_rows integer;
begin
    recnum := 0;
    select ccu.column_name into v_primary_key
        from
        information_schema.table_constraints  tc
        join information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name and ccu.constraint_schema=tc.constraint_schema
        and tc.constraint_type='PRIMARY KEY'
        and tc.table_name=p_table
        and tc.table_schema=p_schema;

    for rx in (
        select kcu.table_name as foreign_table_name, 
        kcu.column_name as foreign_column_name, 
        kcu.table_schema foreign_table_schema,
        kcu2.column_name as foreign_table_primary_key
        from information_schema.constraint_column_usage ccu
        join information_schema.table_constraints tc on tc.constraint_name=ccu.constraint_name and tc.constraint_catalog=ccu.constraint_catalog and ccu.constraint_schema=ccu.constraint_schema 
        join information_schema.key_column_usage kcu on kcu.constraint_name=ccu.constraint_name and kcu.constraint_catalog=ccu.constraint_catalog and kcu.constraint_schema=ccu.constraint_schema
        join information_schema.table_constraints tc2 on tc2.table_name=kcu.table_name and tc2.table_schema=kcu.table_schema
        join information_schema.key_column_usage kcu2 on kcu2.constraint_name=tc2.constraint_name and kcu2.constraint_catalog=tc2.constraint_catalog and kcu2.constraint_schema=tc2.constraint_schema
        where ccu.table_name=p_table  and ccu.table_schema=p_schema
        and TC.CONSTRAINT_TYPE='FOREIGN KEY'
        and tc2.constraint_type='PRIMARY KEY'
)
    loop
        v_sql := 'select '||rx.foreign_table_primary_key||' as key from '||rx.foreign_table_schema||'.'||rx.foreign_table_name||'
            where '||rx.foreign_column_name||'='||quote_literal(p_key)||' for update';
        --raise notice '%',v_sql;
        --found a foreign key, now find the primary keys for any data that exists in any of those tables.
        for rd in execute v_sql
        loop
            v_recursion_key=rx.foreign_table_schema||'.'||rx.foreign_table_name||'.'||rx.foreign_column_name||'='||rd.key;
            if (v_recursion_key = any (p_recursion)) then
                --raise notice 'Avoiding infinite loop';
            else
                --raise notice 'Recursing to %,%',rx.foreign_table_name, rd.key;
                recnum:= recnum +delete_cascade(rx.foreign_table_schema::varchar, rx.foreign_table_name::varchar, rd.key::varchar, p_recursion||v_recursion_key);
            end if;
        end loop;
    end loop;
    begin
    --actually delete original record.
    v_sql := 'delete from '||p_schema||'.'||p_table||' where '||v_primary_key||'='||quote_literal(p_key);
    execute v_sql;
    get diagnostics v_rows= row_count;
    --raise notice 'Deleting %.% %=%',p_schema,p_table,v_primary_key,p_key;
    recnum:= recnum +v_rows;
    exception when others then recnum=0;
    end;

    return recnum;
end;
$$
language PLPGSQL;

มันเกิดขึ้นตลอดเวลาโดยเฉพาะกับตารางอ้างอิงตนเอง พิจารณา บริษัท ที่มีระดับการจัดการที่แตกต่างกันในแผนกต่าง ๆ หรืออนุกรมวิธานลำดับชั้นทั่วไป ใช่ฉันเห็นด้วยว่าฟังก์ชั่นนี้ไม่ใช่สิ่งที่ดีที่สุดอย่างแน่นอนตั้งแต่ขนมปังหั่นบาง ๆ แต่เป็นเครื่องมือที่มีประโยชน์ในสถานการณ์ที่เหมาะสม
Joe Love

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

2
ขอบคุณสำหรับวิธีการแก้ปัญหาของคุณ ฉันเขียนการทดสอบและฉันต้องการลบบันทึกและฉันมีปัญหาในการลบการลบนั้น ฟังก์ชั่นของคุณทำงานได้ดีจริงๆ!
Fernando Camargo

1
@ JoeLove คุณมีปัญหาความเร็วอะไร? ในสถานการณ์นั้นการวนรอบซ้ำเป็นวิธีแก้ปัญหาเดียวที่ถูกต้องในใจของฉัน
Hubbitus

1
@ อาร์เทอร์คุณอาจใช้บางเวอร์ชันของแถว -> json -> ข้อความเพื่อให้มันเสร็จอย่างไรก็ตามฉันไม่ได้ไปไกลขนาดนั้น ฉันได้ค้นพบตลอดหลายปีที่ผ่านมาว่าคีย์หลักเอกพจน์ (ที่มีคีย์รองที่เป็นไปได้) นั้นดีสำหรับหลาย ๆ เหตุผล
Joe Love

17

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

ตัวอย่างเช่น:

testing=# create table a (id integer primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "a_pkey" for table "a"
CREATE TABLE
testing=# create table b (id integer references a);
CREATE TABLE

-- put some data in the table
testing=# insert into a values(1);
INSERT 0 1
testing=# insert into a values(2);
INSERT 0 1
testing=# insert into b values(2);
INSERT 0 1
testing=# insert into b values(1);
INSERT 0 1

-- restricting works
testing=# delete from a where id=1;
ERROR:  update or delete on table "a" violates foreign key constraint "b_id_fkey" on table "b"
DETAIL:  Key (id)=(1) is still referenced from table "b".

-- find the name of the constraint
testing=# \d b;
       Table "public.b"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
Foreign-key constraints:
    "b_id_fkey" FOREIGN KEY (id) REFERENCES a(id)

-- drop the constraint
testing=# alter table b drop constraint b_a_id_fkey;
ALTER TABLE

-- create a cascading one
testing=# alter table b add FOREIGN KEY (id) references a(id) on delete cascade; 
ALTER TABLE

testing=# delete from a where id=1;
DELETE 1
testing=# select * from a;
 id 
----
  2
(1 row)

testing=# select * from b;
 id 
----
  2
(1 row)

-- it works, do your stuff.
-- [stuff]

-- recreate the previous state
testing=# \d b;
       Table "public.b"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
Foreign-key constraints:
    "b_id_fkey" FOREIGN KEY (id) REFERENCES a(id) ON DELETE CASCADE

testing=# alter table b drop constraint b_id_fkey;
ALTER TABLE
testing=# alter table b add FOREIGN KEY (id) references a(id) on delete restrict; 
ALTER TABLE

แน่นอนคุณควรสรุปสิ่งต่าง ๆ เช่นนี้ในกระบวนการเพื่อสุขภาพจิตของคุณ


4
ในการสันนิษฐานว่าคีย์ foreign schould ป้องกันการทำสิ่งต่าง ๆ ซึ่งทำให้ฐานข้อมูลไม่สอดคล้องกันนี่ไม่ใช่วิธีการจัดการ คุณสามารถลบ "น่ารังเกียจ" รายการในตอนนี้ แต่คุณกำลังจะออกจำนวนมากเศษผีดิบชอาจทำให้เกิดปัญหาในอนาคต
Sprinterfreak

1
คุณหมายถึงเศษอะไรกันแน่? ระเบียนจะถูกลบผ่านทาง cascade ไม่ควรมีความไม่สอดคล้องกัน
Pedro Borges

1
แทนที่จะเป็นกังวลเกี่ยวกับ "เศษที่น่ารังเกียจ" (ข้อ จำกัด การเรียงซ้อนจะยังคงสอดคล้องกัน) ฉันจะกังวลมากขึ้นเกี่ยวกับการเรียงซ้อนไม่ไกลพอ - ถ้าบันทึกที่ถูกลบต้องการบันทึกที่ถูกลบเพิ่มเติมข้อ จำกัด เหล่านั้นจะต้องแก้ไข เพื่อให้แน่ใจว่าเรียงซ้อนเช่นกัน (หรือใช้ฟังก์ชั่นที่ผมเขียนไว้ด้านบนเพื่อหลีกเลี่ยงสถานการณ์นี้) ... หนึ่งคำแนะนำสุดท้ายในทุกกรณี: ใช้การทำธุรกรรมเพื่อให้คุณสามารถย้อนกลับได้ถ้ามันแย่
Joe Love

7

ฉันไม่สามารถแสดงความคิดเห็นคำตอบของ Palehorse ดังนั้นฉันจึงเพิ่มคำตอบของฉันเอง ตรรกะของ Palehorse นั้นใช้ได้ แต่ประสิทธิภาพอาจไม่ดีกับชุดข้อมูลขนาดใหญ่

DELETE FROM some_child_table sct 
 WHERE exists (SELECT FROM some_Table st 
                WHERE sct.some_fk_fiel=st.some_id);

DELETE FROM some_table;

จะเร็วกว่าถ้าคุณมีดัชนีในคอลัมน์และชุดข้อมูลมีขนาดใหญ่กว่าระเบียนไม่กี่รายการ


7

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

  • ดำเนินการลบทั้งหมดอย่างชัดเจนครั้งละหนึ่งแบบสอบถามเริ่มต้นด้วยตารางลูก (แม้ว่าจะไม่ได้บินถ้าคุณมีการอ้างอิงแบบวงกลม); หรือ
  • ดำเนินการลบทั้งหมดอย่างชัดเจนในการสืบค้นเดียว หรือ
  • สมมติว่าข้อ จำกัด กุญแจต่างประเทศที่ไม่ใช่แบบของคุณถูกสร้างขึ้นเป็น 'ON DELETE NO ACTION DEFERRABLE' ให้ทำการลบทั้งหมดอย่างชัดเจนในธุรกรรมเดียว หรือ
  • ปล่อย 'ไม่มีการกระทำ' และ 'จำกัด ' ข้อ จำกัด คีย์ต่างประเทศชั่วคราวในกราฟสร้างพวกเขาใหม่เป็นแบบ CASCADE ลบบรรพบุรุษที่ล่วงละเมิดวางข้อ จำกัด คีย์ต่างประเทศอีกครั้งและในที่สุดก็สร้างใหม่ตามเดิม ข้อมูลของคุณ); หรือ
  • บางสิ่งอาจสนุกเท่ากัน

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

ฉันมาที่นี่เมื่อสองสามเดือนก่อนเพื่อหาคำตอบสำหรับคำถาม "CASCADE DELETE เพียงครั้งเดียว" (แต่เดิมถูกถามเมื่อสิบกว่าปีที่แล้ว) ฉันได้รับไมล์สะสมจากโซลูชันที่ชาญฉลาดของ Joe Love (และตัวแปรของ Thomas CG de Vilhena) แต่ในที่สุดกรณีการใช้งานของฉันมีข้อกำหนดเฉพาะ (การจัดการการอ้างอิงแบบวงกลมภายในตารางสำหรับข้อหนึ่ง) ในที่สุดแนวทางดังกล่าวก็กลายเป็นซ้ำซ้ำ (PG 10.10)

ฉันใช้ recursively_delete ในการผลิตมาระยะหนึ่งแล้วและในที่สุดก็รู้สึก (มั่นใจ) มากพอที่จะทำให้คนอื่น ๆ ที่อาจมาที่นี่มองหาแนวคิด เช่นเดียวกับโซลูชันของ Joe Love จะช่วยให้คุณสามารถลบกราฟข้อมูลทั้งหมดราวกับว่าข้อ จำกัด คีย์ต่างประเทศทั้งหมดในฐานข้อมูลของคุณถูกตั้งค่าเป็น CASCADE ชั่วครู่ แต่มีคุณสมบัติเพิ่มเติมสองสามประการ:

  • แสดงตัวอย่าง ASCII ของเป้าหมายการลบและกราฟของผู้ติดตาม
  • ทำการลบในแบบสอบถามเดียวโดยใช้ CTE แบบเรียกซ้ำ
  • รองรับการอ้างอิงแบบวงกลมภายในและระหว่างตาราง
  • จัดการคีย์ผสม
  • ข้ามข้อ จำกัด 'set default' และ 'set null'

ฉันได้รับข้อผิดพลาด: ข้อผิดพลาด: อาร์เรย์ต้องมีองค์ประกอบจำนวนคู่โดยที่: ฟังก์ชัน PL / pgSQL _recursively_delete (regclass, ข้อความ [], จำนวนเต็ม, jsonb, จำนวนเต็ม, ข้อความ [], jsonb, jsonb) บรรทัดที่ 15 ที่คำสั่ง SQL "SELECT * FROM _recursively_delete (ARG_table, VAR_pk_col_names)" ฟังก์ชัน PL / pgSQL recursively_delete (regclass, anyelement, boolean) บรรทัด 73 ที่คำสั่ง SQL
Joe Love

เฮ้ @JoeLove ขอบคุณที่ทดลองใช้ คุณสามารถให้ขั้นตอนการทำซ้ำฉันได้หรือไม่ แล้วรุ่น PG ของคุณคืออะไร?
TRL

ฉันไม่แน่ใจว่าสิ่งนี้จะช่วยได้ แต่ฉันเพิ่งสร้างฟังก์ชั่นของคุณแล้วรันรหัสต่อไปนี้: select recursively_delete ('dallas.vendor', 1094, false) หลังจากตรวจแก้จุดบกพร่องบางอย่างฉันพบว่าสิ่งนี้เสียชีวิตทันทีจากค้างคาว - ความหมายดูเหมือนว่าเป็นสายแรก ฟังก์ชั่นไม่ใช่หลังจากทำหลายอย่าง สำหรับการอ้างอิงฉันใช้ PG 10.8
Joe Love

@JoeLove โปรดลองสาขา trl-fix-array_must_have_even_number_of_element ( github.com/trlorenz/PG-recursively_delete/pull/2 )
TRL

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

3

คุณสามารถใช้งานได้โดยอัตโนมัตินี้คุณสามารถกำหนดข้อ จำกัด ON DELETE CASCADEที่สำคัญต่างประเทศที่มี
ฉันอ้างถึงคู่มือของข้อ จำกัด กุญแจต่างประเทศ :

CASCADE ระบุว่าเมื่อลบแถวอ้างอิงแล้วแถวอ้างอิงควรถูกลบโดยอัตโนมัติเช่นกัน


1
แม้ว่าสิ่งนี้จะไม่จัดการกับ OP แต่ก็เป็นการวางแผนที่ดีสำหรับเมื่อต้องลบแถวที่มีคีย์ต่างประเทศ ดังที่ Ben Franklin กล่าวว่า "การป้องกันหนึ่งออนซ์นั้นคุ้มค่ากับการรักษาหนึ่งปอนด์"
Jesuisme

1
ฉันพบว่าวิธีนี้อาจเป็นอันตรายได้หากแอปของคุณลบบันทึกที่มีพี่น้องจำนวนมากและแทนที่จะมีข้อผิดพลาดเล็กน้อยคุณได้ลบชุดข้อมูลขนาดใหญ่อย่างถาวร
Joe Love

2

ฉันใช้คำตอบของ Joe Love และเขียนใหม่โดยใช้INโอเปอเรเตอร์ที่มีตัวเลือกย่อยแทน=เพื่อทำให้การทำงานเร็วขึ้น (ตามคำแนะนำของ Hubbitus):

create or replace function delete_cascade(p_schema varchar, p_table varchar, p_keys varchar, p_subquery varchar default null, p_foreign_keys varchar[] default array[]::varchar[])
 returns integer as $$
declare

    rx record;
    rd record;
    v_sql varchar;
    v_subquery varchar;
    v_primary_key varchar;
    v_foreign_key varchar;
    v_rows integer;
    recnum integer;

begin

    recnum := 0;
    select ccu.column_name into v_primary_key
        from
        information_schema.table_constraints  tc
        join information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name and ccu.constraint_schema=tc.constraint_schema
        and tc.constraint_type='PRIMARY KEY'
        and tc.table_name=p_table
        and tc.table_schema=p_schema;

    for rx in (
        select kcu.table_name as foreign_table_name, 
        kcu.column_name as foreign_column_name, 
        kcu.table_schema foreign_table_schema,
        kcu2.column_name as foreign_table_primary_key
        from information_schema.constraint_column_usage ccu
        join information_schema.table_constraints tc on tc.constraint_name=ccu.constraint_name and tc.constraint_catalog=ccu.constraint_catalog and ccu.constraint_schema=ccu.constraint_schema 
        join information_schema.key_column_usage kcu on kcu.constraint_name=ccu.constraint_name and kcu.constraint_catalog=ccu.constraint_catalog and kcu.constraint_schema=ccu.constraint_schema
        join information_schema.table_constraints tc2 on tc2.table_name=kcu.table_name and tc2.table_schema=kcu.table_schema
        join information_schema.key_column_usage kcu2 on kcu2.constraint_name=tc2.constraint_name and kcu2.constraint_catalog=tc2.constraint_catalog and kcu2.constraint_schema=tc2.constraint_schema
        where ccu.table_name=p_table  and ccu.table_schema=p_schema
        and TC.CONSTRAINT_TYPE='FOREIGN KEY'
        and tc2.constraint_type='PRIMARY KEY'
)
    loop
        v_foreign_key := rx.foreign_table_schema||'.'||rx.foreign_table_name||'.'||rx.foreign_column_name;
        v_subquery := 'select "'||rx.foreign_table_primary_key||'" as key from '||rx.foreign_table_schema||'."'||rx.foreign_table_name||'"
             where "'||rx.foreign_column_name||'"in('||coalesce(p_keys, p_subquery)||') for update';
        if p_foreign_keys @> ARRAY[v_foreign_key] then
            --raise notice 'circular recursion detected';
        else
            p_foreign_keys := array_append(p_foreign_keys, v_foreign_key);
            recnum:= recnum + delete_cascade(rx.foreign_table_schema, rx.foreign_table_name, null, v_subquery, p_foreign_keys);
            p_foreign_keys := array_remove(p_foreign_keys, v_foreign_key);
        end if;
    end loop;

    begin
        if (coalesce(p_keys, p_subquery) <> '') then
            v_sql := 'delete from '||p_schema||'."'||p_table||'" where "'||v_primary_key||'"in('||coalesce(p_keys, p_subquery)||')';
            --raise notice '%',v_sql;
            execute v_sql;
            get diagnostics v_rows = row_count;
            recnum := recnum + v_rows;
        end if;
        exception when others then recnum=0;
    end;

    return recnum;

end;
$$
language PLPGSQL;

2
ฉันจะต้องดูที่นี้และดูว่ามันทำงานได้ดีกับข้อ จำกัด การอ้างอิงตนเองและไม่ชอบ ฉันพยายามทำสิ่งที่คล้ายกัน แต่หยุดทำงานไม่เต็มที่ หากโซลูชันของคุณใช้ได้สำหรับฉันฉันจะนำไปใช้ นี่เป็นหนึ่งในเครื่องมือ dba จำนวนมากที่ควรทำแพ็กเกจและวางไว้บน GitHub หรืออะไรบางอย่าง
Joe Love

ฉันมีฐานข้อมูลขนาดกลางสำหรับ CMS ที่มีผู้เช่าหลายคน (ลูกค้าทุกคนใช้ตารางเดียวกันร่วมกัน) รุ่นของฉัน (โดยไม่ต้อง "ใน") ดูเหมือนว่าจะทำงานค่อนข้างช้าในการลบร่องรอยทั้งหมดของลูกค้าเก่า ... ฉันสนใจที่จะลองใช้กับข้อมูลจำลองเพื่อเปรียบเทียบความเร็ว คุณมีอะไรที่คุณสามารถพูดเกี่ยวกับความแตกต่างความเร็วที่คุณสังเกตเห็นในกรณีการใช้งานของคุณหรือไม่?
Joe Love

สำหรับกรณีการใช้งานของฉันฉันสังเกตเห็นความเร็วเพิ่มขึ้นเป็น 10 เท่าเมื่อใช้inโอเปอเรเตอร์และการสอบถามย่อย
โธมัส CG de Vilhena

1

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

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


2
คำตอบของ Grant มีผิดบางส่วน - Postgresql ไม่สนับสนุน CASCADE จากการลบ DELETE postgresql.org/docs/8.4/static/dml-delete.html
Fredrik Wendt

มีความคิดใดบ้างที่ไม่สนับสนุนการลบคิวรี
Teifion

2
ไม่มีทางที่จะ "ลบด้วยการเรียงซ้อน" บนตารางที่ไม่ได้ตั้งค่าไว้เช่นข้อ จำกัด คีย์ต่างประเทศไม่ได้ถูกกำหนดเป็น ON DELETE CASCADE ซึ่งเป็นคำถามที่เกิดขึ้นในตอนแรก
lensovet

เป็นคำตอบกับผู้นี้คำถามนี้ไม่ถูกต้องสมบูรณ์ ไม่มีวิธีการเรียงซ้อนอีกครั้ง
Jeremy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.