一个例子
让我们看下,用Pig Latin写一个程序计算几年中气温最大值(就像我们第二章做的事情那样),这样的简单例子。完整的程序只有几行:
-- max_temp.pig: Finds the maximum temperature by year
records = LOAD 'input/ncdc/micro-tab/sample.txt'
AS (year:chararray, temperature:int, 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 script的第一行
grunt> records = LOAD 'input/ncdc/micro-tab/sample.txt'
>> AS (year:chararray, temperature:int, quality:int);
为了简单起见,程序假设输入时tab分割的文本,每一行仅有一个年份,温度,天气质量字段。(Pig实际上有比这种格式的输入,有更多的灵活性,稍后会见到)这行描述了我们将要处理的数据。chararray 对应java的string等。LOAD操作符使用URI作为参数;这里我们仅使用一个本地文件,但是可以指向HDFS URI。
AS语句(可选择的)给字段起了个别名,以便在后续语句中引用方便。LOAD操作符的结果,实际上,Pig Latin中的任何操作符的结果,都是一元数组。一元数组就像是数据库表中的一行,多个字段以特定的顺序排列。在这个例子中,LOAD函数,产生输入文件的一元数组。我们写了每行一元数组的关系,一元数组是以常用的圆括号作为分隔符的。
(1950,0,1)
(1950,22,1)
(1950,-11,1)
(1949,111,1)
关系被给了名字或者别名,因此它们可以被引用。我们可以使用DUMP操作符,检查一个别名的内容。
grunt> DUMP records;
(1950,0,1)
(1950,22,1)
(1950,-11,1)
(1949,111,1)
(1949,78,1)
我们同样可以看到,数据关系的结构或者模式,通过DESCRIBE命令
grunt> DESCRIBE records;
records: {year: chararray,temperature: int,quality: int}
下面的语句删除了不满足要求的数据。
grunt> filtered_records = FILTER records BY temperature != 9999 AND
>> (quality == 0 OR quality == 1 OR quality == 4 OR quality == 5 OR quality == 9);
grunt> DUMP filtered_records;
(1950,0,1)
(1950,22,1)
(1950,-11,1)
(1949,111,1)
(1949,78,1)
下面的语句使用GROUP函数根据year字段分组。
grunt> grouped_records = 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)})
现在,我们有了两行数据。在数据中,一个是每年的输入数据。第一个字段是year字段,第二个字段是那一年的一元数组。一个bag仅仅是一元数组的集合,在Pig Latin中,使用大括号来表示。
通过以这种方式,分组数据,我们创建了每年的数据,因此,所有剩下的事情就是找到每个bag中一元数组中的最大值。在做次之前,让我们理解下grouped_records relation的结构:
grunt> DESCRIBE grouped_records;
grouped_records: {group: chararray,filtered_records: {year: chararray,
temperature: int,quality: int}}
这就告诉了我们,分组字段是被Pig重命名了,并且第二个字段与filtered_records的结构是一样的。有了这个知识以后,我们可以尝试第四种转换:
grunt> max_temp = FOREACH grouped_records GENERATE group,
>> MAX(filtered_records.temperature);
FOREACH 处理每一行来生成行的派生集,使用GENERATE语句来定义每一个生成的行的字段。在这个例子中,第一个字段分组字段,就是year字段。第二个字段有一点复杂。grunt> DUMP max_temp;
(1949,111)
(1950,22)
grunt> max_temp = FOREACH grouped_records GENERATE group,
>> MAX(filtered_records.temperature);
这样,我们成功的计算出了每年温度的最大值。
生成例子
在这个例子中,我们使用了小的,仅有几行的数据集,作为例子,这样是为了跟踪数据流和debug的方便。创建一个缩减的数据库是一项艺术,数据库中的数据应该覆盖执行查询的所有丰富的数据(完整属性)。使用一个随机的例子,通常情况下不能工作的,因为,join和filter操作趋向于删除所有的随机数据,留下一个空的结果,是没有说明的一般流程。
ILLUSTRATE操作符,Pig提供了一个工具产生相当完整和简洁的数据集。尽管,它不能生成所有查询的例子(它不支持 LIMIT, SPLIT, 或者FOREACH语句 ),它可以生成对很多查询有用的例子。ILLUSTRATE在仅当数据关系中仅有一个模式的时候。
与数据库的比较
已经看过了Pig的操作,看起来,Pig Latin与SQL有点类似。GROUP BY 和 DESCRIBE操作的存在,更是强化了这种印象。然而,在这两种语言中,有几个区别,通常情况下,是Pig和RDBMS之间的区别。
最重大的区别是,Pig Latin是一个数据流编程语言,而SQL是一个声明式编程语言。换句话说,Pig Latin程序是一步一步的操作。每一步都是单独的转换。但是,SQL语句是一组约束条件,定义了输出。在许多方面,Pig编程就像在一个RDBMS查询规划上面,画出了,怎样声明语句到一个系统中。
RDBMSs把数据存储在表中,有一个预先定义的模式。Pig对它处理的数据就比价轻松些,你可以在运行时设置数据的模式,但是这是可选的。本质上,它会操作任何类型的一元数组(尽管,当有多个文件的时候,元数据应该支持平行的读取),这里,UDF被用来读取原始数据的一元数组。
Pig的对复杂的支持,嵌套的数据结构,区别于SQL,SQL 的操作符信奉结构。同时,Pig的使用UDFS和流操作的能力和在PIg语言中是紧密联系在一起的,这使得Pig Latin比SQL语言更加自定义化。
线上支持有几个特点,RDBMS的查询的低延迟在Pig中是没有的,例如事务性和索引。就像之前提到过的一样,Pig不支持随机在几十毫秒以内完成读取、查询。它也不支持随机的写更新数据的一小部分,所有的写入都是成块的,都是流写入,就像Mapreduce一样。
Hive的定位则是Pig和RDBMS之间。像Pig一样,Hive被设计,使用HDFS作为存储系统,但是,它们之间有些重大的不同。Hive的查询语言,HiveQl是基于SQL的,并且,任何熟悉SQL的人,使用HiveQl没有多大问题的。就像RDBMSs,Hive中,所有的数据都存储在表中,表是有结构的;但是,它可以和之前存在HDFS中的数据进行联接,因此load步骤是可选的。Hive不支持低延迟查询,这是它与Pig的一个共性。