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

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

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

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

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

Q&A

解決済

1回答

392閲覧

MySQLで「対象スレッドの投稿者でない者」が投稿したコメントの有無を確認したい

ikatako

総合スコア270

MySQL

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

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

0グッド

0クリップ

投稿2020/11/06 12:21

編集2020/11/06 18:33

###前提
掲示板を作っていて、「対象スレッドの投稿者でない者」が投稿したコメントの有無を確認したいという目的がございます。

例えばスレッドID=1に、その投稿者以外がコメントしていた場合、「スレッド管理者以外のコメントがあります」といった表示をしたいイメージです。

###実現したいこと
現状は「①スレッドのIDが与えられるパターン」なのですが、
これを「②コメントのIDが与えられるパターン」のコードに変更したいです。

つまり
「スレッドIDで対象スレッドを決める」のではなく、
「コメントIDからそのコメントが投稿されたスレッドIDを得て、そのスレッドIDで対象スレッドを決める」という変更です。

###テーブル
テーブルにはすべての投稿が入るp_mattersがあり、そのサブタイプとしてp_threadsp_commentsがあります。

SQL

1-- 投稿 (スーパータイプ) 2CREATE TABLE p_matters ( 3 `ID` int 4 ,`user_ID` int 5 ,`kind` int 6 ,`sentence` varchar(1000) 7 ,`created_at` DATETIME(6) 8 ,PRIMARY KEY (`ID`) 9 ,INDEX idx_p_matters_1 (`kind`) 10 ,CONSTRAINT fk_p_matters_1 FOREIGN KEY (user_ID) REFERENCES users (ID) # 2020/11/07 03:33 追記 11); 12INSERT INTO p_matters 13 (`ID`, `user_ID`, `kind`, `sentence`, `created_at`) 14VALUES 15 # thread は kind=1 で comment は kind=2 16 (1, 3, 1, '猫について語りましょう', '2020-01-01 12:00:00.123456'), 17 (2, 3, 1, '犬について語りましょう', '2020-01-02 12:00:00.123456'), 18 (3, 4, 2, '猫はいいです', '2020-01-03 12:00:00.123456'), 19 (4, 1, 2, 'たしかに猫は最高です', '2020-01-04 12:00:00.123456'), 20 (5, 2, 1, '鰐について語りましょう', '2020-01-05 12:00:00.123456'), 21 (6, 3, 2, '鰐は怖いです', '2020-01-06 12:00:00.123456'), 22 (7, 1, 2, '犬はやわらかいです', '2020-01-07 12:00:00.123456'), 23 (8, 3, 2, '猫は液体です', '2020-01-08 12:00:00.123456'), 24 (9, 2, 2, '猫は液体ではないです', '2020-01-09 12:00:00.123456'), 25 (10, 4, 2, '鰐は怖くないです', '2020-01-10 12:00:00.123456') 26; 27 28-- 投稿 (サブタイプ/スレッド) 29CREATE TABLE p_threads ( 30 `matter_ID` int 31 ,`title` varchar(100) 32 ,PRIMARY KEY (`matter_ID`) 33 ,CONSTRAINT fk_p_threads_1 FOREIGN KEY (`matter_ID`) REFERENCES p_matters(`ID`) # 2020/11/06 23:18 追記 34); 35INSERT INTO p_threads 36 (`matter_ID`, `title`) 37VALUES 38 (1, '猫スレ'), (2, '犬スレ'), (5, '鰐スレ') 39; 40 41-- 投稿 (サブタイプ/コメント) 42CREATE TABLE p_comments ( 43 `matter_ID` int 44 ,`thread_ID` int 45 ,PRIMARY KEY (`matter_ID`) 46 ,CONSTRAINT fk_p_comments_1 FOREIGN KEY (`matter_ID`) REFERENCES p_matters(`ID`) 47 ,CONSTRAINT fk_p_comments_2 FOREIGN KEY (`thread_ID`) REFERENCES p_threads(`matter_ID`) 48); 49INSERT INTO p_comments 50 (`matter_ID`, `thread_ID`) 51VALUES 52 (3, 1),(4, 1),(6, 5),(7, 2),(8, 1),(9, 1),(10, 5) 53;

###該当のソースコード
現状の「①スレッドのIDが与えられるパターン」がこちらです。

実行 → http://sqlfiddle.com/#!9/ec5910/2

SQL

1/* 2- 与えられる値 3スレッド ID=1 が与えられます 4 5- 実行したいSQL文 6その投稿者である user_ID=3 以外が投稿したコメントを取得します 7 8- 求める結果 9結果は ID=3,4,9 となります 10*/ 11 12# スレッドへのコメントを取得 13SELECT * 14FROM p_matters matters1 15 INNER JOIN p_comments comments 16 ON comments.matter_ID = matters1.ID 17 AND comments.thread_ID = 1 18 19# ただしそのコメントの投稿者は次を含まない 20WHERE matters1.user_ID NOT IN ( 21 22 # 同スレッドの投稿者 23 SELECT matters2.user_ID 24 FROM p_matters matters2 25 LEFT JOIN p_threads threads 26 ON threads.matter_ID = matters2.ID 27 WHERE threads.matter_ID = 1 28)

###試したこと
上記を「②コメントのIDが与えられるパターン」のコードに変更したいわけで次のように試しました。

上記はスレッドIDが与えられますが、こちらはコメントIDしか与えられないため、対象スレッドを判断するためにSELECTを増やさないといけないと思います。

一応できはしたのですが…

SELECTをこうして増やしますと# ここでスレッド ID=1 を取得というSELECTが2か所になってしまい、この重複に冗長さを覚えています。

実行 → http://sqlfiddle.com/#!9/ec5910/4

sql

1/* 2- 与えられる値 3コメント ID=3 が与えられます 4 5- 実行したいSQL文 6そのスレッドである ID=1 を取得し 7その投稿者である user_ID=3 以外が投稿したコメントを取得します 8 9- 求める結果 10結果は ID=3,4,9 となります 11*/ 12 13# スレッドへのコメントを取得 14SELECT * 15FROM p_matters matters1 16 INNER JOIN p_comments comments 17 ON comments.matter_ID = matters1.ID 18 AND comments.thread_ID = ( 19 # ここでスレッド ID=1 を取得 20 SELECT thread_ID 21 FROM p_comments 22 WHERE matter_ID = 3 23 ) 24 25# ただしそのコメントの投稿者は次を含まない 26WHERE matters1.user_ID NOT IN ( 27 28 # 同スレッドの投稿者 29 SELECT matters2.user_ID 30 FROM p_matters matters2 31 LEFT JOIN p_threads threads 32 ON threads.matter_ID = matters2.ID 33 WHERE threads.matter_ID = ( 34 # ここでスレッド ID=1 を取得 35 SELECT thread_ID 36 FROM p_comments 37 WHERE matter_ID = 3 38 ) 39)

改めまして、上記「②コメントのIDが与えられるパターン」のコードにつきまして、この冗長さを回避するとしたらどんな書き方が良いでしょうか。

アドバイスよろしくお願い致します。

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

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

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

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

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

sazi

2020/11/06 13:08

なんで、p_threads の主キーが`matter_ID`なんですか。 ドメイン管理的にはIDですけどね。 SQL考える上で、邪魔でしょうがないです。という訳で挫折しました。
ikatako

2020/11/06 13:18

初心者設計ですみません。よろしければ教えてください。 もしそちらをmatter_IDでなくIDとした場合、p_commentsのカラムは 「matter_ID,thread_ID」でなく 「ID,thread_ID」とする感じでしょうか? もしそうであれば、その「p_commentsのID」が、「p_comments自身のIDなのか、それともp_mattersのIDなのか」がわかりにくくなりませんでしょうか?それをわかりやすいと思って「matter_ID,thread_ID」とした。というのが初心者なりに考えた設計の理由になります。 もしかしてこのわかりにくさは、外部キーでカバーするのが一般の設計ですか?
ikatako

2020/11/06 13:20

つまり 「matter_ID,thread_ID」でなく 「ID,thread_ID」としておいて、 「p_comments自身のIDなのか、それともp_mattersのIDなのか」がわからない と一瞬なりますが 「p_commentsのIDに、p_mattersへの外部キーがあるから、ここはp_mattersのIDだ」ということがわかる という考え方になりますか?
sazi

2020/11/06 13:33

>もしそちらをmatter_IDでなくIDとした場合、p_commentsのカラムは >「matter_ID,thread_ID」でなく >「ID,thread_ID」とする感じでしょうか? p_commentsは双方への外部キーだからそのままでおかしくないですよ。 それよりも、SQL中に > p_matters matters2 > LEFT JOIN p_threads threads > ON threads.matter_ID = matters2.ID とあり、ON threads.matter_ID = matters2.ID という事は、threads.matter_ID はmatters.IDへの外部キーという事ですか?
ikatako

2020/11/06 13:48

> という事は、threads.matter_ID はmatters.IDへの外部キーという事ですか? イエスです。もしかして、それも何かおかしいでしょうか。 p_matters は、 p_threads 並びに p_comments のスーパータイプとして作りましたのでそのような設計と致しました。
sazi

2020/11/06 13:58 編集

>イエスです では、以下のようした方が良いと思います。 -- 投稿 (サブタイプ/スレッド) CREATE TABLE p_threads ( `ID` int ,`matter_ID` int ,`title` varchar(100) ,PRIMARY KEY (`ID`) ,CONSTRAINT fk_p_comments_1 FOREIGN KEY (`matter_ID`) REFERENCES p_matters(`ID`) ); -- 投稿 (サブタイプ/コメント) CREATE TABLE p_comments ( `ID` int ,`matter_ID` int ,`thread_ID` int ,PRIMARY KEY (`ID`) ,CONSTRAINT fk_p_comments_1 FOREIGN KEY (`matter_ID`) REFERENCES p_matters(`ID`) ,CONSTRAINT fk_p_comments_2 FOREIGN KEY (`thread_ID`) REFERENCES p_threads(`ID`) );
sazi

2020/11/06 14:03

一意性の担保は、別途ユニーク制約で。
ikatako

2020/11/06 14:18

確かにp_tereadsの外部キーを失念していました。ありがとうございます。(質問文に「# 2020/11/06 23:18 追記」としておきました。) しかし、別途「ID」を新たに足すのでしょうか?その「ID」にはんな値が想定されているのですか?
sazi

2020/11/06 14:30 編集

オートナンバーで良いかと思います。 ただ、敢えてとは言いません。 そのままナチュラルキーでも良いかと思います。
sazi

2020/11/06 14:36

threadじゃなくて、p_matters がスーパータイプなのが非常に違和感を感じます。
ikatako

2020/11/06 14:45 編集

オートインクリメントのIDですが、そちら何のためのものなのでしょうか? 当初の「matter_IDの代わりにIDにした方がいい」というのは合点がいきますが、「matter_IDとは別途IDを追加した方がいい」というのは意図が掴めなくて…
ikatako

2020/11/06 15:00 編集

>threadじゃなくて、p_matters がスーパータイプなのが非常に違和感を感じます 他に返信テーブルとしてp_replyなどもありまして、「それらすべての投稿タイプが共通で持つカラム」の意味で「p_matters」というスーパータイプを設けました。 例えばスレッドテーブルにしかないカラム(例えばそのスレッドの先頭に固定するコメントのID)などのカラムがあり、このカラムはコメントテーブルや返信テーブルに不要です。 スレッドテーブルをスーパータイプにしてしまうと、コメントや返信のレコードでこのカラムにNULLが入ってしまいますので、NULLを避けるために、すべての投稿タイプが共通で持つカラムを抽出して、p_mattersというスーパータイプになっています。
ikatako

2020/11/06 15:34

…おかしいでしょうか。テーブル設計から見直す覚悟もあるので、どうぞ遠慮なく仰ってください。
sazi

2020/11/06 15:51

詳細については理解していなので、単に名称から違和感を感じているだけです。
ikatako

2020/11/06 16:08

たしかにmatterという名称が上位に置かれるのはおかしいですよね!笑 ご指摘ありがとうございます。
guest

回答1

0

ベストアンサー

SELECTをこうして増やしますと# ここでスレッド ID=1 を取得というSELECTが2か所になってしまい、この重複に冗長さを覚えています。

条件はよくわからないので、単に記述を変えただけのものです。

SQL

1select * 2from p_matters matters 3 inner join p_comments comments 4 on comments.matter_id = matters.id 5 inner join p_comments comments2 6 on comments.thread_id=comments2.thread_id 7where not exists( 8 select 1 9 from p_matters matters2 10 left join p_threads threads 11 on threads.matter_id = matters2.id 12 left join p_comments comments 13 on threads.matter_id = comments.thread_id 14 where comments.matter_id = comments2.matter_id 15 and matters2.user_id=matters.user_id 16 ) 17 and comments2.matter_id = 3

投稿2020/11/06 16:03

sazi

総合スコア25195

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

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

ikatako

2020/11/06 17:22

ありがとうございます。さすが、想定通りの取得となりました。 EXPLAINしますとmatters2でフルインデックススキャン(typeがindex)が見つかりましてここだけ改善したいのですが、これはサブクエリ内ですから避けられないでしょうか?
sazi

2020/11/06 17:30 編集

p_mattersに(id,user_id)若しくは(user_id)のインデックスを追加してみて下さい
ikatako

2020/11/06 20:53 編集

はい、インデックスを追加しての「typeがindex」でして、追加前は「typeがall」でした。 下図の左がご回答の「typeがall」で、右がそのあとインデックスを追加しての「typeがindex」です。 https://imgur.com/a/mQnI0gi
sazi

2020/11/07 02:52 編集

質問にない、is_trushが条件にあるようですが? 追加したインデックスの内容は? 使用されているfk_p_matters_1 はusersへの外部参照であり、SQL上usersが出てこないので参照先がNullなのです。
ikatako

2020/11/07 09:24 編集

> 質問にない、is_trushが条件にあるようですが? is_trashはレコードが削除されたことを 0|1 で示すp_mattersのカラムですが、SQLにあまり影響ないと判断し質問から割愛してしまいました。失礼いたしました。 > 追加したインデックスの内容は? 追加したインデックスは「# 2020/11/07 03:33 追記」とある行のfk_p_matters_1になります。 > 使用されているfk_p_matters_1 はusersへの外部参照であり、SQL上usersが出てこないので参照先がNullなのです。 SQL上usersを出すというと、次の「#以下を追加」でusersをJOINするという意味かと思いましたが、やはり画像右と同様の結果でした。この意味とは違うでしょうか。 from p_matters matters2 left join p_threads threads on threads.matter_id = matters2.id left join p_comments comments on threads.matter_id = comments.thread_id # 以下を追加 left join users u on u.ID = matters2.user_ID
ikatako

2020/11/07 09:52

というか実行計画の改善は質問内容ではありませんでしたね。あれこれ質問してしまい申し訳ございません。 自分でいろいろ調べてみますので、どうぞお気になさらず。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問