KASKADA USUŃ tylko raz


199

Mam bazę danych Postgresql, na której chcę wykonać kilka kaskadowych operacji usuwania. Jednak tabele nie są konfigurowane z regułą ON DELETE CASCADE. Czy jest jakiś sposób, aby wykonać operację usunięcia i powiedzieć Postgresqlowi, aby wykonała kaskadę tylko raz? Coś równoważnego z

DELETE FROM some_table CASCADE;

Odpowiedzi na to starsze pytanie sprawiają, że wydaje się, że nie ma takiego rozwiązania, ale pomyślałem, że zadam to pytanie wprost, aby się upewnić.


Zobacz moją niestandardową funkcję poniżej. Jest to możliwe z pewnymi ograniczeniami.
Joe Love

Odpowiedzi:


175

Nie. Aby to zrobić, wystarczy napisać instrukcję delete dla tabeli, którą chcesz kaskadować.

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

12
To niekoniecznie działa, ponieważ mogą istnieć inne klucze obce kaskadowe od oryginalnej kaskady (rekurencji). Możesz nawet dostać się do pętli, w której tabela a odnosi się do b, który odnosi się do a. Aby to osiągnąć w ogólnym sensie, zobacz moją tabelę poniżej, ale ma pewne ograniczenia. Jeśli masz prostą konfigurację tabeli, a następnie wypróbuj powyższy kod, łatwiej jest zrozumieć, co robisz.
Joe Love

2
Prosto, bezpiecznie. Powinieneś uruchomić je w jednej transakcji, jeśli masz wstawki gęstości.
İsmail Yavuz,

39

Jeśli naprawdę chcesz, DELETE FROM some_table CASCADE; co oznacza „ usuń wszystkie wiersze z tabelisome_table ”, możesz użyć TRUNCATEzamiast DELETEi CASCADEzawsze jest obsługiwane. Jednak jeśli chcesz użyć selektywnego usuwania z whereklauzulą, TRUNCATEnie jest wystarczająco dobre.

UŻYWAJ Z PIELĘGNACJĄ - Spowoduje to upuszczenie wszystkich wierszy wszystkich tabel, które mają ograniczenia na klucz obcy some_tablei wszystkich tabel, które mają ograniczenia na tych tabelach itp.

Obsługa Postgres CASCADEz poleceniem TRUNCATE :

TRUNCATE some_table CASCADE;

Jest to zręcznie transakcyjne (tzn. Może zostać wycofane), chociaż nie jest w pełni odizolowane od innych równoczesnych transakcji i ma kilka innych zastrzeżeń. Przeczytaj dokumentację, aby uzyskać szczegółowe informacje.


226
wyraźnie „kilka kaskadowych usunięć” ≠ upuszczając wszystkie dane ze stołu…
lensovet

33
Spowoduje to upuszczenie wszystkich wierszy wszystkich tabel, które mają ograniczenie klucza obcego na some_table i wszystkich tabel, które mają ograniczenia na tych tabelach itp., Jest to potencjalnie bardzo niebezpieczne.
AJP 24.04.13

56
strzec się. to lekkomyślna odpowiedź.
Jordan Arseno,

4
Ktoś oznaczył tę odpowiedź do usunięcia - prawdopodobnie dlatego, że się z nią nie zgadza. Prawidłowym działaniem w tym przypadku jest głosowanie, a nie oznaczanie.
Wai Ha Lee,

7
Ma na górze ostrzeżenie. Jeśli zdecydujesz się to zignorować, nikt ci nie pomoże. Myślę, że twoi użytkownicy „copyPaste” są tutaj prawdziwym zagrożeniem.
BluE

28

Napisałem funkcję (rekurencyjną), aby usunąć dowolny wiersz na podstawie jego klucza podstawowego. Napisałem to, ponieważ nie chciałem tworzyć moich ograniczeń jako „przy kasowaniu kasowania”. Chciałem być w stanie usunąć złożone zestawy danych (jako DBA), ale nie pozwolić moim programistom na kaskadowe usuwanie bez przemyślenia wszystkich konsekwencji. Wciąż testuję tę funkcję, więc mogą być w niej błędy - ale proszę nie próbuj jej, jeśli twoja baza danych ma wiele kolumn podstawowych (a więc obcych) kluczy. Ponadto wszystkie klucze muszą być reprezentowane w postaci ciągu, ale można je zapisać w sposób, który nie ma tego ograniczenia. Używam tej funkcji BARDZO Oszczędnie, zbyt cenię swoje dane, aby umożliwić kaskadowe ograniczenia na wszystko. Zasadniczo ta funkcja jest przekazywana w schemacie, nazwie tabeli i wartości podstawowej (w postaci ciągu), i zacznie od znalezienia jakichkolwiek obcych kluczy w tej tabeli i upewni się, że dane nie istnieją - jeśli tak, rekurencyjnie wywołuje się na znalezionych danych. Wykorzystuje tablicę danych już zaznaczonych do usunięcia, aby zapobiec nieskończonym pętlom. Sprawdź to i daj mi znać, jak to działa. Uwaga: jest trochę powolny. Nazywam to tak: 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;

Zdarza się to cały czas, szczególnie w przypadku tabel z odnośnikami. Rozważmy firmę z różnymi poziomami zarządzania w różnych działach lub ogólną hierarchiczną taksonomię. Tak, zgadzam się, że ta funkcja nie jest absolutnie najlepszą rzeczą od krojonego chleba, ale jest użytecznym narzędziem w odpowiedniej sytuacji.
Joe Love

Jeśli przepiszesz, zaakceptujesz tablicę identyfikatorów, a także wygenerujesz zapytania, które użyją INoperatora z podselekcjami zamiast =(więc krok do użycia logiki zbiorów) stałoby się znacznie szybsze.
Hubbitus

2
Dziękuję za twoje rozwiązanie. Piszę kilka testów i musiałem usunąć rekord i miałem problem z jego kaskadowym usunięciem. Twoja funkcja działała naprawdę dobrze!
Fernando Camargo

1
@JoeLove, jaki masz problem z prędkością? W takiej sytuacji rekurencja jest dla mnie jedynym poprawnym rozwiązaniem.
Hubbitus

1
@arthur prawdopodobnie możesz użyć jakiejś wersji wiersza -> json -> tekstu, aby to zrobić, jednak nie posunąłem się tak daleko. Odkryłem przez lata, że ​​pojedynczy klucz główny (z potencjalnymi kluczami wtórnymi) jest dobry z wielu powodów.
Joe Love

17

Jeśli dobrze rozumiem, powinieneś być w stanie zrobić, co chcesz, usuwając ograniczenie klucza obcego, dodając nowe (które będzie kaskadowo), robiąc swoje rzeczy i odtwarzając ograniczenia klucza obcego.

Na przykład:

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

Oczywiście, ze względu na swoje zdrowie psychiczne, powinieneś streścić takie rzeczy w procedurze.


4
Zakładając, że klucz obcy nie powinien robić rzeczy, które powodują niespójność bazy danych, nie jest to sposób na radzenie sobie z tym. Możesz teraz usunąć wpis „paskudny”, ale pozostawiasz wiele odłamków zombie, które mogą powodować problemy w przyszłości
Sprinterfreak

1
Co dokładnie masz na myśli? rekordy zostaną usunięte kaskadowo, nie powinno być niespójności.
Pedro Borges

1
zamiast martwić się o „paskudne odłamki” (ograniczenia kaskadowe będą nadal spójne), MNIE bardziej martwiłoby to, że kaskadowanie nie zajdzie wystarczająco daleko - jeśli usunięte rekordy wymagają dalszych usuniętych rekordów, wówczas ograniczenia te trzeba będzie zmienić aby zapewnić również kaskadowanie. (lub użyj funkcji, którą napisałem powyżej, aby uniknąć tego scenariusza) ... I tak ostatnia rekomendacja: UŻYJ TRANSAKCJI, aby móc ją cofnąć, jeśli pójdzie nie tak.
Joe Love

7

Nie mogę skomentować odpowiedzi Palehorse, więc dodałem własną odpowiedź. Logika Palehorse jest dobra, ale wydajność może być zła w przypadku dużych zbiorów danych.

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;

Jest to szybsze, jeśli masz indeksy na kolumnach, a zestaw danych jest większy niż kilka rekordów.


7

Tak, jak powiedzieli inni, nie ma wygodnego „USUŃ Z mojej_tabeli ... KASKADA” (lub odpowiednika). Aby usunąć niekaskadowe rekordy potomne chronione kluczem obcym i ich przodków, do których się odwołujesz, masz następujące opcje:

  • Wykonuj wszystkie usunięcia jawnie, po jednym zapytaniu na raz, zaczynając od tabel podrzędnych (choć to nie powiedzie się, jeśli masz okrągłe odwołania); lub
  • Wykonaj wszystkie usunięcia jawnie w jednym (potencjalnie masywnym) zapytaniu; lub
  • Zakładając, że twoje niekaskadujące ograniczenia dla klucza obcego zostały utworzone jako „PRZY USUNIĘCIU BEZ DZIAŁANIA ZRÓŻNICOWANEGO”, wykonaj wszystkie usunięcia jawnie w jednej transakcji; lub
  • Tymczasowo porzuć „brak działania” i „ogranicz” ograniczenia klucza obcego na wykresie, odtwórz je jako KASKADA, usuń szkodliwych przodków, ponownie upuść ograniczenia klucza obcego i na koniec odtwórz je takimi, jakie były pierwotnie (tym samym tymczasowo osłabiając integralność Twoje dane); lub
  • Coś chyba równie fajnego.

Zakładam, że obchodzenie ograniczeń klucza obcego nie jest wygodne. ale rozumiem, dlaczego w określonych okolicznościach chcesz to zrobić. Jeśli robisz to z pewną częstotliwością, a jeśli chcesz zignorować mądrość DBA wszędzie, możesz zautomatyzować to za pomocą procedury.

Przybyłem tu kilka miesięcy temu, szukając odpowiedzi na pytanie „KASKADA USUŃ tylko raz” (pierwotnie zadane ponad dekadę temu!). Przebiegłem trochę przez sprytne rozwiązanie Joe Love (i wariant Thomasa CG de Vilhena), ale ostatecznie mój przypadek użycia miał szczególne wymagania (na przykład obsługę okólnych odniesień wewnątrz tabeli), które zmusiły mnie do innego podejścia. To podejście ostatecznie stało się rekurencyjnie_delete (PG 10.10).

Od jakiegoś czasu używam rekursywnie_kasowania w produkcji i nareszcie czuję się (ostrożnie) na tyle pewny siebie, aby udostępnić go innym, którzy mogą tu znaleźć pomysły. Podobnie jak w przypadku rozwiązania Joe Love, pozwala on usuwać całe wykresy danych, tak jakby wszystkie ograniczenia kluczy obcych w bazie danych były chwilowo ustawione na CASCADE, ale oferuje kilka dodatkowych funkcji:

  • Zapewnia podgląd ASCII celu usuwania i wykres zależności.
  • Wykonuje usunięcie w jednym zapytaniu przy użyciu rekurencyjnych CTE.
  • Obsługuje zależności cykliczne, wewnętrzne i między tabelami.
  • Obsługuje klucze kompozytowe.
  • Pomija ograniczenia „ustaw domyślne” i „ustaw zerowe”.

Otrzymuję błąd: BŁĄD: tablica musi mieć parzystą liczbę elementów Gdzie: funkcja PL / pgSQL _recursively_delete (regclass, text [], liczba całkowita, jsonb, liczba całkowita, tekst [], jsonb, jsonb) wiersz 15 przy przypisaniu Instrukcja SQL „WYBIERZ * Z _recursively_delete (ARG_table, VAR_pk_col_names)” Funkcja PL / pgSQL recursively_delete (regclass, anyelement, boolean) wiersz 73 w instrukcji SQL
Joe Love

Cześć, @JoeLove. Dzięki za wypróbowanie. Czy możesz mi podać kroki, aby się odtworzyć? A jaka jest twoja wersja PG?
TRL

Nie jestem pewien, czy to pomoże. ale właśnie utworzyłem twoje funkcje, a następnie uruchomiłem następujący kod: select recursively_delete ('dallas.vendor', 1094, false) Po pewnym debugowaniu stwierdziłem, że to umiera od razu - oznacza to, że wygląda na to, że jest to pierwsze wywołanie do funkcji, nie po zrobieniu wielu rzeczy. Dla porównania korzystam z PG 10.8
Joe Love

@JoeLove, Uprzejmie wypróbuj gałąź trl-fix-array_must_have_even_number_of_element ( github.com/trlorenz/PG-recursively_delete/pull/2 ).
TRL

Próbowałem tej gałęzi i naprawiłem pierwotny błąd. Niestety, nie jest to szybsze niż moja oryginalna wersja (co może nie było twoim celem w napisaniu tego w pierwszej kolejności). Pracuję nad kolejną próbą, która tworzy zduplikowane klucze obce z opcją „przy kasowaniu kasowania”, następnie usuwa oryginalny rekord, a następnie usuwa wszystkie nowo utworzone klucze obce,
Joe Love

3

Możesz użyć do zautomatyzowania tego, możesz zdefiniować ograniczenie klucza obcego za pomocą ON DELETE CASCADE.
Cytuję instrukcję ograniczeń klucza obcego :

CASCADE określa, że ​​po usunięciu przywoływanego wiersza wiersze, do których się odwołuje, powinny również zostać automatycznie usunięte.


1
Chociaż nie dotyczy to PO, dobrze jest zaplanować, kiedy wiersze z kluczami obcymi muszą zostać usunięte. Jak powiedział Ben Franklin, „uncja zapobiegania jest warta funta lekarstwa”.
Jesuisme

1
Przekonałem się, że to rozwiązanie może być dość niebezpieczne, jeśli twoja aplikacja usuwa rekord z dużą liczbą rodzeństwa i zamiast drobnego błędu trwale usunąłeś ogromny zestaw danych.
Joe Love

2

Wziąłem odpowiedź Joe Love i przepisałem ją za pomocą IN operatora z =podselekcjami zamiast przyspieszyć funkcję (zgodnie z sugestią Hubbitusa):

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
Muszę na to spojrzeć i zobaczyć, jak dobrze działa z ograniczeniami do samodzielnego odwoływania się i tym podobnymi. Próbowałem zrobić coś podobnego, ale przestałem działać. Jeśli twoje rozwiązanie działa dla mnie, zamierzam je wdrożyć. Jest to jedno z wielu narzędzi dba, które należy spakować i umieścić na github lub coś takiego.
Joe Love

Mam bazy danych średniej wielkości dla systemu CMS dla wielu dzierżawców (wszyscy klienci mają te same tabele). Moja wersja (bez „in”) wydaje się działać dość wolno, aby usunąć wszystkie ślady starego klienta ... Jestem zainteresowany wypróbowaniem tego z pewnymi danymi makiety w celu porównania prędkości. Czy masz coś do powiedzenia na temat różnicy prędkości, którą zauważyłeś w swoich przypadkach użycia?
Joe Love

W moim przypadku użycia zauważyłem przyspieszenie rzędu 10x podczas korzystania z inoperatora i zapytań podrzędnych.
Thomas CG de Vilhena

1

Usuń z opcją kaskady zastosowaną tylko do tabel ze zdefiniowanymi kluczami obcymi. Jeśli wykonasz operację usuwania, która mówi, że nie możesz, ponieważ naruszyłaby to ograniczenie klucza obcego, kaskada spowoduje usunięcie niepoprawnych wierszy.

Jeśli chcesz usunąć powiązane wiersze w ten sposób, musisz najpierw zdefiniować klucze obce. Pamiętaj też, że jeśli nie wydasz wyraźnego polecenia rozpoczęcia transakcji lub nie zmienisz ustawień domyślnych, nastąpi automatyczne zatwierdzenie, którego oczyszczenie może zająć dużo czasu.


2
Odpowiedź Granta jest częściowo błędna - Postgresql nie obsługuje CASCADE dla zapytań DELETE. postgresql.org/docs/8.4/static/dml-delete.html
Fredrik Wendt

Masz pomysł, dlaczego nie jest obsługiwany w kwerendzie usuwania?
Teifion

2
nie ma sposobu, aby „usunąć kaskadowo” w tabeli, która nie została odpowiednio skonfigurowana, tj. dla której ograniczenie klucza obcego nie zostało zdefiniowane jako NA USUŃ KASKADĘ, o co pierwotnie chodziło w pytaniu.
lensovet

Jako odpowiedź na to pytanie jest to całkowicie błędne. Raz nie ma sposobu na KASKADĘ.
Jeremy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.