件数や人数を数えてみる
すべての件数を数えるには、いったんグループ化することでできます。そのため、すべてのレコードに「id:1」という属性を追加し、それをグループ化して集計します(MapReduceと同じですね)。
jaql> $rank
-> transform {id:1, $.head-count}
-> group by $id_group = $.id
into {$id_group, head-count: count($[*].head-count)};
[
{
"id_group": 1,
"head-count": 530250
}
]
この結果「530,250」はレコードの件数です。すなわち、530,250位までデータがある、ということです。しかし、「だれだれちゃんは何人いる」のように、レコードには「head-count」という人数が含まれていて、これを全部足し算すると人数が分かります。先ほどと似たことをやって、人数を「sum」ビルトイン関数を使って集計するには、以下のように記述します。
jaql> $rank
-> transform {id:1, $.head-count}
-> group by $id_group = $.id
into {$id_group, head-count: sum($[*].head-count)};
[
{
"id_group": 1,
"head-count": 651777
}
]
この結果で、530,250位まで存在するが、総計651,777人いることが分かりました。
名前だけで集計し、ソートする
元データを見ていただくと、「鈴木 健太」くんだけで78人いるわけですが、これでは「健太」くんが何人いるか分かりません。そこで、苗字と名前を分離し、名前だけで集計した結果を配列にし、ソートしてランクを見てみようと思います。幸いなことに、苗字と名前の間には、全角のスペースが入っていますので、これで分けられそうです。
コマンドは以下のようになります。
jaql> $rank
-> transform
{ firstname : strSplit($.name, " ")[1],
head-count: $.head-count
}
-> group by $firstname_group = $.firstname
into {$firstname_group, total: sum($[*].head-count)}
-> sort by [$.total];
:
:
:
途中省略
:
{
"firstname_group": "美咲",
"total": 2499
},
{
"firstname_group": "翔太",
"total": 2506
},
{
"firstname_group": "拓也",
"total": 2521
}
]
これで、拓也くんが一番多い名前で、2,521人いたことが分かりました。
このコマンドは長いので、途中のJSONがどうなっているか、順を追って確認しておきましょう。
strSplit($.name, " ")[1]
まず、strSplitビルトイン関数です。これは、引数を2つ持っており、2つ目の文字列をデリミッタ(区切り文字)として1つ目の文字列を分解する関数です。結果はJSON配列となります。そこに添え字[1]を付けることによって、2つ目を取り出しています(JSON配列の添え字は0から)。つまり、全角スペース" "で苗字と名前に分け、2つ目である名前を取り出しています。
transform
transform演算子によって、新しいJSON配列が作られます。さきほど取り出した名前と、その同じレコードにあるhead-countを{ "firstname": "拓也", "head-count": 27}というようなJSONレコードにします。拓也くんは、別の姓にもいることでしょうから、何度も違うhead-countを持って出現する可能性があります。
transformが終わった時点で、JSONは以下のようなものになります。
[
{"firstname": "健太","head-count": 78},
{"firstname": "匠","head-count": 73},
{"firstname": "里奈", "head-count": 67},
{"firstname": "翔太", "head-count": 67},
{"firstname": "里奈", "head-count": 62},
:
:
以下同様
]
group by
group by は、MapReduceでいうところのシャッフルとReduce処理に相当します。前半の「$firstnem_group = $.firstname」によって、以下のような状態になります(数値はデタラメです)。
$firstname_group: "健太"
$:[
{"firstname": "健太","head-count": 1},
{"firstname": "健太","head-count": 78},
{"firstname": "健太","head-count": 51},
{"firstname": "健太","head-count": 29}
]
各行のhead-count属性の配列の数値を合計するために、「sum($[*].head-count)」を使います。$変数の配列のすべてのエントリーの「head-count」属性の値を合計する、という意味です。これをtotalという名前を付けてつなぎ合わせ、「into {$firstname_group, total: sum($[*].head-count)}」によって、最終的な形状のJSONができ上がります。あとは、totalフィールドでソートし、名前だけのランキングを出しているわけです。
まとめと期待
Jaqlは、MapReduceを簡単に記述するためのとても便利な言語です。シェルから試す場合、試行錯誤しながらMapReduceジョブを作っていくことが可能ですし、Javaで記述するような型の宣言などの整合性などで悩むことは少ないと考えられます。
