【MySQL5.7】相関サブクエリで、グループ単位で所属レコードに採番する方法

mysql

仕事で久しぶりに相関サブクエリを見たので、備忘録の意味合いも込めて記事に残しておく。検証したMySQLバージョンは5.7.26。

相関サブクエリとは

サブクエリが何かを分かっている前提で解説する。簡単に言うと、サブクエリの外側のクエリをサブクエリ内で使うことができるというもの。例としては下記のような書き方ができる:

パッと見サブクエリの中で外側のクエリを使うことができるのはちょっと気持ち悪い感じもあるが、

  1. 非相関サブクエリ(=通常のサブクエリ)の時はSQLをネストの一番深いところから解読する
  2. 相関サブクエリの時は、SQLの流れを一番外側から解読する

という感じで読み分ければ解読しやすい(ただ、フレームワークのORMで相関サブクエリを使い始めると解読辛め)。

相関サブクエリなら、グループ単位で所属レコードに採番できる

例えば著者毎に記事それぞれに採番をしたいとしよう。登場するテーブルはauthors, articlesの2つ。

 

著者は全部で3人いるとする:

 

記事は全部で6記事。それぞれの著者が執筆しているとする:

 

さて、著者毎に記事それぞれに採番をしたいというニーズはすなわち、

こんな結果が帰ってきてほしいというわけである。

 

この場合、まずはどの著者にどの記事が結びついているかが分かりたいのでgroup byの使用を思い描く思うが、group byは集計用関数なのですぐに違う方法を検討しなければならないと気付くのではないか。

となると次は自己結合だと思うわけだが、どうも単に自己結合しただけでは上手くいかなそうな気配がある。articles1レコードずつに対して採番したいのだからサブクエリ、それも相関サブクエリを使ったほうが良さそうだと連想する必要がある。

 

試しに下記SQLを実行してみる:

この場合、ar1とar2をauthor_id単位で直交させた結果のカウントがar1_numとして反映されている。ar1_numをあともう1段階加工すれば良さげな感じだ。

次は、ar2.idとar1.idに何かしらの関係性を持たせるのはどうかと考える。直交結果をCOUNT()しないで考えた場合、

上記のような間柄になっている。

例えばar2.id > a1.idという条件を付与したとしたらどうだろうか?

これは何か使えそうな気配。

この条件を付与した状態で、ar1_numのCOUNT()を取ってみる。

お、ということはar1_numに+1すれば採番になるんじゃない?

ってことで、ar1_num + 1の名前をrow_numとして

ようやく欲しい結果にたどり着いた。

 

ただ、相関サブクエリは結構重めのSQLになりがちなので、採用する際はexplainしたり実際のデータに対してSQL投げてみてどうかを確かめる等、入念に検討したほうが良い。

MySQL8系からはROW_NUMBER()関数で代用できる

なんと、MySQL8なら同じことが相関サブクエリを使わずROW_NUMBER()を使えば済むとのこと。むう、技術の進歩には日々ついてかないと大変だ。