Bagaimana cara menambahkan batasan “pada penghapusan kaskade”?

163

Dalam PostgreSQL 8, apakah mungkin menambahkan ON DELETE CASCADESkedua kunci asing di tabel berikut tanpa menjatuhkan yang terakhir?

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

Kedua tabel yang direferensikan di bawah - di sini:

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

Dan di sini:

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

Dan juga saya bertanya-tanya apakah masuk akal untuk menambahkan 2 index'es ke tabel sebelumnya?

UPDATE: Terima kasih, dan saya juga sudah mendapat saran di milis, bahwa saya bisa mengelolanya dalam 1 pernyataan dan karenanya tanpa secara eksplisit memulai transaksi:

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
sumber
1
Sedikit PL, tetapi saya perhatikan bahwa Anda belum membuat indeks pada kolom referensi (misalnya, pref_scores.gid). Menghapus tabel yang direferensikan akan memakan waktu lama tanpa itu, jika Anda mendapatkan banyak baris di tabel itu. Beberapa database secara otomatis membuat indeks pada kolom referensi; PostgreSQL menyerahkannya kepada Anda, karena ada beberapa kasus yang tidak bermanfaat.
kgrittn
1
Terima kasih! Saya benar-benar memperhatikan bahwa penghapusan itu memakan waktu lama, tetapi tidak tahu itu alasannya
Alexander Farber
1
Kasus apa yang akan terjadi, ketika indeks pada kunci asing tidak bermanfaat?
Alexander Farber
2
Saya memasukkan temuan Anda ke dalam jawaban saya. (Pernyataan tunggal itu juga merupakan satu transaksi.)
Mike Sherrill 'Cat Recall'
2
@AlexanderFarber: Kapan Anda ingin menghilangkan indeks pada kolom referensi FK? Ketika ada indeks lain yang bukan pencocokan tepat yang akan bekerja dengan cukup baik (misalnya, Anda mungkin memiliki indeks trigram untuk pencarian kesamaan yang sering, yang juga akan OK untuk menghapus FK). Ketika penghapusan jarang terjadi dan dapat dijadwalkan di luar jam kerja. Ketika tabel memiliki pembaruan yang sering dari nilai referensi. Ketika tabel referensi sangat kecil tetapi sering diperbarui. Pengecualian terjadi cukup sering sehingga komunitas PostgreSQL lebih memilih untuk mengendalikannya daripada membuatnya otomatis.
kgrittn

Jawaban:

218

Saya cukup yakin Anda tidak bisa hanya menambah on delete cascadebatasan kunci asing yang ada. Anda harus membuang kendala terlebih dahulu, lalu menambahkan versi yang benar. Dalam SQL standar, saya percaya cara termudah untuk melakukan ini adalah

  • memulai transaksi,
  • jatuhkan kunci asing,
  • tambahkan kunci asing dengan on delete cascade, dan akhirnya
  • melakukan transaksi

Ulangi untuk setiap kunci asing yang ingin Anda ubah.

Tetapi PostgreSQL memiliki ekstensi non-standar yang memungkinkan Anda menggunakan beberapa klausa kendala dalam satu pernyataan SQL. Sebagai contoh

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

Jika Anda tidak tahu nama batasan kunci asing yang ingin Anda jatuhkan, Anda dapat mencarinya di pgAdminIII (cukup klik nama tabel dan lihat DDL, atau perluas hierarki hingga Anda melihat "Kendala"), atau Anda dapat menanyakan skema informasi .

select *
from information_schema.key_column_usage
where position_in_unique_constraint is not null
Mike Sherrill 'Cat Recall'
sumber
Terima kasih, itu yang saya pikirkan juga - tapi apa yang harus dilakukan dengan KUNCI LUAR NEGERI? Apakah mereka hanya kendala (mirip dengan NOT NULL) yang dapat dijatuhkan dan dibaca dengan mudah?
Alexander Farber
2
@AlexanderFarber: Ya, itu adalah batasan yang bisa Anda jatuhkan dan tambahkan dengan mudah. Tetapi Anda mungkin ingin melakukan itu dalam suatu transaksi. Memperbarui jawaban saya dengan lebih detail.
Mike Sherrill 'Cat Recall'
+1 untuk mencari di pgAdminIII. Bahkan memberi Anda perintah DROP CONSTRAINT dan ADD CONSTRAINT, sehingga Anda bisa menyalin dan menempelkan ke jendela permintaan dan mengedit perintah untuk apa yang Anda inginkan.
Dave Pile
Setelah menulis kueri, saya perhatikan Postgres GUI (Navicat) saya, biarkan saya melakukan perubahan sepele dari dalam GUI: dl.dropboxusercontent.com/spa/quq37nq1583x0lf/wwqne-lw.png
danneu
Untuk tabel besar, apakah ini mungkin dengan NOT VALIDdan memvalidasi dalam transaksi terpisah? Saya punya pertanyaan yang belum terjawab tentang ini.
TheCloudlessSky
11

Didasarkan pada jawaban @Mike Sherrill Cat Recall, inilah yang bekerja untuk saya:

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

Pemakaian:

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

Fungsi:

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;

Sadarilah: fungsi ini tidak akan menyalin atribut kunci asing awal. Hanya membutuhkan nama tabel / nama kolom asing, menjatuhkan kunci saat ini dan menggantikannya dengan yang baru.

Daniel Garmoshka
sumber