it-swarm.com.de

CASCADE DELETE nur einmal

Ich habe eine Postgresql-Datenbank, auf der ich einige kaskadierende Löschvorgänge ausführen möchte. Die Tabellen werden jedoch nicht mit der ON DELETE CASCADE-Regel eingerichtet. Gibt es eine Möglichkeit, ein Löschen durchzuführen und Postgresql anzuweisen, es nur einmal zu kaskadieren? Etwas äquivalent zu

DELETE FROM some_table CASCADE;

Die Antworten auf diese ältere Frage lassen es so aussehen, als gäbe es keine solche Lösung, aber ich dachte, ich würde diese Frage ausdrücklich stellen, nur um sicherzugehen.

170
Eli Courtwright

Nein. Um dies nur einmal zu tun, schreiben Sie einfach die delete-Anweisung für die Tabelle, die Sie kaskadieren möchten.

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

Wenn Sie wirklich wollen DELETE FROM some_table CASCADE; was bedeutet " entferne alle Zeilen aus der Tabelle some_table ", Sie können TRUNCATE anstelle von DELETE verwenden, und CASCADE wird immer unterstützt. Wenn Sie jedoch das selektive Löschen mit einem where Klausel, TRUNCATE ist nicht gut genug.

USE WITH CARE - Dies wird alle Zeilen aller Tabellen löschen, die haben eine Fremdschlüsseleinschränkung für some_table und alle Tabellen mit Einschränkungen für diese Tabellen usw.

Postgres unterstützt CASCADE mit TRUNCATE-Befehl :

TRUNCATE some_table CASCADE;

Handlich ist dies eine Transaktion (d. H. Kann rückgängig gemacht werden), obwohl es nicht vollständig von anderen gleichzeitigen Transaktionen isoliert ist und mehrere andere Vorbehalte aufweist. Lesen Sie die Dokumentation für Details.

38
DanC

Ich habe eine (rekursive) Funktion geschrieben, um jede Zeile basierend auf ihrem Primärschlüssel zu löschen. Ich habe dies geschrieben, weil ich meine Einschränkungen nicht als "on delete cascade" erstellen wollte. Ich wollte in der Lage sein, komplexe Datensätze (als DBA) zu löschen, aber meinen Programmierern nicht die Möglichkeit geben, Kaskadenlöschvorgänge durchzuführen, ohne über alle Auswirkungen nachzudenken. Ich teste diese Funktion immer noch aus, daher kann es sein, dass sie Fehler enthält. Versuchen Sie es jedoch nicht, wenn Ihre Datenbank mehrspaltige Primärschlüssel (und damit Fremdschlüssel) enthält. Außerdem müssen alle Schlüssel in der Lage sein, in Zeichenfolgenform dargestellt zu werden, sie können jedoch auch so geschrieben werden, dass diese Einschränkung nicht besteht. Ich benutze diese Funktion sowieso sehr sparsam, ich schätze meine Daten zu sehr, um die kaskadierenden Einschränkungen für alles zu ermöglichen. Grundsätzlich wird diese Funktion in Form eines Schemas, eines Tabellennamens und eines Primärwerts (in Form einer Zeichenfolge) übergeben und sucht zunächst nach Fremdschlüsseln in dieser Tabelle und stellt sicher, dass keine Daten vorhanden sind. Wenn dies der Fall ist, wird sie rekursiv aufgerufen sich auf die gefundenen Daten. Es verwendet ein Array von Daten, die bereits zum Löschen markiert sind, um Endlosschleifen zu vermeiden. Bitte testen Sie es aus und lassen Sie mich wissen, wie es bei Ihnen funktioniert. Hinweis: Es ist etwas langsam. Ich nenne es so: 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;
23
Joe Love

Wenn ich das richtig verstehe, sollten Sie in der Lage sein, das zu tun, was Sie wollen, indem Sie die Fremdschlüsseleinschränkung löschen, eine neue (die kaskadiert) hinzufügen, Ihre Aufgaben erledigen und die einschränkende Fremdschlüsseleinschränkung neu erstellen.

Zum Beispiel:

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

Natürlich sollten Sie solche Dinge aus Gründen Ihrer geistigen Gesundheit zu einem Verfahren zusammenfassen.

17
Ryszard Szopa

Ich kann palehorse Antwort nicht kommentieren, also fügte ich meine eigene Antwort hinzu. Palehorse Logick ist in Ordnung, aber die Effizienz kann bei großen Datenmengen schlecht sein.

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;

Es ist schneller, wenn Sie Indizes für Spalten haben und der Datensatz größer ist als wenige Datensätze.

5
Grzegorz Grabek

Ich nahm Joe Loves Antwort und schrieb sie mit dem IN -Operator mit Unterauswahl anstelle von = Um, um die Funktion zu beschleunigen (gemäß Hubbits Vorschlag):

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;

Sie können dies automatisieren, indem Sie die Fremdschlüsselbedingung mit ON DELETE CASCADE Definieren.
Ich zitiere das das Handbuch der Fremdschlüsseleinschränkungen :

CASCADE gibt an, dass beim Löschen einer referenzierten Zeile auch die darauf verweisenden Zeilen automatisch gelöscht werden sollen.

2
atiruz

Das Löschen mit der Kaskadenoption gilt nur für Tabellen mit definierten Fremdschlüsseln. Wenn Sie einen Löschvorgang ausführen und dies nicht der Fall ist, da dies die Fremdschlüsseleinschränkung verletzen würde, werden die fehlerhaften Zeilen durch die Kaskade gelöscht.

Wenn Sie auf diese Weise verknüpfte Zeilen löschen möchten, müssen Sie zuerst die Fremdschlüssel definieren. Denken Sie auch daran, dass das automatische Festschreiben sehr zeitaufwändig sein kann, wenn Sie nicht ausdrücklich anweisen, eine Transaktion zu starten, oder wenn Sie die Standardeinstellungen ändern.

1
Grant Johnson