前提
該当のソースコードのように3つのテーブルがあります。
my_fruits : フルーツテーブル
my_tags : タグテーブル
my_tag_holders : リレーションテーブル
そして my_tag_holders の主キー(34行目)によって 「フルーツとタグの同じ組み合わせはない」 という制限をしています。
発生している問題
「フルーツとタグの同じ組み合わせはない」 という制限のために INSERT IGNORE を使うつもりですが、そのタイミングと正しい記述がわかりません。
該当のソースコード
SQL
1-- フルーツテーブル 2CREATE TABLE my_fruits ( 3 `ID` int AUTO_INCREMENT, 4 `count_likes` int(100) DEFAULT 0, 5 `content` varchar(100) not null, 6 PRIMARY KEY(`ID`)); 7 8INSERT INTO my_fruits (`count_likes`, `content`) 9VALUES 10(10, 'フルーツ1'), 11(56, 'フルーツ2'), 12(36, 'フルーツ3'); 13 14-- タグテーブル 15CREATE TABLE my_tags ( 16 `ID` int AUTO_INCREMENT, 17 `tag_kind_id` int, 18 `tag_name` varchar(100), 19 PRIMARY KEY(`ID`)); 20 21INSERT INTO my_tags 22(`tag_kind_id`, `tag_name`) 23VALUES 24(1, '赤'), 25(1, '青'), 26(1, '緑'), 27(1, '黄'), 28(1, '白'); 29 30-- リレーションテーブル 31CREATE TABLE my_tag_holders ( 32 `fruits_ID` int, 33 `tags_ID` int, 34 PRIMARY KEY (`tags_ID`, `fruits_ID`), #「フルーツとタグの同じ組み合わせはない」という制限 35 CONSTRAINT fk_01 FOREIGN KEY (`fruits_ID`) REFERENCES my_fruits(`ID`), 36 CONSTRAINT fk_02 FOREIGN KEY (`tags_ID`) REFERENCES my_tags(`ID`) ); 37 38INSERT INTO my_tag_holders 39(`fruits_ID`, `tags_ID`) 40VALUES 41(1, 1),(1, 2), 42(2, 2),(2, 4),(2, 5), 43(3, 3); 44 45-- タグテーブルにユニーク制約を追加( sazi様からのご指摘 2022/07/31 20:54 を受けての追加 ) 46ALTER TABLE my_tags ADD CONSTRAINT UNIQUE u_01 (`tag_kind_id`, `tag_name`);
具体例とご質問
例えばあるユーザーが次の値をPOSTするという例で考えますと、下の方は 先述の制限 によってエラーとなります。(既にフルーツ3の緑はINSERT済み)
php
1[ 2 // これは投稿できる 3 [ 'content'=>'フルーツ3', 'tag_name'=>'赤' ], 4 // これは「フルーツとタグの同じ組み合わせはない」という制限によりエラー 5 [ 'content'=>'フルーツ3', 'tag_name'=>'緑' ] 6]
しかし当然エラーによって同じ組み合わせを防ぐのではなく「既存確認」の処理を挟むべきだろうと考えているのですが、では「既存確認」はどのように書いたらよろしいでしょうか?
INSERT IGNORE を使うのかと想像はできるのですが、テーブルが3つにまたがっているせいで書き方がわかりません。
もしテーブルが1つならば次のようにすればいいと思いますが…
SQL
1INSERT IGNORE INTO my_fruits 2(`content`, `tag_name` ) 3VALUES 4('フルーツ3', '赤'), 5('フルーツ3', '緑');
…3つの場合はどうすればいいのでしょうか?
自分で考えられる方法1
自分で考えられるのは2つの方法です。
まず3つのテーブルの INSERT より前に、SELECT で「既存確認」を入れるという方法です。
php
1// SELECT で「既存確認」を入れる 2$sth = $dbh->(" 3SELECT * 4FROM my_fruits F 5LEFT JOIN my_tag_holders TH ON TH.fruits_ID = F.ID 6LEFT JOIN my_tags T ON T.ID = TH.tags_ID 7WHERE F.content = $content 8AND T.tag_name = $tag_name 9;"); 10$count = $sth->rowCount(); 11 12// フルーツとタグの同じ組み合わせがあればdieで中断(質問のための簡易表現) 13if ( $count > 0 ) 14die( 'フルーツとタグの同じ組み合わせが既にあります' ); 15 16// my_fruits を INSERT して、$fruits_id を得る 17$dbh->query(" 18INSERT INTO my_fruits (`content`) 19VALUES ('$content') 20";); 21$fruits_id = $dbh->lastInsertId(); 22 23// my_tags を INSERT して、$tag_id を得る 24$dbh->query(" 25INSERT INTO my_tags (`tag_name`) 26VALUES ('$tag_name') 27";); 28$tag_id = $dbh->lastInsertId(); 29 30// my_tag_holders を INSERT する 31$dbh->query(" 32INSERT INTO my_tag_holders (`tags_ID`, `fruits_ID`) 33VALUES ( $tag_id, $fruits_id ) 34";);
しかしこの方法は、整合性を保つために「既存確認」の前にLOCK TABLES
が必要となり負荷が懸念されます。
自分で考えられる方法2
続いて考えれる方法としては、上記のような SELECT での「既存確認」はせず、最後の my_tag_holders への INSERT の際に INSERT IGNORE を使って「既存確認」をするというものです。 最後に ROW_COUNT()
して0ならロールバックし整合性を保つつもりです。
php
1// my_fruits を INSERT して、$fruits_id を得る 2$dbh->query(" 3INSERT INTO my_fruits (`content`) 4VALUES ('$content') 5";); 6$fruits_id = $dbh->lastInsertId(); 7 8// my_tags を INSERT して、$tag_id を得る 9$dbh->query(" 10INSERT INTO my_tags (`tag_name`) 11VALUES ('$tag_name') 12";); 13$tag_id = $dbh->lastInsertId(); 14 15// my_tag_holders を INSERT IGNORE を使って「既存確認」してから INSERT する 16$dbh->query(" 17INSERT IGNORE INTO my_tag_holders (`tags_ID`, `fruits_ID`) 18VALUES ( $tag_id, $fruits_id ) 19";);
しかしこの方法は、失敗を前提としているような印象で違和感を覚えます。私がテーブルが1つの場合の INSERT IGNORE しか知らないためにこの方法に至ったのですが、もし3つのテーブルについて INSERT IGNORE ができれば、my_fruits を INSERT するより以前に「既存確認」ができるので、この違和感もなくなるのではと思いました。
改めまして、INSERT IGNORE のタイミングと正しい記述についてご教示頂けませんでしょうか。
補足情報(FW/ツールのバージョンなど)
それぞれ次のバージョンです。
PHP 8.0
MySQL 5.7.2
宜しくお願い致します。
(尚インジェクションリスクがある点はスルーしてください。)

回答1件
あなたの回答
tips
プレビュー