web-dev-qa-db-ja.com

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

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

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

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

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

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

products
- id
- name
- price

categories_products
- categories_id
- products_id
143
Cudos

カスケードされた製品が削除されたカテゴリのメンバーであったためにその製品を削除する場合、外部キーを不適切に設定しています。サンプルテーブルを考えると、次のテーブル設定が必要です。

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;

この方法では、製品ORカテゴリを削除できます。また、categories_productsの関連レコードのみが一緒に削除されます。カスケードはツリーをさらに上に移動せず、親製品/カテゴリテーブルを削除しません。

例えば.

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

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

「赤」のカテゴリを削除すると、カテゴリテーブルの「赤」のエントリと、prod/catsの2つのエントリ「赤のブーツ」と「赤のコート」だけが死にます。

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

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

カスケード削除がどのように機能するかについてはまだ誤解しています。これらは、「削除カスケード」が定義されているテーブルにのみ影響します。この場合、カスケードは「categories_products」テーブルで設定されます。 「赤」カテゴリを削除すると、categoals_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テーブルには外部キーが定義されていないため、カスケードはそこで機能しません。そのため、ブートとミトンがリストされています。 「青いブーツ」や「青いミトン」はもうありません。

368
Marc B

この質問への回答に混乱したため、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
11
Abderrahim

外部キーの制約は、テーブルのデザインを与えられたあなたが望むものを正確に実行しないと思います(確かではありません)。おそらく、最善の方法は、必要な方法でカテゴリを削除するストアドプロシージャを定義し、カテゴリを削除するたびにそのプロシージャを呼び出すことです。

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は削除するカテゴリ)を発行して、カテゴリを削除し、目的の動作を取得できます。ただし、より標準的な動作が必要な場合(つまり、リンクテーブルからのみ削除し、productsテーブルをそのままにする場合)を除き、通常のDELETE FROMクエリを発行しないでください。

8
Hammerite