集計をする上で必要となるgroup byですが、MySQLではちょっと変わった形の(SQL標準とは挙動が異なる)実行することもできます。MySQL 5.6以前のMySQLを利用した方の中には、お世話になった人も現在奮闘されている方も多いのではないかなと思います。
今回はMySQLの拡張されたgroup byについて説明していきたいと思います。
検証環境
今回は第125回 phpMyAdminでDockerで建てたMySQLにアクセスするで記載したdocker-composeを利用して作成します。手元で簡単に試せるように、GitHubのわたしのレポジトリにサンプルコードとして置いてあるので、気軽に試したい方はgit cloneして試してみてください。試すにはdockerとdocker-composeが必要です。
group byのMySQL拡張
今回の検証はMySQLの独自拡張を利用することになるため、第18回 MySQL5.7のデフォルトのSQLモードを確認してみるで紹介したONLY_FULL_GROUP_BYを無効にする必要があります。
まずは今回紹介したいgroup by拡張ですが、以下のようなクエリを実行することができます。
SQLモードを設定せずに上記のSQLを現行のMySQLで実行すると、以下のようにエラーとなります。
SELECTのリストの中でGROUP BYの中に含まれていないカラムと集約関数を通してないカラムが含まれているためエラーになっていて、これはsql_mode=only_full_group_byと非互換であると書かれています。
では、sql_modeを空っぽにしてみましょう。
SQLモードを指定する方法は、第60回 SQLモードについて[その1]でglobalセッションに指定する方法と、第31回 MySQLのオプションファイル my.cnfの豆知識[その1]で、my.cnfを指定する方法を紹介してきました。
手元で試してみたい場合は、globalセッションで登録してしまうと良いかもしれません。今回は、my.cnfをdocker-composeで指定する方法を紹介します。
docker-composeでmy.cnfを設定する
まずはじめに、現状のSQLモードについて確認してみましょう。
以下のように、mycnfディレクトリに設定したいmy.cnfを配置しました。
今回のmy.cnfの内容は、以下の通りになります。
docker-compose.ymlのvolumesで、上記のマウントの設定を追加します。
docker-composeを再起動して、sqlモードを確認してみましょう。
ということで、空にすることができました。
group byのクエリを実行してみる
さて、はじめにエラーとなってしまったSELECT文のクエリを実行してみましょう。
ということで、SQLモードを変更する前と違って実行することができました。
さて、ここで問題です。今回実行したSQLで*
で取れた行の値はどうやって決まるのでしょうか?
この問題を考える前に、まずzip_codeでダブリがどれくらいあるのか見てみましょう。
0040000の地域は、2個KEN_ALL上に登録されていることがわかります。この地域を例に、以下のようにwhere句でzip_codeが0040000を確認してみましょう。
北海道札幌市の厚別区か清田区の「以下に掲載が無い場合」となっています。では、これに対してgroup byをして結果を取得してみたら、どちらが出るかわかりますか?
今回は厚別区が出ましたが、実はこの挙動は保証されていません。別の環境で実行を行った際には結果が異なることもあり得る、という恐ろしい結果になります。なので、このクエリを実行する際には、結果が不定であるというのを踏まえて実行する必要があります。
不定であることが望ましくない場合には、注意する必要があります。ですが、不定ではないと言い切るには、データセットへの限りない理解が必要で、たとえば郵便番号のデータでよくあるうっかりミスの例として、郵便番号には県をまたぐような場合が無いと勘違いをして、郵便番号から県名を取得するような処理を書こうとしてしまい、おかしなことになってしまう場合などが考えられます。
実際は、県をまたぐ郵便番号が以下のように3個ほどあるのがわかります。
それぞれ愛知と三重、大阪と京都、福岡と大分の県境にある市区町村で共有していることがわかります。
この状態で郵便番号から県名を取得する際に以下のようにしてしまうと、三重県なのに愛知県と表示されてしまったりすることに繋がり、バグとなります。
このように、理解していると思いこんでいるデータセットでも例外となるケースがあったりするため、常に考慮漏れが潜んでいる可能性があります。
また、KEN_ALLに関しては、今後データが増えることが無いと思われるので問題にはなりませんが、今後データが増えるような場合には注意が必要となります。
そのため、できる限りonly_full_group_byを利用して、結果が不定とならないようなクエリを作成するように心がけてください。現在only_full_group_byを利用している場合には、外さないように注意をしましょう。
まとめ
今回紹介したMySQLのgroup by拡張ですが、基本的にこれらは便利ではありますが、データセット次第で結果が不定になってしまう可能性があるため、利用を避けたほうが良いです。現在のデータセットで問題が起きないとしても、将来何らかの問題が発生してしまう可能性もあるため、できる限り使用を避けたほうが良いと思います。
MySQLでは、5.7以降ではSQLモードでデフォルトで使用できなくなってはいますが、それ以前のバージョンで作成していたアプリケーションでMySQLをアップグレードした場合に、SQLの書き換えなどの対応が取れずに無効にしている場合もあるかと思います。そういった場合には、どこかのタイミングでぜひ一度、SQLモードの見直しを進めてみてはいかがでしょうか。