http://qiita.com/advent-calendar/2015/gaiax
初めまして、GaiaxAdventCalender 4日目担当の技術開発部の金田と申します。
既に熱い記事ばかりなので後続の人たちのハードルを下げる為に小ネタで行きたいと思います。
GROUP_CONCATという関数がMySQLにあるんですがそれがアツいのでみんなに教えたいという記事です。
1対多の関係のとき
普通にテーブル設計してると1対多の関係(has many)になるリレーションシップをテーブル間で張ることが多いですよね。
まぁこんな感じで。
ざっくりと記事に対して複数のタグが設定できるような作りを想定してみます。
Entryから出た線がTagは複数に枝分かれしているのがhas manyの関係です。
で、こんなデータが入ってます。
取得するとき
記事と、それに付随したタグの一覧を取得したいときは当然JOINして
こうします。
結果としてはコレで間違ってないんですが、記事は2件しか無いのにレコードは5件出ちゃってます。
なので大抵はGROUP BYします。
でもこれだとタグが先頭の物しか出てこなくなってしまって、結局もう1回記事のIDでTagテーブルをSELECTする必要がありますよね。
O/Rマッパーを使っているとその辺のリレーションシップとhas_manyの関係は全てオブジェクトで表現されると思うのでEntryオブジェクトのメンバとして関連するTagオブジェクトのArrayが入ってるような感覚で良いんですが、内部的には全体のリレーションを表現するために複数回クエリが呼ばれてます(少なくともPerlのDBIx::Classとかではそう)。
そこでGROUP_CONCAT
複数回クエリ呼べばいいじゃんって言われたらそこで話終わっちゃうんでやめてください。
今書いてきたケースだと1クエリの中でJOINしてGROUP BYするとhas_manyの関係のデータ埋もれちゃうから一気に取るのは無理よねって話でしたがMySQLにはGROUP_CONCATとかいう関数があります。早速使ってみます。
うおおおお、グループ化されて埋もれちゃったカラムを1つの文字列としてカンマで連結して返せているーーー。(説明台詞)
https://dev.mysql.com/doc/refman/5.6/ja/group-by-functions.html#function_group-concat
ちなみにセパレーターをカンマ以外に変更したり、GROUP_CONCAT内でソートも出来ます。
集約関数(COUNTとかMAXとかと同じ)なので1クエリで完結しているのがポイントです。
使い所
O/Rマッパー使ってるならあんまり無いです。
自力でアプリからクエリを送信して結果をレコード毎に振り分けて構造化する際にGROUP_CONCATで取ると1クエリで取得できて、splitするだけなので非常に便利。
その場で作った文字列なのでインデックスは効きませんが、カンマ区切りなのでFIND_SET関数で絞り込むこともできます。
今回の例みたいな単純なデータ構造ならそもそもEntryテーブルにカンマ区切りでタグを入れるカラムを作れば良いんですが、タグ自体に個別情報をもたせたりしたい場合はそうも行かないので有効です。
現場からは以上です。
今年の年末は狩りのほうが忙しいです。
明日は!
弊社インターン生の笠井さんです!若いです!
よろしくお願いします!