114

在PostgreSQL 8中,是否可以在下表中添加「刪除級聯」到兩個外鍵而不丟棄後者?如何添加「刪除級聯」約束?

# \d pref_scores 
     Table "public.pref_scores" 
Column |   Type   | Modifiers 
---------+-----------------------+----------- 
id  | character varying(32) | 
gid  | integer    | 
money | integer    | not null 
quit | boolean    | 
last_ip | inet     | 
Foreign-key constraints: 
    "pref_scores_gid_fkey" FOREIGN KEY (gid) REFERENCES pref_games(gid) 
    "pref_scores_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 

兩個引用的表都低於 - 在這裏:

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

在這裏:

# \d pref_users 
       Table "public.pref_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: 
    "pref_users_pkey" PRIMARY KEY, btree (id) 
Referenced by: 
    TABLE "pref_cards" CONSTRAINT "pref_cards_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_catch" CONSTRAINT "pref_catch_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_chat" CONSTRAINT "pref_chat_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_game" CONSTRAINT "pref_game_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_hand" CONSTRAINT "pref_hand_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_luck" CONSTRAINT "pref_luck_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_match" CONSTRAINT "pref_match_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_misere" CONSTRAINT "pref_misere_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_money" CONSTRAINT "pref_money_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_pass" CONSTRAINT "pref_pass_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_payment" CONSTRAINT "pref_payment_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_rep" CONSTRAINT "pref_rep_author_fkey" FOREIGN KEY (author) REFERENCES pref_users(id) 
    TABLE "pref_rep" CONSTRAINT "pref_rep_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_scores" CONSTRAINT "pref_scores_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 
    TABLE "pref_status" CONSTRAINT "pref_status_id_fkey" FOREIGN KEY (id) REFERENCES pref_users(id) 

而且也不知是否是有意義的2個index'es添加到舊錶?

UPDATE:謝謝,我也得在郵件列表的建議,我可以在1條語句進行管理,因而沒有必要交易:

ALTER TABLE public.pref_scores 
DROP CONSTRAINT pref_scores_gid_fkey, 
ADD CONSTRAINT pref_scores_gid_fkey 
    FOREIGN KEY (gid) 
    REFERENCES pref_games(gid) 
    ON DELETE CASCADE; 
+1

有點OT,但我注意到你還沒有在引用列上創建索引(例如'pref_scores.gid')。如果在這些表中有很多行,那麼在引用的表上刪除將需要很長時間。有些數據庫會自動在引用列上創建一個索引; PostgreSQL留給你,因爲有些情況下它是不值得的。 – kgrittn 2012-04-28 01:16:36

+1

謝謝!我實際上注意到刪除需要很長時間,但不知道這就是原因 – 2012-04-28 08:57:52

+1

當外鍵索引不值得時,會出現哪些情況? – 2012-04-28 09:00:17

回答

144

我敢確定你不能簡單地將on delete cascade添加到現有的外鍵約束。您必須先刪除約束,然後添加正確的版本。在標準的SQL,我認爲這樣做最簡單的方法就是

  • 啓動一個事務,
  • 刪除外鍵,
  • 添加一個外鍵與on delete cascade,終於
  • 提交事務

針對要更改的每個外鍵重複該步驟。

但是PostgreSQL有一個非標準的擴展,可以讓你在單個SQL語句中使用多個約束子句。例如

alter table public.pref_scores 
drop constraint pref_scores_gid_fkey, 
add constraint pref_scores_gid_fkey 
    foreign key (gid) 
    references pref_games(gid) 
    on delete cascade; 

如果你不知道你要刪除的外鍵約束的名稱,你可以去查pgAdminIII(只需點擊表名,並期待在DDL,或擴大直到您看到「約束條件」),或者您可以query the information schema

select * 
from information_schema.key_column_usage 
where position_in_unique_constraint is not null 
+0

謝謝,那也是我的想法 - 但如何處理FOREIGN KEY?它們只是簡單的約束(類似於NOT NULL),可以輕鬆地刪除和重寫嗎? – 2012-04-27 19:37:47

+2

@AlexanderFarber:是的,它們被命名爲約束條件,您可以輕鬆地刪除和添加。但是你可能想在交易中這樣做。更詳細地更新了我的答案。 – 2012-04-27 19:53:04

+0

+1用於在pgAdminIII中查找。它甚至爲您提供了DROP CONSTRAINT和ADD CONSTRAINT命令,因此您可以將其複製並粘貼到查詢窗口中,然後將命令編輯爲所需內容。 – 2015-04-21 03:45:46

3

用法:

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; 

注意:此功能不會複製初始外鍵的屬性。 它只需要外國名稱/列名稱,丟棄當前密鑰,並用新的代替