Як додати обмеження "на каскад видалення"?


163

Чи можливо в PostgreSQL 8 додати ON DELETE CASCADESдо обох зовнішніх ключів у наступній таблиці, не скидаючи останню?

# \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)

Обидві посилаються таблиці нижче - тут:

# \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)

І ось:

# \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)

А також мені цікаво, чи є сенс додати до попередньої таблиці 2 index'es?

ОНОВЛЕННЯ: Дякую, а також я отримав поради у списку розсилки, що я міг би керувати ним у 1 виписці і, таким чином, без явного початку транзакції:

ALTER TABLE public.scores
DROP CONSTRAINT scores_gid_fkey,
ADD CONSTRAINT scores_gid_fkey
   FOREIGN KEY (gid)
   REFERENCES games(gid)
   ON DELETE CASCADE;

1
Трохи ОТ, але зауважую, що ви не створили індекси на посиланнях стовпців (наприклад, pref_scores.gid). Видалення з посиланої таблиці триватиме багато часу, без них, якщо ви отримаєте багато рядків у цих таблицях. Деякі бази даних автоматично створюють індекс на стовпчиках (рефералах); PostgreSQL залишає це вам, оскільки є випадки, коли це не варто.
kgrittn

1
Дякую! Я фактично помітив, що видалення триває багато часу, але не знаю, що це причина
Олександр Фарбер

1
Які б це випадки, коли індекси на зовнішніх ключах не варті?
Олександр Фарбер

2
Я включив ваші висновки у свою відповідь. (Це єдине твердження - це також одна транзакція.)
Майк Шеррілл 'Відкликання кішок'

2
@AlexanderFarber: Коли ви можете опустити індекс у стовпці (іх) ФС? Якщо є інший не точний збіг, який буде працювати досить добре (наприклад, у вас може бути індекс триграми для частих пошуків подібності, який також буде OK для видалення FK). Коли вилучення нечасті і їх можна запланувати в неробочий час. Коли таблиця має часті оновлення значення посилань. Коли таблиця посилань дуже мала, але часто оновлюється. Винятки трапляються досить часто, що спільнота PostgreSQL вважає за краще мати контроль над нею, а не робити автоматичним.
kgrittn

Відповіді:


218

Я впевнений, що ви не можете просто додати on delete cascadeіснуюче обмеження в зовнішньому ключі. Спочатку потрібно скинути обмеження, а потім додати правильну версію. У стандартному SQL, я вважаю, найпростіший спосіб зробити це

  • розпочати транзакцію,
  • скиньте іноземний ключ,
  • додайте іноземний ключ за допомогою on delete cascadeі, нарешті,
  • здійснити транзакцію

Повторіть для кожного зовнішнього ключа, який потрібно змінити.

Але PostgreSQL має нестандартне розширення, яке дозволяє використовувати кілька пропозицій обмежень в одному операторі SQL. Наприклад

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

Якщо ви не знаєте імені обмеження для зовнішнього ключа, яке ви хочете скинути, ви можете або шукати його в pgAdminIII (просто натисніть ім'я таблиці і подивіться на DDL, або розгорніть ієрархію, поки не з’явиться "Обмеження"), або ви можете запитувати інформаційну схему .

select *
from information_schema.key_column_usage
where position_in_unique_constraint is not null

Дякую, це я теж думав - але що робити з ІНШИМИ КЛЮЧАМИ? Це просто обмеження (схожі на NOT NULL), які можна легко скасувати та перечитати?
Олександр Фарбер

2
@AlexanderFarber: Так, вони названі обмеженнями, які ви можете легко скидати та додавати. Але ви, мабуть, хочете зробити це в рамках транзакції. Оновив мою відповідь більш докладно.
Майк Шеррілл 'Відкликання котів'

+1 для пошуку в pgAdminIII. Він навіть дає команди DROP CONSTRAINT та ADD CONSTRAINT, тож ви можете просто скопіювати та вставити у вікно запиту та відредагувати команду на потрібне.
Дейв Пайл

Після написання запиту я помітив мій графічний графічний інтерфейс Postgres (Navicat), давайте мені тривіально зробити цю зміну з GUI: dl.dropboxusercontent.com/spa/quq37nq1583x0lf/wwqne-lw.png
danneu

Для великих таблиць це можливо за допомогою NOT VALIDта підтвердженням окремої транзакції? У мене на це питання без відповіді .
TheCloudlessSky

11

Виходячи з відповіді @Mike Шеррілл Cat Recall, ось що працювало для мене:

ALTER TABLE "Children"
DROP CONSTRAINT "Children_parentId_fkey",
ADD CONSTRAINT "Children_parentId_fkey"
  FOREIGN KEY ("parentId")
  REFERENCES "Parent"(id)
  ON DELETE CASCADE;

5

Використання:

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

Функція:

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;

Будьте в курсі: ця функція не копіює атрибути початкового іноземного ключа. Він бере лише ім'я стороннього столу / ім'я стовпця, скидає поточний ключ і замінює новий.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.