示例
现在,我们用Pig Latin写一个计算天气数据集中年度最高气温的程序(和第2章用 MaPRedUCe写的程序功能相同),作为示例。这个程序只需要很少几行代码:
-- max_temp.pig:Finds the maximum temperature by year records = LOAD 'input/ncdc/micro-tab/sample.txt'AS (year:chararray, temperature:intj quality:int); filtered_records = FILTER records BY temperature ! =9999 AND(quality == 0 OR quality == 1 OR quality == 4 OR quality == 5 OR quality 9); grouped_records = GROUP filtered_records BY year; max_temp = FOREACH grouped_records GENERATE group, MAX(filtered_records.temperature);DUMP max_temp;
为了看每一行到底做了什么事情,我们将使用Pig的Grunt解释器。它让我们能够 输入几行代码,然后通过交互,理解程序到底在做什么。我们在本地模式下启动 Grunt,然后输入Pig脚本的第一行:
grunt> records -LOAD ,input/ncdc/micro-tab/sampXe.txt'>> AS (year:chararray, temperature:int, quaXity:int);
为了简单起见,程序假设输入,由制表符分割的文本,每行只包含年度、气温和质 量三个字段。事实上,Pig的输入格式处理能力比这个灵活得多,我们在后面会看 到。这行代码描述了我们要处理的输入数据。Year:chararray描述了字段的名称 和类型。chararray和Java字符串类似;int和Java类似。LOAD操作接 受一个URI参数作为输人。在这个示例中,我们只使用了一个本地文件。在 LOAD中,我们也可以使用HDFS URI。AS子句(可选)设定了字段的名称,以便在随后的语句中更方便地引用它们。
LOAD操作的结果Pig Latin其他所有操作的结果一样,都是一个关系(relation), 即一个元组的集合。一个元组类似于数据库表中的一行数据,包含按照特定顺序排 列的多个字段。在这个示例中,LOAD函数根据输入文件,生成一组(年份,气 温,质量)元组。我们把关系写成每个元组一行,每行由小括号括起,毎项字段由 逗号分割:
(1950,0,1)(1950.22.1)(1950,-11,1)(1949.111.1)
关系要有名称或“别名”以便于引用。这个关系的别名是records。我们可以使 用DUMP操作来查看某个别名所对应关系的内容:
grunt> DUMP records,(1950,0,1)(1950.22.1)(1950,-11,1)(1949,llljl)(1949.78.1)
我们也可以使用DESCRIBE操作查看一个关系的结构,即关系的“模式”(schema):
grunt> DESCRIBE records;records: {year: chararray,temperature: intjquality: int}
从输出可以知道,records有三个字段,别名分别为year, temperature和 quality。字段的别名是在AS子句中指定的。同样,字段的类型也是在AS子句 中指定的。关于Pig中的数据类型,我们后面会有更详细的介绍。
第二条语句去除了没有气温的记录(即用9999表示气温的记录)和质量不令人满意 的记录。在这个示例里,并没有记录被过滤掉:
grunt> filtered_records =FILTER records BY temperature ! =9999 AND >> (quality 0 OR quality 1 OR quality 4 OR quality S OR quality 9); grunt> DUMP filtered_records;(1950,0,1) (1950j22,l)(1950,-11,1)(1949jlll,l)(1949,78,1)
第三条语句使用GROUP函数把records关系中的记录按照year字段分组。让我 们用DUMP查看GROUP的结果:
grunt> grouped_re^ords =GROUP filtered_records BY year; grunt> DUMP grouped_records;(1949,{(1949,111,1),(1949,78,1)})(1950,{(1950,0,1),(1950,22,1),(1950,-11,1)})
这样,我们就有了两行,或叫两个元组。每个元组对应于输入数据中的一个年度。 每个元组的第一个字段是用于进行分组的字段(即年度)。第二个字段是该年度的元 组的“包”(bag)。包(bag)是元组的无序集。在Pig Latin里,包用大括号表示。
通过这样对数据进行分组,我们已经为每个年度创建了一行。剩下的事情就是在每 个包里找到包含最髙气温的那个元组。在做这件事情之前,我们首先要理解 grouped_records这一关系的结构:
grunt> DESCRIBE grouped_records;grouped_records: {group: chararray,filtered_records: {year: chararray, temperature: int,quality: int}}
从输出结果可以看到,Pig给分组字段分配了别名group。第二个字段和被分组的filtered_records关系的结构相同。根据这些信息,我们可以试着执行第四条语 句,对数据进行变换:
grunt> max_temp =FOREACH grouped_records GENERATE group,>> MAX(filtered_records.temperature);
FOREACH对每一行数据进行处理,并生成一组导出的行。导出的行的字段由GENERATE子句定义。在这个示例中,第一个字段是group,也就是年度。第二个字段稍微复杂一点。filtered_records.temperature引用了 grouped_records关系中的filtered_records包中的temperature字段。MAX是计算字段包中最大值的内置函数。在这个例子中,它计算了 filterecLrecords包中temperature字段的最大值。我们看一下它的结果:
grunt> DUMP max_temp;(1949,111) (1950,22)
这样,我们便成功计算出每年的最高气温。
生成示例
在这个示例中,我们已经使用了仅包含少数行的抽样数据集,以简化数据流跟踪和 调试。创建一个精简的数据集是一门艺术。理想情况下,这个数据集的内容应该足 够丰富,能够覆盖查询中可能碰到的各种情况(满足完备性条件),同时,这个数据 集应该足够小,能够被程序员用来进行推导和演算(满足简明性条件)。通常情况 下,使用随机取样并不能满足要求。因为连接和过滤这两个操作往往会去除掉所有 的随机取样的数据,而获_一个空的结果集。这样是无法获取典型数据流的。
Pig提供了ILLUSTRATE操作,能够生成相对完备和简明的数据集。虽然它并不能 为所有查询生成示例(例如,它不支持LIMT、SPLIT或嵌套的FOREACH语句), 它能够为很多査询生成有用的示例。只有当一个关系有模式时才能使用 ILLUSTRATE。
下面列出的是运行lLLUSTRATE后的输出(进行了少许格式重排):
grunt> ILLUSTRATE max_temp;
注意,Pig既使用了部分的原始数据(这对于保持生成数据集的真实性很重要),也 创建了一些新的数据。Pig注意到査询中9999这一值,所以创建了一个包含该值 的元组来测试FILTER语句。
综上所述,ILLUSTRATE的输出本身易于理解,而且也能帮助你更好地了解查询 的执行过程。