Jak dodać ograniczenia „przy usuwaniu kaskady”?

163

Czy w PostgreSQL 8 można dodać ON DELETE CASCADESdo obu kluczy obcych w poniższej tabeli bez pomijania tych drugich?

# \d scores
        Table "public.scores"
 Column  |         Type          | Modifiers
---------+-----------------------+-----------
 id      | character varying(32) |
 gid     | integer               |
 money   | integer               | not null
 quit    | boolean               |
 last_ip | inet                  |
Foreign-key constraints:
   "scores_gid_fkey" FOREIGN KEY (gid) REFERENCES games(gid)
   "scores_id_fkey" FOREIGN KEY (id) REFERENCES users(id)

Obie przywoływane tabele znajdują się poniżej - tutaj:

# \d games
                                     Table "public.games"
  Column  |            Type             |                        Modifiers
----------+-----------------------------+----------------------------------------------------------
 gid      | integer                     | not null default nextval('games_gid_seq'::regclass)
 rounds   | integer                     | not null
 finished | timestamp without time zone | default now()
Indexes:
    "games_pkey" PRIMARY KEY, btree (gid)
Referenced by:
    TABLE "scores" CONSTRAINT "scores_gid_fkey" FOREIGN KEY (gid) REFERENCES games(gid)

I tu:

# \d users
                Table "public.users"
   Column   |            Type             |   Modifiers
------------+-----------------------------+---------------
 id         | character varying(32)       | not null
 first_name | character varying(64)       |
 last_name  | character varying(64)       |
 female     | boolean                     |
 avatar     | character varying(128)      |
 city       | character varying(64)       |
 login      | timestamp without time zone | default now()
 last_ip    | inet                        |
 logout     | timestamp without time zone |
 vip        | timestamp without time zone |
 mail       | character varying(254)      |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "cards" CONSTRAINT "cards_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "catch" CONSTRAINT "catch_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "chat" CONSTRAINT "chat_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "game" CONSTRAINT "game_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "hand" CONSTRAINT "hand_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "luck" CONSTRAINT "luck_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "match" CONSTRAINT "match_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "misere" CONSTRAINT "misere_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "money" CONSTRAINT "money_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "pass" CONSTRAINT "pass_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "payment" CONSTRAINT "payment_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "rep" CONSTRAINT "rep_author_fkey" FOREIGN KEY (author) REFERENCES users(id)
    TABLE "rep" CONSTRAINT "rep_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "scores" CONSTRAINT "scores_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "status" CONSTRAINT "status_id_fkey" FOREIGN KEY (id) REFERENCES users(id)

A także zastanawiam się, czy warto dodać 2 indeksy do poprzedniej tabeli?

AKTUALIZACJA: Dziękuję, a także mam radę na liście mailingowej, że mogę nią zarządzać w 1 wyciągu, a więc bez jawnego rozpoczynania transakcji:

ALTER TABLE public.scores
DROP CONSTRAINT scores_gid_fkey,
ADD CONSTRAINT scores_gid_fkey
   FOREIGN KEY (gid)
   REFERENCES games(gid)
   ON DELETE CASCADE;
Alexander Farber
źródło
1
Trochę OT, ale zauważyłem, że nie utworzyłeś indeksów na kolumnach odwołujących się (na przykład pref_scores.gid). Usunięcia w tabeli, do której istnieją odwołania, będą wymagały długiego czasu bez nich, jeśli w tych tabelach jest wiele wierszy. Niektóre bazy danych automatycznie tworzą indeks w kolumnie (kolumnach) odwołującej się; PostgreSQL pozostawia to tobie, ponieważ są przypadki, w których nie warto.
kgrittn
1
Dziękuję Ci! Właściwie zauważyłem, że usuwanie trwa długo, ale nie wiedziałem, że to jest powód
Alexander Farber
1
Jakie byłyby to przypadki, gdy indeksy kluczy obcych nie są warte zachodu?
Alexander Farber
2
Uwzględniłem twoje odkrycie w mojej odpowiedzi. (To pojedyncze oświadczenie jest również pojedynczą transakcją.)
Mike Sherrill `` Cat Recall ''
2
@AlexanderFarber: Kiedy możesz chcieć pominąć indeks w kolumnach odwołujących się do FK? Gdy istnieje inny indeks, który nie jest dokładnym dopasowaniem, który będzie działał wystarczająco dobrze (np. Możesz mieć indeks trygramowy dla częstych wyszukiwań podobieństw, który będzie również OK w przypadku usuwania FK). Gdy usunięcia są rzadkie i można je zaplanować poza godzinami pracy. Gdy tabela ma częste aktualizacje wartości odniesienia. Gdy tabela odwołań jest bardzo mała, ale często aktualizowana. Wyjątki zdarzają się na tyle często, że społeczność PostgreSQL woli mieć nad nim kontrolę, zamiast robić to automatycznie.
kgrittn

Odpowiedzi:

218

Jestem prawie pewien, że nie możesz po prostu dodać on delete cascadedo istniejącego ograniczenia klucza obcego. Najpierw musisz usunąć ograniczenie, a następnie dodać poprawną wersję. Uważam, że w standardowym języku SQL najłatwiej to zrobić

  • rozpocząć transakcję,
  • upuść klucz obcy,
  • dodaj klucz obcy za pomocą on delete cascadei na końcu
  • zatwierdzić transakcję

Powtórz te czynności dla każdego klucza obcego, który chcesz zmienić.

Ale PostgreSQL ma niestandardowe rozszerzenie, które umożliwia użycie wielu klauzul ograniczających w pojedynczej instrukcji SQL. Na przykład

alter table public.scores
drop constraint scores_gid_fkey,
add constraint scores_gid_fkey
   foreign key (gid)
   references games(gid)
   on delete cascade;

Jeśli nie znasz nazwy ograniczenia klucza obcego, które chcesz usunąć, możesz sprawdzić je w pgAdminIII (po prostu kliknij nazwę tabeli i spójrz na DDL lub rozwiń hierarchię, aż zobaczysz „Ograniczenia”), lub możesz zapytać o schemat informacji .

select *
from information_schema.key_column_usage
where position_in_unique_constraint is not null
Mike Sherrill `` Cat Recall ''
źródło
Dzięki, tak też myślałem - ale co zrobić z KLUCZAMI OBCYMI? Czy są to tylko ograniczenia (podobne do NOT NULL), które można łatwo usunąć i odczytać?
Alexander Farber,
2
@AlexanderFarber: Tak, są to nazwane ograniczenia, które można łatwo usunąć i dodać. Ale prawdopodobnie chcesz to zrobić w ramach transakcji. Zaktualizowałem moją odpowiedź o więcej szczegółów.
Mike Sherrill `` Cat Recall ''
+1 za wyszukanie w pgAdminIII. Udostępnia nawet polecenia DROP CONSTRAINT i ADD CONSTRAINT, więc możesz po prostu skopiować i wkleić do okna zapytania i edytować polecenie do tego, co chcesz.
Dave Pile
Po napisaniu zapytania zauważyłem, że mój graficzny interfejs użytkownika Postgres (Navicat) pozwala mi zrobić trywialną zmianę z poziomu GUI: dl.dropboxusercontent.com/spa/quq37nq1583x0lf/wwqne-lw.png
danneu
Czy w przypadku dużych tabel jest to możliwe NOT VALIDw przypadku oddzielnej transakcji i walidacja w ramach tej transakcji? Mam na ten temat pytanie bez odpowiedzi .
TheCloudlessSky
11

Na podstawie odpowiedzi @Mike Sherrill Cat Recall, oto, co zadziałało dla mnie:

ALTER TABLE "Children"
DROP CONSTRAINT "Children_parentId_fkey",
ADD CONSTRAINT "Children_parentId_fkey"
  FOREIGN KEY ("parentId")
  REFERENCES "Parent"(id)
  ON DELETE CASCADE;
Kolega nieznajomy
źródło
5

Stosowanie:

select replace_foreign_key('user_rates_posts', 'post_id', 'ON DELETE CASCADE');

Funkcjonować:

CREATE OR REPLACE FUNCTION 
    replace_foreign_key(f_table VARCHAR, f_column VARCHAR, new_options VARCHAR) 
RETURNS VARCHAR
AS $$
DECLARE constraint_name varchar;
DECLARE reftable varchar;
DECLARE refcolumn varchar;
BEGIN

SELECT tc.constraint_name, ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name 
FROM 
    information_schema.table_constraints AS tc 
    JOIN information_schema.key_column_usage AS kcu
      ON tc.constraint_name = kcu.constraint_name
    JOIN information_schema.constraint_column_usage AS ccu
      ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY' 
   AND tc.table_name= f_table AND kcu.column_name= f_column
INTO constraint_name, reftable, refcolumn;

EXECUTE 'alter table ' || f_table || ' drop constraint ' || constraint_name || 
', ADD CONSTRAINT ' || constraint_name || ' FOREIGN KEY (' || f_column || ') ' ||
' REFERENCES ' || reftable || '(' || refcolumn || ') ' || new_options || ';';

RETURN 'Constraint replaced: ' || constraint_name || ' (' || f_table || '.' || f_column ||
 ' -> ' || reftable || '.' || refcolumn || '); New options: ' || new_options;

END;
$$ LANGUAGE plpgsql;

Uwaga: ta funkcja nie skopiuje atrybutów początkowego klucza obcego. Pobiera tylko nazwę obcej tabeli / nazwę kolumny, usuwa bieżący klucz i zastępuje go nowym.

Daniel Garmoshka
źródło