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

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

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

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

Q&A

2回答

1297閲覧

MySQLでの取得が複雑な条件で困っています

azagomarashi

総合スコア13

MySQL

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

0グッド

0クリップ

投稿2020/08/13 12:58

編集2020/08/15 05:21

【前提・実現したいこと】

MySQL5.7 にて、以下の【条件】でpostsテーブルから取得ができました。

しかし、【現状のコード】はWHEREの対象がpostsのすべてになっていると思います。

そこで、➀➁➂➃に限定したpostsを対象に、➄➅でWHEREをかけることができれば早くなると思っているのですが、そのための【試したコード】で躓いています。

###【条件】
★ 取得条件
➀post_kind = 1 のもの
AND
指定ユーザが ➁投稿したもの OR ➂フォローしたもの OR ➃保存したもの

☆ 除外条件
➄削除されたもの( is_trash=1 のもの )
OR
➅ブロックした、されたユーザによるもの

【CREATE、INSERT】

ユーザ、投稿、アクション(フォロー、保存、ブロック)
という5つのテーブルで構成されます。

SQL

1-- ユーザ 2CREATE TABLE users( 3 `ID` INT NOT NULL AUTO_INCREMENT, 4 `name` VARCHAR(100) NOT NULL, 5 PRIMARY KEY (`ID`) 6); 7INSERT INTO users 8 (`name`) 9VALUES 10 ('A子'),('B子'),('C子'),('D子'),('E子') 11; 12 13-- 投稿 14CREATE TABLE posts( 15 `ID` INT NOT NULL AUTO_INCREMENT, 16 `user_ID` INT NOT NULL, 17 `title` VARCHAR(100) NOT NULL, 18 `post_kind` VARCHAR(10) NOT NULL, 19 `is_trash` INT(1) NOT NULL DEFAULT 0, 20 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 21 PRIMARY KEY (`ID`), 22 INDEX (user_ID), 23 CONSTRAINT FOREIGN KEY (`user_ID`) REFERENCES users(`ID`) 24); 25INSERT INTO posts 26 (`user_ID`, `title`, `post_kind`) 27VALUES 28 (4, 'a', 1), (2, 'b', 1), (2, 'c', 1), (3, 'd', 1), (1, 'e', 1), 29 (5, 'd', 2), (2, 'f', 2), (5, 'g', 2), (2, 'h', 2), (1, 'j', 2) 30; 31 32-- 保存した投稿 33CREATE TABLE keeps( 34 `user_ID` INT NOT NULL, 35 `post_ID` INT NOT NULL, 36 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 37 PRIMARY KEY (`user_ID`,`post_ID`), 38 INDEX (`user_ID`,`post_ID`), 39 CONSTRAINT FOREIGN KEY (`user_ID`) REFERENCES users(`ID`), 40 CONSTRAINT FOREIGN KEY (`post_ID`) REFERENCES posts(`ID`) 41); 42INSERT INTO keeps 43 (`user_ID`,`post_ID`) 44VALUES 45 (3,1),(2,4),(2,1),(1,1),(4,1),(2,2),(3,5),(5,1),(3,2),(4,5) 46; 47 48-- フォローした投稿 49CREATE TABLE follows( 50 `user_ID` INT NOT NULL, 51 `post_ID` INT NOT NULL, 52 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 53 PRIMARY KEY (`user_ID`,`post_ID`), 54 INDEX (`user_ID`,`post_ID`), 55 CONSTRAINT FOREIGN KEY (`user_ID`) REFERENCES users(`ID`), 56 CONSTRAINT FOREIGN KEY (`post_ID`) REFERENCES posts(`ID`) 57); 58INSERT INTO follows 59 (`user_ID`,`post_ID`) 60VALUES 61 (5,3),(4,4),(2,2),(2,3),(1,4),(2,5),(5,1),(2,4),(5,4),(3,1) 62; 63 64-- ブロックしたユーザ 65CREATE TABLE blocks( 66 `user_ID` INT NOT NULL, -- した側 67 `user_ID2` INT NOT NULL, -- された側 68 `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 69 PRIMARY KEY (`user_ID`,`user_ID2`), 70 INDEX (`user_ID2`,`user_ID`), 71 CONSTRAINT FOREIGN KEY (`user_ID`) REFERENCES users(`ID`), 72 CONSTRAINT FOREIGN KEY (`user_ID2`) REFERENCES users(`ID`) 73); 74INSERT INTO blocks 75 (`user_ID`,`user_ID2`) 76VALUES 77 (1,5),(1,2),(3,4),(5,1),(4,1) 78;

###【現状のコード】
このデータから条件に合致した投稿を得るSELECTですが、先述のようにWHEREの対象がpostsのすべてになっていると思います。

SQL

1SELECT 2 p1.ID post_id, 3 CASE WHEN p1.post_kind = 1 THEN 'dog' WHEN p1.post_kind = 2 THEN 'cat' END post_kind, 4 k.created_at date_keeping, 5 f.created_at date_following, 6 p2.created_at date_posting, 7 u.ID author_id, 8 u.name author_name, 9 -- 以下2つのblock日はSELECTに不要でした ( 2020/08/15 14:21 に削除 ) 10 -- b1.created_at date_blocking, 11 -- b2.created_at date_blocked 12 13FROM 14 posts p1 15 16 -- 投稿者情報を連結 17 LEFT JOIN users u ON u.ID = p1.user_ID 18 19 -- 2(B子) のブロック情報を連結 20 LEFT JOIN blocks b1 ON b1.user_ID = 2 AND b1.user_ID2 = p1.user_ID 21 LEFT JOIN blocks b2 ON b2.user_ID2 = 2 AND b2.user_ID = p1.user_ID 22 23 -- 2(B子) の keep 情報を連結 24 LEFT JOIN keeps k ON k.user_ID = 2 AND k.post_ID = p1.ID 25 26 -- 2(B子) の follow 情報を連結 27 LEFT JOIN follows f ON f.user_ID = 2 AND f.post_ID = p1.ID 28 29 -- 2(B子) の 投稿 情報を連結 30 LEFT JOIN posts p2 ON p2.user_ID = 2 AND p2.ID = p1.ID 31 32-- 以下のWHEREは間違いです ( 2020/08/15 14:07 に削除 ) 33/* 34WHERE 35 p1.post_kind = 1 -- ➀ 36 AND p1.is_trash = 0 -- ➄ 37 AND b1.created_at IS NULL -- ➅ 38 AND b2.created_at IS NULL -- ➅ 39 -- 以下忘れていたので追記( 2020/08/14 13:34 に追加 ) 40 AND ( k.created_at IS NOT NULL OR f.created_at IS NOT NULL ) -- ➂➃ 41*/ 42 43-- 以下のWHEREが正しいです ( 2020/08/15 14:07 に追加 ) 44WHERE 45 p1.post_kind = 1 -- ➀ 46 AND p1.is_trash = 0 -- ➄ 47 AND ( 48 ( 49 b1.created_at IS NULL -- ➅ 50 AND b2.created_at IS NULL -- ➅ 51 AND ( k.created_at IS NOT NULL OR f.created_at IS NOT NULL ) -- ➂➃ 52 ) 53 OR p2.created_at IS NOT NULL -- ➁ 54 ) 55

【試したコード】

➀➁➂➃に限定したpostsを対象に、➃➄でWHEREをかけようと思い、以下、下の3つでINNSER JOINを使ったのですが、これは連続では使えないようで、どうすればいいかわからず躓いています。

SQL

1FROM 2 posts p1 3 4 -- 投稿者情報を連結 5 LEFT JOIN users u ON u.ID = p1.user_ID 6 7 -- 2(B子) のブロック情報を連結 8 LEFT JOIN blocks b1 ON b1.user_ID = 2 AND b1.user_ID2 = p1.user_ID 9 LEFT JOIN blocks b2 ON b2.user_ID2 = 2 AND b2.user_ID = p1.user_ID 10 11 -- 2(B子) の keep したものに限定 12 INNER JOIN keeps k ON k.user_ID = 2 AND k.post_ID = p1.ID 13 14 -- 2(B子) の follow したものに限定 15 INNER JOIN follows f ON f.user_ID = 2 AND f.post_ID = p1.ID 16 17 -- 2(B子) の 投稿 したものに限定 18 INNER JOIN posts p2 ON p2.user_ID = 2 AND p2.ID = p1.ID

自分なりの考えで【試したコード】の書き方になりましたが、INNER JOINよりも良い方法などあればぜひ知りたいです。

とにかく最適な取得は何かについて勉強されていただければと思っていますので、ご回答宜しくお願い申し上げます。

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

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

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

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

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

yambejp

2020/08/13 14:01

条件を文書でただしく説明してくれないとやりようがないですね テーブル構成もこれがマストなのでしょうか?
sazi

2020/08/13 14:23

> 下の3つでINNSER JOINを使ったのですが、これは連続では使えないようで とはどういう事ですか? エラーではないようですし。
azagomarashi

2020/08/13 14:52 編集

yambejp様 テーブル構成もマストかわかりませんが、ひとまずこれで進めようと思っています。明らかにおかしいところがあればご指摘頂けると嬉しいです。 条件の文書はどこか不明瞭だったり、多義的な解釈ができたりしますか?
azagomarashi

2020/08/13 14:49

sazi様 エラーではないのですが、 ”【現状のコード】と同じ結果にならない” ことを指して ”連続では使えないようで” と書きました。
guest

回答2

0

色々おかしなところがあります。
先ずテーブルの定義において、keepsテーブルとして独立させる意味はありますか?
postsはidで一意なのだから、保存した日を持たせるにしてもpostsに持てば済む話だと思います。

次にSQLについてですが、postsに対してblocksやfollowsは1:Nの関係ですが、joinするとpostsは複数件になりますが、そのような結果を望んでいるのでしょうか?
post_idにつき1件としたいなら、blocksやfollowsは集計したものと結合するか、サブクエリーで1件だけ取得するようにしなければなりません。

postsの自己結合については意味不明です。一意キー同士を結合しても同じ情報しか所得出来ないのでその事に意味がありません。

チューニングの前に、それらを整理する必要があると思います。
あなたの質問はぱっと見で性能を改善したいのが目的のように見えますが、本当は意図通りに抽出できていない事に対してではありませんか?
もしそうなら、もっと要件が分かるように、希望する結果をサンプルとして質問に追記して下さい。

追記

★ 取得条件
➀post_kind = 1 のもの
AND
指定ユーザが ➁投稿したもの OR ➂フォローしたもの OR ➃保存したもの

☆ 除外条件
➄削除されたもの( is_trash=1 のもの )
OR
➅ブロックした、されたユーザによるもの

最終形が良く分からないので、上記条件に合わせてpostsを取得するとすると以下の様な記述になります。

SQL

1select * from posts 2where post_kind = 1 -- ① 3 and ( 4 user_id = 2 -- ② 5 or id in ( 6 select post_id from follows where user_id=2 -- ③ 7 union all 8 select post_id from keeps where user_id=2 -- ④ 9 ) 10 ) 11 and is_trash = 0 -- ⑤ 12 and user_id not in ( -- ⑥ 13 select user_id from blocks where user_ID2=2 14 union all 15 select user_id2 from blocks where user_ID=2 16 )

これを結合条件に直したとしても、質問に記載されたSQLにはなりません。
なので、質問にあるSQLで本当に得たい結果になっているかどうかが疑問なのです。

投稿2020/08/14 03:54

編集2020/08/15 04:52
sazi

総合スコア25206

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

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

azagomarashi

2020/08/14 04:51 編集

大変多くのアドバイスをありがとうございます!!設計を見直すきっかけになりました。 以下4点に引用コメントさせて頂きましたので、長文で誠にすみませんが…お時間ございますときにご返信いただけますと幸いです。
azagomarashi

2020/08/14 04:51

【1】 > 先ずテーブルの定義において postsのレコードに「このレコードのIDをkeepしたuser_IDを持たせる」わけですか?うーん。例えばposts_ID=1をkeepしているのはB子だけでなく全員ですが、これをどうpostsにINSERTしますか? ・5つのカラムをレコードに追加する(横に追加) ・または5件のレコードを追加しpostsのPKをなくす(縦に追加) になると思いますが、いずれもkeepsを独立させることと比べてどんなメリットがあるのかわからないのですが… 【2】 > 次にSQLについて 仰る通り、joinでは1:多は望んでいません。しかしuser_IDを2(B子)と指定すれば、postsに対してblocksやfollowsは1:1になると思いますので、問題視していません。どのようなケースを問題視なさっていますか?(joinが1:多になるケースとは?) 【3】 > postsの自己結合について 自己結合は「> 指定ユーザが ➁投稿したもの OR ➂フォローしたもの OR ➃保存したもの」に走査対象を限定したかったためです。まず「> ➁投稿したもの」に限定し、そのうえでWHEREをかけた方が、全体のpostsにWHEREをかけるより良さそうだと考えた、というのが質問の経緯でしたので。(その考えがあやしいというのはmaisumakun様からご指摘頂きましたが、結局どうすべきかはまだ不明です。) 【4】 > 本当は意図通りに抽出できていない事に対してではありませんか? 一行だけ追記しましたが、【現状のコード】で意図通りに抽出できています。他のサンプルならば、「2(B子)」の部分を「3(C子)」に変更していただければと思います。
sazi

2020/08/15 05:08 編集

【1】これはkeepの目的を取り違え(保存という言葉から投稿を確定した場合の情報だと解釈)ていたので無視して下さい。 【2】postsが軸ではなく、userが軸という事みたいなので無視して下さい。 【3】意図は理解しました。ですがその意図どおりのSQLにはなっていないと思いますよ。 関係しているユーザーに紐つくpostならfollowsとかkeepsのuser_id経由だと思います。 【4】それなら、サンプルデータがパターン網羅できていないのでしょう。 そもそもテーブルの目的すら説明されていないのですから、サンプルデータなり、説明なりでもっと補完がが必要です。
azagomarashi

2020/08/14 05:55 編集

以下たびたび恐れ入りますが引用コメントさせて頂きます。特に【3】が気になります。 【3】 > 関係しているユーザーに紐つくpostならfollowsとかkeepsのuser_id経由だと思います。 こちら読解できませんでした… おそらく【現状のコード】を『そのように』書き換えるべきだと仰っているのかと思いますが、『どのように」かわからないです。『followsとかkeepsのuser_id経由』といいますと…?JOINがおかしいということですか? 【4-1】 > それなら、サンプルデータがパターン網羅できていないのでしょう こちらもわからないです。サンプルデータのパターン網羅とは、なんですか? 「B子」を「C子」や「D子」に変更すること(FROMの「user_ID = 2」を「user_ID = 3」や「user_ID = 4」に変更すること)以外に、サンプルの提示ができないのですが…網羅して提示するというのは、まさかそれを「user_ID = 10」まで質問文に書くべきだと仰っているのではないですよね? 【4-2】 > そもそもテーブルの目的すら説明されていないのですから CREATE文の上のコメントが、テーブルの目的になります。 例えば -- ユーザ CREATE TABLE users( ); とあれば「users」は「ユーザ」のリストですし、 また例えば -- フォローした投稿 CREATE TABLE follows( ); とあれば「follows」は「フォローした投稿」のリストです。
sazi

2020/08/14 09:13 編集

【4】 keepはどうですか? 説明を聞くと、投稿者以外が引用した投稿が本来の意味のように受け取れますが、「保存した投稿」で説明になっていると思われますか? まあ、そこが議論する所では無くて、事実伝わらなかったという事だけです。 SQLが正しいなら通常はそれで補完するのですが、SQLが??な状態なので。 【3】 > LEFT JOIN posts p2 ON p2.user_ID = 2 AND p2.ID = p1.ID 上記の「user_ID = 2 」は投稿者を指しているのではないですか? フォロー者の投稿を取得するなら2では無いはずだし、そもそも[p2.ID = p1.ID]の条件はおかしいと思うのですが、違ってますか? left joinなので抽出の条件には影響していませんが、取得できた場合は指定ユーザーが投稿者の場合のみですので、同じ情報を2重に取得しているだけです。
azagomarashi

2020/08/15 04:39

たしかに、提示したSQLは条件を満たしていませんでした。 user_ID=3で試すと、3が投稿したpost_ID=4が取れませんでした。 ご回答いただいたSQLが(IDの大文字とコメントのスペースを微調整すれば)正しかったです。 まさに「>本当は意図通りに抽出できていない」がはじめっから正しかったわけで、会わせる顔がございません。素人が余計なSQLを提示すべきではなかったと反省しています。ご迷惑おかけして大変申し訳ございませんでした。
azagomarashi

2020/08/15 04:54 編集

コメントに誤りがあったため削除しました
sazi

2020/08/15 04:51

いえ、SQLを提示されるのは良い事なのです。 それにあわせて、テーブルの説明や要求事項が明確である事で、今回はそこに不足があると感じました。
azagomarashi

2020/08/15 04:53

で【3】の「>同じ情報」がなぜあるかというと、それが「>3が投稿したpost_ID=4が取れませんでした」を取るためのJOINです。 つまり以下最後のORで、3の投稿を取得するためのJOINが、【3】の「>同じ情報」です。 なので【現状のコード】のWHEREはこれで提示すべきでした。 WHERE p1.post_kind = 1 AND p1.is_trash = 0 AND ( ( b1.created_at IS NULL AND b2.created_at IS NULL AND ( k.created_at IS NOT NULL OR f.created_at IS NOT NULL ) ) -- 当人の投稿をここで指定する OR p2.created_at IS NOT NULL )
sazi

2020/08/15 04:59

> user_ID=3で試すと、3が投稿したpost_ID=4が取れませんでした。 予めデータのパターンをサンプルとして用意しておけば気付けたはずです。 それがテストデータとしてのパターンが網羅出来ていないという事です。
azagomarashi

2020/08/15 05:01

もうまったく仰る通りで..( ;´꒳`;)
sazi

2020/08/15 05:04

SQLを組み立てるとき、結合条件と抽出条件は分けて考える方が良いですよ。 from句ではあくまで結合条件によって求める集合を作り出し、それをwhere条件でフィルターする。 チューニングするにしても、そこをスタートにする方が明確にできます。
azagomarashi

2020/08/15 05:14

なるほど。つまり質問の【試したこと】は、求める集合を作る前に、INNSER JOINによってその集合を少なくしたら早くなりそうだという考えに基づいたSQLですが、それよりも【現状のコード】のようにまず求める集合を作った方がいいだろいうことですね。 そうしますと、初めのご回答からご指摘頂いていた【3】の自己結合の部分ですが、これはもっと良い方法などあるのでしょうか?こうしないと求める集合から➁が得られないのではと思うのですが、ご指摘に鑑みれば何やら他の適した方法がありそうなにおいがします。よろしければそのお心をお聞かせ願えませんでしょうか。
sazi

2020/08/15 05:15

今回結合した形のSQLで回答しなかったのは、select項目の目的が分からないためです。 条件でブロックされているものは除外するのに、selectではそのデータの日付を取得する矛盾があったり。
sazi

2020/08/15 05:19

現状の質問はSQLで正しく取得できている前提だったのでしょうから、求めたい結果に関する情報が不足しています。 ほとんどこの回答をした時に言っている事ですが、そこを整理して下さい。
azagomarashi

2020/08/15 05:24

たしかにblock日はSELECTに不要でした。間違いばかりで申し訳ございませんでした。
sazi

2020/08/15 05:53 編集

結合条件と抽出条件は分けて考える方が良いと言っているのは、集合が明確になるからです。 これ以上なく正規化されている状態であれば、from句内でインラインビューを使用することはありません。 逆に言うと、インラインビューにしなければ集合全体が表わせないなら、正規化の余地があったりするわけです。
guest

0

しかし、【現状のコード】はWHEREの対象がpostsのすべてになっていると思います。

そこで、➀➁➂➃に限定したpostsを対象に、➄➅でWHEREをかけることができれば早くなると思っているのですが、

机上の空論で考える前にEXPLAINを走らせましょう。エンジンの方で、絞り込みがより効く条件を優先して使っていることもありえます。

投稿2020/08/13 13:01

編集2020/08/13 13:02
maisumakun

総合スコア145208

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

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

azagomarashi

2020/08/13 13:09

EXPLAINをうっかりしていたのは申し訳ございませんが、「EXPLAINすればわかる」と間違いだけをあげつらわれてしまうと… 質問はその先についてでして、とにかく最適な取得は何かについて勉強されていただければと思っていますので( ;´꒳`;)
maisumakun

2020/08/13 13:12

> 「EXPLAINすればわかる」と間違いだけをあげつらわれてしまうと… いえ、間違いではなく、「それがチューニングの本質」だと考えています。 細かい制御の効かないSQL経由で操らないといけない関係上、実際にコードを書いてEXPLAINして、の繰り返しが最善の手段だと考えています。
maisumakun

2020/08/13 13:13

(元のコードと試したコードで、どちらがいい結果となっていますか?)
maisumakun

2020/08/13 13:16

> 最適な取得は何か データ分布や分量にすら依存しますので、実際に動かしたデータを見なければ何も言えません。
azagomarashi

2020/08/13 13:33

なるほどですね。「チューニングの本質」ありがとうございます。心得ておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.46%

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

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

質問する

関連した質問