PigのNested Block

Hiveと比べるとややマイナー感が漂うPigですが試してみると、「おお、これはちとHiveだとやりづらいけどPigだと楽かもなー」というのがあります。Nested Blockはその代表な気がするので書いてみます。

今回は単純なアクセスログ解析を例とします。

入力データとしては下記を考えてみます。URL、ユーザ、順番(本来ならタイムスタンプのほうがいい例だと思いますがPigにタイムスタンプ型が無いので単純にナンバリングしてます)とあります。

aaa.com	user1	1
bbb.com	user2	2
ccc.com	user3	3
bbb.com	user1	4
ccc.com	user1	5
aaa.com	user1	6
aaa.com	user2	7
aaa.com	user3	8
aaa.com	user4	9
aaa.com	user5	10

URLごとのPVを求めるならこんな感じのPigクエリになるでしょう。3行目のgroupというのはPigによって与えられるエイリアスで、グループ化に使われたフィールドだと思ってください。今回の例だとurlですね。

accesslogs = LOAD 'input/accesslog.txt' USING PigStorage() AS (url:chararray, name:chararray, sequence:int);
grp = GROUP accesslogs BY url;
pv = FOREACH grp GENERATE group, COUNT(accesslogs.name);

実行結果はこんな感じ。

(aaa.com,6)
(bbb.com,2)
(ccc.com,2)

Hiveならこんな感じでしょうか。試してないので動くかわかりませんw

select url, count(name) from accesslogs group by url;

まあこれぐらいならHiveでもPigでも差が出ないですね。

次はURLごとのUU数を計算してみましょう。

Hiveならこんな感じでしょうか。試してないので(ry

select url, count(distinct name) from accesslogs group by url;

Pigだとこんな感じ。これがNested Blockの例です。3行目以降がそれでFOREACHでイテレートする際に個々の要素をいじれます。

accesslogs = LOAD 'input/accesslog.txt' USING PigStorage() AS (url:chararray, name:chararray, sequence:int);
grp = GROUP accesslogs BY url;
uu = FOREACH grp {
  user = accesslogs.name;
  distinctUser = DISTINCT user;
  GENERATE group, COUNT(distinctUser);
}

実行結果はこんな感じ。

(aaa.com,5)
(bbb.com,2)
(ccc.com,2)

で、ぶっちゃけこれだけだったらHiveでもいいじゃんとなるわけですが、あるキーでグループ化してそのキー毎に上位n件のデータを取得するとかなるとHiveだと難しくなります。いわゆるウィンドウ関数ですね。SQLだとGROUP BYで指定したカラム以外をSELECT句には書けない(集計関数を除く)わけでHiveもそれに準じているわけですが、Pigの場合はグループ化した後の結果集合の情報も保持しているのでその個々の要素にもアクセスできます。

具体的にいうと

accesslogs = LOAD 'input/accesslog.txt' USING PigStorage() AS (url:chararray, name:chararray, sequence:int);
grp = GROUP accesslogs BY url;
DUMP grp;

というクエリを流すと

(aaa.com,{(aaa.com,user1,1),(aaa.com,user1,6),(aaa.com,user2,7),(aaa.com,user3,8),(aaa.com,user4,9),(aaa.com,user5,10)})
(bbb.com,{(bbb.com,user2,2),(bbb.com,user1,4)})
(ccc.com,{(ccc.com,user3,3),(ccc.com,user1,5)})

という結果になります。urlでグループ化した後の結果集合の情報も保持してますね。

ここでURL単位でアクセスした順番の上位3件を出したいとします。

その場合

accesslogs = LOAD 'input/accesslog.txt' USING PigStorage() AS (url:chararray, name:chararray, sequence:int);
grp = GROUP accesslogs BY url;
seq = FOREACH grp {
  ord = ORDER accesslogs BY sequence;
  li = LIMIT ord 3;
  GENERATE FLATTEN(li);
}

というクエリを実行すれば下記のようにうまく結果が得られます。

(aaa.com,user1,1)
(aaa.com,user1,6)
(aaa.com,user2,7)
(bbb.com,user2,2)
(bbb.com,user1,4)
(ccc.com,user3,3)
(ccc.com,user1,5)