S-JIS[2011-09-28/2012-01-21] 変更履歴

Pig Latin「foreach」

Pig Latinの「foreach」のメモ。


概要

「foreach」は、入力データを1レコードずつ処理していく命令。
項目数を増減させたり順序を入れ替えたり、計算をしたり関数を呼び出したりするのに使う。

foreach 入力元エイリアス generate 値, 値, …;
foreach 入力元エイリアス generate 値 as 名前, …;
foreach 入力元エイリアス generate 計算式, …;

条件式

generateで条件に応じて値を変えたいときは「? :」を使う。[2012-01-21]

foreach 入力元エイリアス generate (判定 ? 真のときの値 : 偽のときの値);
b = foreach a generate (hoge is null ? 0 : hoge) as foo;

フラット化

foreachの各項目では(あるいはバッグやタプルを受け取って)バッグやタプルで複数の値を出力することが出来る。
これをフラット化する(外側へ出す)にはflattenを使う。

foreach 入力元エイリアス generate flatten(値);

値がタプル(複数の項目を一項目として扱っている。いわば構造体)の場合はフラット化すると複数項目になる。

foreach 入力元エイリアス generate flatten(タプル);
foreach 入力元エイリアス generate flatten(タプル) as (項目名,…);
  foreach describe dump 備考
タプル化 b = foreach a generate TOTUPLE(col1,col2,col3) as t; b: {t: (col1: chararray,col2: chararray,col3: chararray)} ((data1,data2,data3)) TOTUPLEはタプルを作る関数。
フラット化 c = foreach b generate flatten(t); c: {t::col1: chararray,t::col2: chararray,t::col3: chararray} (data1,data2,data3) 後続処理では「t::」を除いて「col1」等で指定できる。
c = foreach b generate flatten(t) as (c1,c2,c3); c: {c1: chararray,c2: chararray,c3: chararray} (data1,data2,data3)  

値がバッグ(不定個の複数のデータを保持する。いわばリスト)の場合はフラット化すると複数レコードになる。

foreach 入力元エイリアス generate flatten(バッグ);
foreach 入力元エイリアス generate flatten(バッグ) as 項目名;
  foreach describe dump 備考
バッグ化 b1 = group a by col1; b1: {group: chararray,a: {col1: chararray,col2: chararray,col3: chararray,num: int}} (data1, {(data1,data2,data3),(data1,dataB,dataC)} groupを使うと、同じキーを持つデータのバッグが作られる。
b2 = foreach a generate TOKENIZE(col3) as tokens; b2: {tokens: {tuple_of_tokens: (token: chararray)}} ({hello,world}) TOKENIZEは、複数の文字列に分割してバッグを作る関数。
b3 = foreach a generate TOBAG(col1,col2,col3) as col; b3: {col: {chararray}} ({(data1),(data2),(data3)})
 
TOBAGはバッグを作る関数。
フラット化 c2 = foreach b2 generate flatten(tokens) as token; c2: {token: chararray} (hello)
(world)
 
c3 = foreach b3 generate flatten(col) as col; c3: {col: chararray} (data1)
(data2)
(data3)
 

ネスト

foreachでは、バッグ(値の一覧)のデータに対してネストしたブロックを指定することが出来る。(いわばサブクエリーのようなもの)

例えば入力元エイリアスがgroupの場合、foreachの中でバッグ(キー毎に集められた値の一覧)のみを対象とした演算を行うことが出来る。

foreach 入力元エイリアス {
  〜ネスト用の操作;…;〜
  generate 出力項目, …;
}

ネスト用に指定できる操作は、DISTINCTFILTERLIMITORDERのみ。(pig0.8.1)


ネストブロックを使うと、「キー毎に上位n件のデータを取得する」といったことが出来る。

/* 例 */
a = load '/tmp/pig/test.txt' using PigStorage(',') as (
  key1: chararray,
  key2: chararray,
  key3: chararray,
  num: int
);
foreach describe dump 備考
b = group a by key3;
c = foreach b {
  d = order a by num desc, key1, key2;
  e = limit d 3;
  generate flatten(e) as (key1, key2, key3, num);
}
c: {key1: chararray,key2: chararray,key3: chararray,num: int} (key2,keyA,bar,10)
(key2,keyA,bar,9)
(key2,keyB,bar,6)
(key2,keyA,foo,8)
(key2,keyA,foo,7)
(key1,keyC,foo,6)
集計キーが1項目の場合の例。
c::d: {key1: chararray,key2: chararray,key3: chararray,num: int}
c::e: {key1: chararray,key2: chararray,key3: chararray,num: int}
  ネストブロック内のエイリアスは「::」を付けて照会(describe)できる。
b = group a by key3;
c = foreach b {
  d = order a by num desc, key1, key2;
  e = limit d 3;
  generate group as key3, flatten(e.num) as num;
}
c: {key3: chararray,num: int} (bar,10)
(bar,9)
(bar,6)
(foo,8)
(foo,7)
(foo,6)
集計用エイリアスのキーであるgroupも出力項目に指定できる。
1項目だけであれば、flattenに直接指定できる。
b = group a by (key2, key3);
c = foreach b {
  d = order a by num desc, key1;
  e = limit d 3;
  generate flatten(e) as (key1, key2, key3, num);
}
c: {key1: chararray,key2: chararray,key3: chararray,num: int} (key2,keyA,bar,10)
(key2,keyA,bar,9)
(key1,keyA,bar,3)
(key2,keyA,foo,8)
(key2,keyA,foo,7)
(key1,keyA,foo,2)
(key2,keyB,bar,6)
(key2,keyB,bar,5)
(key1,keyB,foo,5)
(key1,keyB,foo,4)
(key1,keyC,foo,6)
集計キーが複数ある場合の例。
b = group a by (key2, key3);
c = foreach b {
  d = order a by num desc, key1;
  e = limit d 3;
  generate group.key2, group.key3, flatten(e.num) as num;
}
c: {key2: chararray,key3: chararray,num: int} (keyA,bar,10)
(keyA,bar,9)
(keyA,bar,3)
(keyA,foo,8)
(keyA,foo,7)
(keyA,foo,2)
(keyB,bar,6)
(keyB,bar,5)
(keyB,foo,5)
(keyB,foo,4)
(keyC,foo,6)
 

\\のパースエラー

円マーク「\」を指定したい場合は「\\」のように二重に書いてエスケープするが、使い方によってはパースエラーになる。(バグだろうなぁ)[2011-10-24]

スクリプトファイルで実行した場合に以下のようなエラーになる。
または、grunt(対話モード)で入力するとEnterを押しても入力モードが終了しなくなる。そこでCtrl+Dを押すと同様のエラーになる。

ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1000: Error during parsing. Lexical error at line 9, column 0. Encountered: <EOF> after : ""

※エラーの出る場所(エラーメッセージが指している行)はファイルの終わりの位置だが、原因は途中の「\\」

同じ例で「\」以外のケース 備考
b = foreach a generate INDEXOF($1,'\\');
OK
b = foreach a generate INDEXOF($1,':');
OK  
b = foreach a
generate INDEXOF($1,'\\');
NG
b = foreach a
generate INDEXOF($1,':');
OK 「generate」の左側にスペースを入れるとOK。[2011-10-26]
b = foreach a generate
INDEXOF($1,'\\');
NG
b = foreach a generate
INDEXOF($1,':');
OK 「generate」の右側にスペースを入れるとOK。[2011-10-26]
b = foreach a generate $0,
INDEXOF($1,'\\');
OK
b = foreach a generate $0,
INDEXOF($1,':');
OK  
b = foreach a generate
$0,
INDEXOF($1,'\\');
NG
b = foreach a generate
$0,
INDEXOF($1,':');
OK 「generate」の右側にスペースを入れるとOK。[2011-10-26]

察するに、文が途中で改行されていて、その中で「\」を使っているとこのような状態に陥るようだ。
(カンマで終わって改行されている場合は、文が続いていると見なされて正しくパースされるのだと思われる)


Pig Latinへ戻る / Pig目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま