MySQL外部キー制約、カスケード削除


158

外部キーを使用して整合性を保ち、孤立を回避したい(私はすでにinnoDBを使用している)。

DELETE ON CASCADEというSQLステートメントを作成するにはどうすればよいですか?

カテゴリを削除した場合、他のカテゴリに関連する製品も削除されないようにする方法を教えてください。

ピボットテーブル「categories_products」は、他の2つのテーブル間に多対多の関係を作成します。

categories
- id (INT)
- name (VARCHAR 255)

products
- id
- name
- price

categories_products
- categories_id
- products_id

こんにちは-あなたは質問のタイトルを変更したいかもしれません、それは具体的にピボットテーブルではなく、本当にカスケード削除についてです。
Paddyslacker

回答:


386

強制終了されたカテゴリのメンバーであったために、カスケードによって製品が削除された場合、外部キーが正しく設定されていません。テーブルの例を考えると、次のテーブル設定が必要です。

CREATE TABLE categories (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
)Engine=InnoDB;

CREATE TABLE products (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
)Engine=InnoDB;

CREATE TABLE categories_products (
    category_id int unsigned not null,
    product_id int unsigned not null,
    PRIMARY KEY (category_id, product_id),
    KEY pkey (product_id),
    FOREIGN KEY (category_id) REFERENCES categories (id)
       ON DELETE CASCADE
       ON UPDATE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products (id)
       ON DELETE CASCADE
       ON UPDATE CASCADE
)Engine=InnoDB;

このように、製品またはカテゴリを削除すると、category_productsの関連するレコードのみが削除されます。カスケードはツリーの上位に移動し、親の製品/カテゴリテーブルを削除しません。

例えば

products: boots, mittens, hats, coats
categories: red, green, blue, white, black

prod/cats: red boots, green mittens, red coats, black hats

「red」カテゴリを削除すると、カテゴリテーブルの「red」エントリと、prod / catsという2つのエントリ「red boots」と「redcoats」のみが無効になります。

削除はそれ以上カスケードされず、「ブーツ」および「コート」のカテゴリは削除されません。

コメントのフォローアップ:

カスケード削除がどのように機能するかについては、まだ誤解しています。「削除時のカスケード」が定義されているテーブルにのみ影響します。この場合、カスケードは「categories_products」テーブルで設定されます。「red」カテゴリを削除した場合、category_productsでカスケード削除されるのは、次のレコードのみcategory_id = redです。'category_id = blue'のレコードには触れず、 "products"テーブルに移動しません。そのテーブルには外部キーが定義されていないためです。

以下に、より具体的な例を示します。

categories:     products:
+----+------+   +----+---------+
| id | name |   | id | name    |
+----+------+   +----+---------+
| 1  | red  |   | 1  | mittens |
| 2  | blue |   | 2  | boots   |
+---++------+   +----+---------+

products_categories:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1          | 1           | // red mittens
| 1          | 2           | // blue mittens
| 2          | 1           | // red boots
| 2          | 2           | // blue boots
+------------+-------------+

カテゴリ#2(青)を削除するとします。

DELETE FROM categories WHERE (id = 2);

DBMSは、「categories」テーブルを指す外部キーを持つすべてのテーブルを調べ、一致するIDが2であるレコードを削除します。外部キー関係をで定義しただけなのでproducts_categories、このテーブルは、削除完了:

+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1          | 1           | // red mittens
| 2          | 1           | // red boots
+------------+-------------+

productsテーブルには外部キーが定義されていないため、カスケードは機能しません。したがって、ブーツとミトンはリストされています。「青いブーツ」や「青いミトン」はもうありません。


質問は間違った方法で書いたと思います。カテゴリを削除した場合、他のカテゴリに関連する製品も削除されないようにする方法を教えてください。
10

36
これは本当に素晴らしく、非常に目立ち、見事に図解された答えです。お時間を割いていただき、ありがとうございます。
scottb

2
テーブルを作成するときは、CASCADE操作が可能なInnoDBまたは別のMySQLエンジンを指定する必要があります。そうでない場合、MySQLのデフォルトであるMyISAMが使用され、MyISAMはCASCADE操作をサポートしません。これを行うENGINE InnoDBには、最後のの前に追加し;ます。
Patrick 14

11

この質問の答えに戸惑いました。そこでMySQLでテストケースを作成しました。

-- Schema
CREATE TABLE T1 (
    `ID` int not null auto_increment,
    `Label` varchar(50),
    primary key (`ID`)
);

CREATE TABLE T2 (
    `ID` int not null auto_increment,
    `Label` varchar(50),
    primary key (`ID`)
);

CREATE TABLE TT (
    `IDT1` int not null,
    `IDT2` int not null,
    primary key (`IDT1`,`IDT2`)
);

ALTER TABLE `TT`
    ADD CONSTRAINT `fk_tt_t1` FOREIGN KEY (`IDT1`) REFERENCES `T1`(`ID`) ON DELETE CASCADE,
    ADD CONSTRAINT `fk_tt_t2` FOREIGN KEY (`IDT2`) REFERENCES `T2`(`ID`) ON DELETE CASCADE;

-- Data
INSERT INTO `T1` (`Label`) VALUES ('T1V1'),('T1V2'),('T1V3'),('T1V4');
INSERT INTO `T2` (`Label`) VALUES ('T2V1'),('T2V2'),('T2V3'),('T2V4');
INSERT INTO `TT` (`IDT1`,`IDT2`) VALUES
(1,1),(1,2),(1,3),(1,4),
(2,1),(2,2),(2,3),(2,4),
(3,1),(3,2),(3,3),(3,4),
(4,1),(4,2),(4,3),(4,4);

-- Delete
DELETE FROM `T2` WHERE `ID`=4; -- Delete one field, all the associated fields on tt, will be deleted, no change in T1
TRUNCATE `T2`; -- Can't truncate a table with a referenced field
DELETE FROM `T2`; -- This will do the job, delete all fields from T2, and all associations from TT, no change in T1

8

外部キー制約は、テーブルの設計で望んでいることを正確には実行しないと思います(確信はありません)。おそらく、最善の方法は、必要に応じてカテゴリを削除するストアドプロシージャを定義し、カテゴリを削除するときにいつでもそのプロシージャを呼び出すことです。

CREATE PROCEDURE `DeleteCategory` (IN category_ID INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN

DELETE FROM
    `products`
WHERE
    `id` IN (
        SELECT `products_id`
        FROM `categories_products`
        WHERE `categories_id` = category_ID
    )
;

DELETE FROM `categories`
WHERE `id` = category_ID;

END

また、リンクテーブルに次の外部キー制約を追加する必要があります。

ALTER TABLE `categories_products` ADD
    CONSTRAINT `Constr_categoriesproducts_categories_fk`
    FOREIGN KEY `categories_fk` (`categories_id`) REFERENCES `categories` (`id`)
    ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `Constr_categoriesproducts_products_fk`
    FOREIGN KEY `products_fk` (`products_id`) REFERENCES `products` (`id`)
    ON DELETE CASCADE ON UPDATE CASCADE

もちろん、CONSTRAINT句はCREATE TABLEステートメントでも使用できます。

これらのスキーマオブジェクトを作成したら、カテゴリを削除し、発行することで必要な動作を取得できますCALL DeleteCategory(category_ID)(category_IDは削除するカテゴリです)。これにより、意図したとおりに動作します。ただしDELETE FROM、より標準的な動作が必要でない限り(つまり、リンクテーブルからのみ削除し、テーブルはそのままにしておくproducts)、通常のクエリを発行しないでください。


質問は間違った方法で書いたと思います。カテゴリを削除した場合、他のカテゴリに関連する製品も削除されないようにする方法を教えてください。
10

その場合は問題ありません。マークBの答えはあなたの望みどおりのものだと思います。
Hammerite、

こんにちは@Hammerite です。受け入れられた回答のKEY pkey (product_id),3番目のCREATE TABLEクエリの意味を教えていただけますか?
Siraj Alam
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.