INSERT IGNORE を使うタイミングと正しい記述

前提

該当のソースコードのように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 コメント