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

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

新規登録して質問してみよう
ただいま回答率
85.35%
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回答

5654閲覧

[Mysql] グループ化した中で 最頻値・2番目に多いデータを取得したい

kirby318

総合スコア10

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/10/08 09:43

編集2020/10/08 14:32

mysqlバージョン: v5.7

やりたいこと

sql

1 2# テーブル作成 3CREATE TABLE `trump` ( 4 `id` int(11) NOT NULL, 5 `card_type` varchar(40) NOT NULL COMMENT 'カード種別', 6 `created_at` datetime DEFAULT NULL COMMENT '作成日時' 7); 8 9# インサート 10INSERT INTO `trump` (`id`, `card_type`, `created_at`) VALUES 11  (1, "K", "2020-10-01 11:00:00"), 12  (1, "K", "2020-10-01 12:00:00"), 13  (1, "K", "2020-10-01 13:00:00"), 14  (1, "Q", "2020-10-01 13:00:00"), 15  (1, "Q", "2020-10-01 14:00:00"), 16  (2, "K", "2020-10-01 11:00:00"), 17  (2, "K", "2020-10-01 12:00:00"), 18  (2, "Q", "2020-10-01 11:00:00"), 19  (2, "Q", "2020-10-01 13:00:00"), 20  (3, "JOKER", "2020-10-01 11:00:00"), 21  (3, "JOKER", "2020-10-01 12:00:00"); 22 23# データ取得 24select * from trump; 25+----+-----------+---------------------+ 26| id | card_type | created_at | 27+----+-----------+---------------------+ 28| 1 | K | 2020-10-01 11:00:00 | 29| 1 | K | 2020-10-01 12:00:00 | 30| 1 | K | 2020-10-01 13:00:00 | 31| 1 | Q | 2020-10-01 13:00:00 | 32| 1 | Q | 2020-10-01 14:00:00 | 33| 2 | K | 2020-10-01 11:00:00 | 34| 2 | K | 2020-10-01 12:00:00 | 35| 2 | Q | 2020-10-01 11:00:00 | 36| 2 | Q | 2020-10-01 13:00:00 | 37| 3 | JOKER | 2020-10-01 11:00:00 | 38| 3 | JOKER | 2020-10-01 12:00:00 | 39+----+-----------+---------------------+

上記のようなテーブルがあるとします。
以下の条件に合ったデータを id でグループ化して mysql で取得したいと思っています。

■ 取得したい内容
1. id 2. 同一 id の中で 最も多い card_type   【エイリアス】 card_type_1    3. { 同一 id の中で 最も多い card_type } の中で 最新 の created_at   【エイリアス】 card_type_1 4. 同一 id の中で 2番目 に多い card_type   【エイリアス】 card_type_2 5. { 同一 id の中で 2番目 に多い card_type } の中で 最新 の created_at   【エイリアス】 card_type_2

※ 同率一位の場合、 created_at で比較し、新しい方が 1位 とする。

■ 結果として欲しいレコード

結果としては以下のレコードが欲しいです。

idcard_type_1created_at_1card_type_2created_at_2
1K2020-10-01 13:00:00Q2020-10-01 14:00:00
2Q2020-10-01 13:00:00K2020-10-01 12:00:00
3JOKER2020-10-01 12:00:00nullnull

いろいろ調べたり試してみたのですが、そもそもこれが実現できることなのかもわからず、困っています。
もし、一回で取得するのが難しければ、phpと処理を分けるつもりなのですが、
できればsqlで一回で取得したいです。

どのようなクエリを書けば良いのか、ご教示いただきたいです。よろしくお願いします。

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

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

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

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

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

firegrape

2020/10/08 10:33

無茶苦茶いい問題ですね。 教科書の応用問題で出そう。 今、考えています。 たぶん、sqlで一発でいけますよ。 ちょっと今から予定があるので、 後で答え合わせしよう。
kirby318

2020/10/08 10:40

ありがとうございます。とても心強いお言葉...! 私も引き続き考えます。お忙しい中ありがとうございます。
sazi

2020/10/08 11:36

MySQLのバージョンは何ですか?
kirby318

2020/10/08 12:23

mysql のバージョンは、 `5.7` です。 記載しておらず申し訳ありません。追記いたしました。
kirby318

2020/10/08 12:52

>Orlofsky様 ありがとうございます。 サンプルテーブル・レコードのクエリを追記させていただきました。
Orlofsky

2020/10/08 13:04

わたしは修正してください、と書きました。 無断に情報がダブるのは良くないです。
kirby318

2020/10/08 14:34

>Orlofsky様 大変失礼しました。おっしゃっている意味を考えて修正したのですが、認識合っていますでしょうか。。 違っていれば申し訳ありません。
guest

回答1

0

ベストアンサー

MySQL5.7だと、Window関数もWithも使えないので、非常に冗長になります。

SQL

1select id 2 , max(case when rank=1 then card_type end) as card_type_1 3 , max(case when rank=1 then created_at end) as created_at_1 4 , max(case when rank=2 then card_type end) as card_type_2 5 , max(case when rank=2 then created_at end) as created_at_2 6from ( 7 select * 8 , greatest( 9 main_rank 10 , case when main_rank>=2 and sub_rank>=2 then 3 else sub_rank end 11 ) Rank 12 from ( 13 select id, card_type, cnt, created_at 14 , ( 15 select count(*) + 1 16 from ( 17 select id, card_type, count(*) as cnt 18 from trump 19 group by id, card_type 20 ) s 21 where id = base.id and cnt > base.cnt 22 ) as main_rank 23 , ( 24 select count(*) + 1 25 from ( 26 select id, card_type, count(*) as cnt, max(created_at) as created_at 27 from trump 28 group by id, card_type 29 ) s 30 where id = base.id and cnt = base.cnt and created_at > base.created_at 31 ) as sub_rank 32 from ( 33 select id, card_type, count(*) as cnt, max(created_at) as created_at 34 from trump 35 group by id, card_type 36 ) base 37 ) step1 38) step2 39where rank <= 2 40group by id 41order by id

MySQL8.0だと、Window関数が使えるので、記述もシンプルでパフォーマンスも向上します。

SQL

1select id 2 , max(case when rank=1 then card_type end) as card_type_1 3 , max(case when rank=1 then created_at end) as created_at_1 4 , max(case when rank=2 then card_type end) as card_type_2 5 , max(case when rank=2 then created_at end) as created_at_2 6from ( 7 select *, rank() over(partition by id order by cnt desc, created_at desc) as rank 8 from ( 9 select id, card_type, count(*) as cnt, max(created_at) as created_at 10 from trump 11 group by id, card_type 12 ) step1 13) step2 14where rank <= 2 15group by id 16order by id

投稿2020/10/08 14:28

編集2020/10/12 07:58
sazi

総合スコア25329

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

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

kirby318

2020/10/08 14:47

8.0 を使用したことがなかったのですが、 Window関数は元の行はそのままで複数の行から計算した値を結果に含められるんですね。 この OVER (PARTITION BY id) の部分で指定した指定した式値で FROMやWHEREによる結果のセットを分割されて、それ元に計算されるんですね。 記述量も全然違いますね。すごいです・・・! 8.0での勉強も、自宅でしてみたいと思います。 本当にありがとうございました。大変助かり、勉強になりました!!
sazi

2020/10/12 01:14 編集

1番目のSQLは同率2位に対応できていませんでしたので、修正しました。 尚、順位の幅を広げるとしても1桁まで(9位)までです。 適宜、rankの編集部分を変更して下さい。
kirby318

2020/10/12 06:37 編集

修正いただき、ありがとうございます。 >select *, main_rank * 10 + sub_rank as rank こちらの部分を修正されたということですよね。 今試しに修正いただいたsqlを実行してみたのですが、全てnullで返ってきてしまいました... 確かに修正前のsqlだと、以下のような同率2位があった場合、 「created_at_2」で最新の作成日時(=2020-10-01 20:00:00)が返ってきてはいたのですが、 「card_type_2」が、「created_at_2」に紐づくカード種別ではなく、 もう一方("Q")の同率2位のカード種別になってしまっていました。 これが、今回の修正されたsqlで解消された、ということですよね? +----+-----------+------------------------------+ | id | card_type | created_at | +----+-----------+------------------------------+ | 2 | K | 2020-10-01 11:00:00 | | 2 | K | 2020-10-01 14:00:00 | | 2 | K | 2020-10-01 11:00:00 | | 2 | Q | 2020-10-01 10:00:00 | | 2 | Q | 2020-10-01 11:00:00 | | 2 | J | 2020-10-01 09:00:00 | | 2 | J | 2020-10-01 20:00:00 | +----+-----------+------------------------------+
sazi

2020/10/12 07:32

連絡を焦って、安易な修正になってました。手打ちで確認も取っていませんでしたし。 ちょっと見直してみます。 更に冗長になりそうですが。。。
sazi

2020/10/12 08:06 編集

修正しました。 rank 算出の所で工夫したので、あまり冗長にはならなくて済みました。 ただ、汎用的ではなく、あくまで上位2位まで限定ですが。
kirby318

2020/10/12 08:13

select id , max(case when rank=11 then card_type end) as card_type_1 , max(case when rank=11 then created_at end) as created_at_1 , max(case when rank=21 then card_type end) as card_type_2 , max(case when rank=21 then created_at end) as created_at_2 from ( select *, main_rank * 10 + sub_rank as rank from ( select id, card_type, cnt, created_at , ( select count(*) + 1 from ( select id, card_type, count(*) as cnt from trump group by id, card_type ) s where id = base.id and cnt > base.cnt ) as main_rank , ( select count(*) + 1 from ( select id, card_type, count(*) as cnt, max(created_at) as created_at from trump group by id, card_type ) s where id = base.id and cnt = base.cnt and created_at > base.created_at ) as sub_rank from ( select id, card_type, count(*) as cnt, max(created_at) as created_at from trump group by id, card_type ) base ) step1 ) step2 where rank <= 21 group by id order by id; こうでしょうか??
kirby318

2020/10/12 08:23 編集

すみません再度修正いただいたコメント確認せずに私もコメントしてしまいました・・・! 期待通りの結果が返ってきました!!再度みていただき本当にありがとうございます。 ----------------------------- greatest( main_rank , case when main_rank>=2 and sub_rank>=2 then 3 else sub_rank end ) Rank ----------------------------- ここを修正されたんですね。 これなら sub_rank の中での 1位と2位が正しく算出されていますね。 本当にとても勉強になりました。 最初のやり方でも長くはなるけどできることも理解できました。 本当にありがとうございました!!
sazi

2020/10/12 10:29

気付いて修正されているかな?とも思ったんですが、気になったのでコメントしました。 ふと気になって気付いたから良かったのですが、バグにならずに一安心しました。
kirby318

2020/10/12 11:18

気づきましたが、リファクタリングというかさらに工夫して短く書くことが全然思いつかなかったので、とっても助かりましたし、勉強になりました。 軽くテストをして実装すすめて、最後テストする際に全パターン確認したいと思います。 本当に本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問