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