質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

2回答

618閲覧

MySQLで「タグ同士の関連性」をデータベースに持たせる方法

nikuatsu

総合スコア177

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

0クリップ

投稿2022/08/18 12:21

編集2022/08/19 22:03

前提

phpとMySQLでユーザーがタグを付けてコメントを投稿するシステムを作りました。

今回は 「指定タグの関連タグを取得する目的」 で、「タグ同士の関連性」テーブルを作ろうとしています。

実現したいこと

「タグ同士の関連性」テーブルにおいて、適切なプランを知りたいです。

発生している問題

下記プランA、Bを考えていますが、経験が浅くどちらを採用すべきか判断ができません。
どういうケースでどういうメリット・デメリットがあるのか、アドバイスを頂きたく思っております。

「コメントとタグ」テーブル(決定)

まず「コメントとタグ」は3つのテーブルが担い、次のstepでそれぞれへINSERTされます。

step1. ユーザーがコメントにタグをつけてPOSTする。
step2. コメントが tbl_comments へINSERTされる。
step3. タグは tbl_tags へINSERTされる。
step4. タグとコメントの紐付けは tbl_tag_holders へINSERTされる。

SQL

1-- コメント 2CREATE TABLE tbl_comments ( 3 `ID` int AUTO_INCREMENT, 4 `comment` varchar(100) not null, 5 PRIMARY KEY(`ID`), 6 INDEX idx_comments_01 (`comment`)); 7 8INSERT INTO tbl_comments (`ID`,`comment`) 9VALUES 10(1, 'こんにちは'), 11(2, 'ありがとう'), 12(3, 'さようなら'); 13 14-- タグ 15CREATE TABLE tbl_tags ( 16 `ID` int AUTO_INCREMENT, 17 `tag_kind_id` int, 18 `tag_name` varchar(100), 19 PRIMARY KEY(`ID`), 20 UNIQUE u_tags_01 (`tag_kind_id`, `tag_name`)); 21 22INSERT INTO tbl_tags (`ID`, `tag_kind_id`, `tag_name`) 23VALUES -- tag_kind_id は「本、著者、ジャンル」の3種類があります。 24(1, 1, '吾輩は猫である'), 25(2, 2, '夏目漱石'), 26(3, 3, '恋'), 27(4, 3, '人生'), 28(5, 1, '人間失格'), 29(6, 2, '太宰治'), 30(7, 3, '罪'); 31 32-- リレーション 33CREATE TABLE tbl_tag_holders ( 34 `comments_ID` int, 35 `tags_ID` int, 36 PRIMARY KEY (`comments_ID`, `tags_ID`), 37 INDEX idx_tag_holders_01 (`tags_ID`, `comments_ID`), 38 CONSTRAINT fk_tag_holders_01 FOREIGN KEY (`comments_ID`) REFERENCES tbl_comments(`ID`), 39 CONSTRAINT fk_tag_holders_02 FOREIGN KEY (`tags_ID`) REFERENCES tbl_tags(`ID`) ); 40 41INSERT INTO tbl_tag_holders 42(`comments_ID`, `tags_ID`) 43VALUES 44(1, 1),(1, 2),(1, 3),(1, 4), 45(2, 5),(2, 6),(2, 7), 46(3, 1),(3, 2),(3, 4);

そしてこれに続くstepとして

step5. タグ同士に「タグ同士の関連性」を持たせるべく、tbl_tag_relations へINSERTされる。

があるわで、ここにおいて以下プランで悩んでいる次第です。

蛇足

質問と直接関係ありませんが、例えば 'さようなら' のコメントを次のように検索します。

SQL

1SELECT C.ID comment_id, 2 GROUP_CONCAT( T.tag_name ) tag_names 3FROM tbl_comments C 4 LEFT JOIN tbl_tag_holders TH ON TH.comments_ID = C.ID 5 LEFT JOIN tbl_tags T ON T.ID = TH.tags_ID 6WHERE C.comment = 'さようなら' 7GROUP BY comment_id;

閑話休題。

「タグ同士の関連性」テーブル(プランA)

まず一方向に関連性を持たせ、tag_uniqueカラムでユニーク性を持たせるプランです。

SQL

1-- タグ同士の関連性 2CREATE TABLE tbl_tag_relations ( 3 `tags_ID1` int, 4 `tags_ID2` int, 5 `tag_unique` varchar(100) as (concat(least(`tags_ID1`,`tags_ID2`),',',greatest(`tags_ID1`,`tags_ID2`))), 6 `count_relations` int not null default 1, 7 CONSTRAINT fk_tag_relations_01 FOREIGN KEY (`tags_ID1`) REFERENCES tbl_tags(`ID`), 8 CONSTRAINT fk_tag_relations_02 FOREIGN KEY (`tags_ID2`) REFERENCES tbl_tags(`ID`) );

このプランAですと、例えばタグを3つ持っている 'さようなら' のコメントは、次の3レコードで済みます。

SQL

1INSERT INTO tbl_tag_relations 2(`tags_ID1`, `tags_ID2`) 3VALUES 4(1, 3),(2, 3),(3, 4);

「タグ同士の関連性」テーブル(プランB)

続いて双方向の関連性を持たせるプランです。

SQL

1-- タグ同士の関連性 2CREATE TABLE tbl_tag_relations ( 3 `tags_ID1` int, 4 `tags_ID2` int, 5 `count_relations` int not null default 1, 6 CONSTRAINT fk_tag_relations_01 FOREIGN KEY (`tags_ID1`) REFERENCES tbl_tags(`ID`), 7 CONSTRAINT fk_tag_relations_02 FOREIGN KEY (`tags_ID2`) REFERENCES tbl_tags(`ID`) );

このプランBですと、'さようなら' のコメントは次の6レコードが必要となります。

SQL

1INSERT INTO tbl_tag_relations 2(`tags_ID1`, `tags_ID2`) 3VALUES 4(1, 3),(3, 1),(2, 3),(3, 2),(3, 4),(4, 3);

メリット・デメリット

上記プランA、Bについて 「指定タグの関連タグを取得する目的」 を基に愚見を申しますと、

まず 'さようなら' のコメントの例で述べたように、プランAの方がtbl_tag_relationsのレコード数が少なくて済む点で良いのかなと思います。

他方SELECTに目を向けてみますと、例えば $target_tag_id=4 と関連するタグを取得するにあたっては、次のコードの違いがあるように思われます。

すなわち、プランAは tags_ID1 と tags_ID2 のそれぞれにWHEREをかけないといけませんが、プランBは tags_ID1 に対してだけで済みますので、この点ではBが良いいのかなと思います。

SQL

1-- 指定タグの関連タグを取得する(プランA) 2SELECT * 3FROM ( 4 SELECT * 5 FROM tbl_tag_relations TR 6 LEFT JOIN tbl_tags T 7 ON T.ID = TR.tags_ID1 8 9 -- tags_ID1 にWHERE 10 WHERE TR.tags_ID1 = $target_tag_id 11 12 GROUP BY TR.count_relations 13 ORDER BY TR.count_relations DESC 14 LIMIT 10 15) UNION ( 16 SELECT * 17 FROM tbl_tag_relations TR 18 LEFT JOIN tbl_tags T 19 ON T.ID = TR.tags_ID2 20 21 -- tags_ID2 にWHERE 22 WHERE TR.tags_ID2 = $target_tag_id 23 24 GROUP BY TR.count_relations 25 ORDER BY TR.count_relations DESC 26 LIMIT 10 27) 28LIMIT 10

SQL

1-- 指定タグの関連タグを取得する(プランB) 2SELECT * 3FROM tbl_tag_relations TR 4 LEFT JOIN tbl_tags T 5 ON T.ID = TR.tags_ID1 6 7-- tags_ID1 に対してだけで済みます 8WHERE TR.tags_ID1 = $target_tag_id 9 10GROUP BY TR.count_relations 11ORDER BY TR.count_relations DESC 12LIMIT 10

このようにメリット・デメリットがよく掴み切れず、どちらが良いのかイマイチ整理がつきません。

ツールのバージョンなど

それぞれ次のバージョンです。

PHP 8.0
MySQL 5.7.2

宜しくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yambejp

2022/08/19 00:32 編集

いまいち理解ができてないのですが tbl_tag_relationsのA案は単純にtbl_tag_holders のことではないでしょうか? あ、ちがうか。コメントを無視して任意に特定タグ同士を関連付けるのでしょうか その場合、1と3、2と3が関連付けられているのに1と2は関連しなくてよいということ?
nikuatsu

2022/08/19 22:03

> tbl_tag_relationsのA案は単純にtbl_tag_holders のことではないでしょうか たしかに tbl_tag_holders から導くことができる値だとは思います。しかし例えば「tag_id=4と関連性の高いタグを順で(count_relations DESC 順で)取得しよう」となったとき tbl_tag_holders に対しどうSELECTをかけたらいいのか…かなり大変そうに思えます。 あるタグページを閲覧した際に「そのタグと関連性の高いタグはこちら」と表示したいと思っていて、その際のSELECTをかけるのがやりやすいかと思い、tbl_tag_relations を作りました。 > コメントを無視して任意に特定タグ同士を関連付けるのでしょうか いえ。その機能は考えておらず、(というか)タグ同士を関連付ける適当な方法が思いつかず、ひとまず次のルールにしてみました。 「step1でコメントに付けられたタグが、その場合のみタグ同士の関連性を持つ」です。 > その場合、1と3、2と3が関連付けられているのに1と2は関連しなくてよいということ? はい、上述のルールに従い、1と2は関連させません。あるコメントに1と2が付けられた場合のみ、1と2は関連させるつもりです。
guest

回答2

0

ベストアンサー

認識が違ったらすみません。2個のtag同士を限定的に関連性をもたせるならこんな感じとか

SQL

1create table tbl_tag_relations( 2relation_id int not null, 3num enum('1','2') not null, 4tag_id int, 5primary key(relation_id,num), 6unique(relation_id,tag_id)); 7 8insert into tbl_tag_relations values 9(1,'1',1),(1,'2',3),(2,'1',2),(2,'2',3),(3,'1',3),(3,'2',4);

※テーブルのカラム設定とデータ投入を調整しました

テスト

以下でテストしてみて

SQL

1select * from tbl_tag_relations as t1 2inner join tbl_tag_relations as t2 3on t1.relation_id=t2.relation_id 4and t1.num<>t2.num

こうなりますか?

relation_idnumtag_idrelation_idnumtag_id
111123
123111
212223
223212
313324
324313

検索

その上で、1と関連するtag_idを検索

SQL

1select t1.tag_id from tbl_tag_relations as t1 2inner join tbl_tag_relations as t2 3on t1.relation_id=t2.relation_id 4and t1.num<>t2.num 5and t2.tag_id=1;
tag_id
3

投稿2022/08/19 00:59

編集2022/08/22 08:09
yambejp

総合スコア114839

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nikuatsu

2022/08/21 02:55 編集

ありがとうございます。 誠に恐れ入りますが、「指定タグの関連タグを取得する目的」のためのselectにおいて、そちらのcreateですとインデックスがないので時間がかかるのでは?という疑問がございます。 例えば insert に (1,'1',1),(1,'2',3) とありこれは「1と3の関連付け」の意味ですよね。ではこれに対し「1と関連するタグ」として「3」を、また逆に「3と関連するタグ」として「1」を見つけるには、どのようなselectになりますでしょうか? 質問の両プランでは fk_tag_relations_01 と fk_tag_relations_02 という外部キーがインデックスとなり、上記目的のselectにおける時間は気にしなくていいと考えているので、恐れながらこの点疑問に思いまして…
nikuatsu

2022/08/21 02:57

あ、もしかして、 unique(relation_id,tag_id) ではなく unique(tag_id,relation_id) でしたか? 後者であれば上記目的のselectにおけるインデックスとして活用でき時間への懸念はなさそうかなと…
nikuatsu

2022/08/22 02:16

追記ありがとうございます。そちら実行させて頂きましたが、いずれも返り値が空でした。 尚、and t2.tag_id=1 の方のselectですが、and t1.num<>t2.num の部分をなくすと 1,3 が返るので、恐らくこの部分に原因がありそうですが… そして、and t2.tag_id=1 の指定ですが、やはりご回答のcreateに対してインデックスが効かないのではないでしょうか? ご回答は次の2つのインデックスが貼られており、tag_id での指定で使うのは後者だと思います。しかし tag_id がその先頭にないので tag_id で指定しても使えないように思えます。 primary key(relation_id,num) unique(relation_id,tag_id) 誠に恐れながら…、やはりここは unique(tag_id,relation_id) という順序で貼るべきではないのでしょうか?
nikuatsu

2022/08/22 02:18

あ、でもそうしたら、on t1.relation_id=t2.relation_id のときのインデックスがなくなりますね。 ということは、別途 (tag_id,relation_id) という順序のインデックスを貼る必要があるのではないでしょうか。
yambejp

2022/08/22 02:28

ちょっと状況がわかりかねます。 コピペをするだけなので、なぜ「返り値が空」なのか理解できません。 なにか勝手な解釈をいれてクエリーを変更していませんか? > やはりここは unique(tag_id,relation_id) という順序で貼るべきではないのでしょうか? ちょっと質問の意図がわかりかねます 順番ってなんでしょう?unique(relation_id,tag_id)とは relation_idとtag_idの組み合わせがユニークだという制限であって 順番などまったく関係ありませんが?
nikuatsu

2022/08/22 03:00

ご返信ありがとうございます。お手数おかけしております。 > なにか勝手な解釈をいれてクエリーを変更していませんか? ご回答にある insert into の (3,2,4) だけ (3,'2',4) にしていますが、これはすべきですよね? そして今改めて全てDROP TABLEし、次のリンクのように完璧にコピペでcreateからselectまで実行しましたが、やはり返り値が空でした。 https://imgur.com/a/lJuALfk > 順番などまったく関係ありませんが? これは知らなかったです…!( relation_id,tag_id ) とインデックスを作れば、WHERE relation_id=? ならインデックスが効くけど、WHERE tag_id =? なら効かない。というものだと思っておりました。
yambejp

2022/08/22 08:09 編集

> (3,2,4) だけ (3,'2',4) にしていますが、これはすべきですよね? 失礼しました。ご指摘のとおりです。 (一応数値でもインデックスとして有効なんですけど統一感がないのは問題ですね) > createからselectまで実行しましたが、やはり返り値が空でした さまざまなMySQLのバージョンで試しましたが、返り値は確実にありますね デバグ用のクエリも追記したので試してください
nikuatsu

2022/08/22 08:22

> 以下でテストしてみて > こうなりますか? なりました。 > その上で、1と関連するtag_idを検索 検索できました。 そして、原因がわかりました。もう大変申し訳ございません。 コメントアウトの /* 3 */ を書いたまま実行した場合に返り値が空になるようでした。 何も考えずにコピペしてしまったせいで大変なお手数をおかけしてしまいました。
yambejp

2022/08/22 08:26

> コメントアウトの /* 3 */ を書いたまま 失礼しました。それは私の書き方がわるかったです
nikuatsu

2022/08/22 08:26

teratailのコードブロックはコメントアウトとして無効なら赤字にするみたいに、もうちょっとチェックしてほしいですね(人のせいw) ちなみに、プランBと比べてどの点で良くなるのでしょうか? プランAはレコード数が少なくて済む点でプランBより良さそうだと質問で述べておりますが、ではプランBとご回答のcreateではどのような違いがあるのかと思いまして。
yambejp

2022/08/22 08:35

(1,3)と(3,1)のように裏返しのデータをもつこと自体が管理が煩雑になるだけで いいことがひとつもありません。 tags_ID1とtags_ID2は本来同じカラムなのでカラムを分けることが非効率です。 私のサンプルですとnumという順番を持っているようにも見えますが、 これはrelation_idごとにデータを2つ持つというユニークを担保するための データでしか無いので、順序が不動ですみます。 総じてデータ管理方法としては私のやり方が効率的だと思います
nikuatsu

2022/08/22 08:59

丁寧なご返信をどうもありがとうございます。さっそくそのようにさせて頂きます。
nikuatsu

2022/08/27 04:58 編集

お世話になっております。 ふと思ったのですが、こちら「関連付けの件数」として count_relations カラムを設けるとしたら、tbl_tag_relations テーブルにしますか?それとも別途テーブルを作成されますか? 実は質問の「ORDER BY」が「count_relations DESC」となっておりますのは、「関連付けの件数」が多い順での取得を想定しているためです。 ご回答頂きました tbl_tag_relations テーブルは1つの関連付けに2行を使っていますので、もし tbl_tag_relations テーブルに count_relations カラムを設けるとしたら、次のように「関連付けの件数」が同じ値になるかと思います。 insert into tbl_tag_relations values (1,'1',1, '関連付けの件数'), (1,'2',3, '関連付けの件数'), , , , でも上記のように tbl_tag_relations に同じ値を入れよりは、次のような tbl_count_relations テーブルを別途作成した方がいいでしょうか? create table tbl_count_relations ( relation_id int, count_relations int default 1, CONSTRAINT fk_01 FOREIGN KEY ( relation_id ) REFERENCES tbl_tag_relations ( relation_id ) ); しかし別途テーブルを作成するとSELECTの際のJOINのコストがかかりますから、やはり同じ値にはなっても count_relations カラムは tbl_tag_relations テーブルに設けたほうがよろしいでしょうか?
guest

0

重視するのが検索性能>リソース量で、それぞれに検索のメリットがあるなら、2つとも作成しておき使い分けるというのもアリだと思います。

投稿2022/08/18 12:52

sazi

総合スコア25195

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nikuatsu

2022/08/20 08:20

なるほど。リソース量、そうですよね。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問