14.7.2 Pig数据分析
下面我们将对学生表、课程表和选课表进行数据分析操作。这一小节将分三个部分,分别计算学生的平均成绩、找出有不及格成绩的学生和找出修了先修课为“C Language”的学生。在语法上,Pig Latin虽然没有关系数据库中的关系操作语言强大,但是因为Pig系统架设在Hadoop的云平台之上,所以在处理大规模数据集的时候,Pig的效率却非常高。
1.计算每个学生的平均成绩
这里要求计算出每个学生的平均成绩,并且输出每个学生的姓名及其平均成绩。
我们先对数据进行分析。很容易看出,我们需要对学生表和选课表进行操作。首先,需要对学生表和选课表基于学号字段进行连接;然后,基于学号对学生数据进行操作,这时需要对每个学生所有的课程成绩分别求和,并除以课程总数;最后,按格式输出结果。
对于传统的关系型数据库的关系操作语言来说,为了实现这个目标,我们需要AVG运算和GROUP运算同时使用,十分方便。下面,我们就Pig Latin语言给出相应的操作。
1 从源数据文件学生表和选课表中读取数据
2 对学生表和选课表基于学号字段进行连接操作
3 基于学号对连接生成的表进行分组操作
4 计算每个学生的平均成绩
上面是对操作的描述,接下来需要对上述描述用Pig Latin语言来实现。
(1)读取数据
MapReduce在Hadoop的HDFS文件系统中对数据进行操作,所以需要复制要操作的数据到HDFS中:
copyFromLocal Student Student;
copyFromLocal SC SC
可以使用Hadoop的ls命令查看数据是否复制成功,确认后再读取数据:
A=load'Tmp/Student'using PigStorage(':')as(Sno:chararray, Sname:chararray, Ss
ex:chararray, Sage:int, Sdept:chararray);B=load'Tmp/SC'using PigStorage(',')
as(Sno:chararray, Cno:chararray, Grade:int);
(2)连接操作
使用JOIN关键字对A、B两组数据基于Sno字段进行连接操作。JOIN关键字的语法如下:
alias=JOIN alias BY{expression|'('expression[,expression……]')'}(,alias BY
{expression|'('expression[,expression……]')'}……)[USING'replicated'|'skewed'
|'merge'][PARALLEL n];
下面是连接操作的命令:
D=Join A By Sno, B By Sno;
这里我们可以使用DUMP关键字来查看D中存储的数据,如图14-4所示。
图 14-4 对学生表和选课表进行连接操作后的结果
(3)分组操作
在进行分组操作之前,我们先提取必要的数据,这样不但减少了需要处理的数据量,而且让我们的操作更加简单。接着,我们基于学号字段对连接操作后的数据进行分组,如下所示:
E=Foreach D generate A:Sno, Sname, Grade;
F=Cogroup E By(Sno, Sname);
我们再使用DUMP关键字查看一下F中的数据,如图14-5所示。接着用DESCRIBE分析F的模式,如图14-6所示。
图 14-5 F中的数据
图 14-6 F的模式
(4)计算学生的平均成绩
我们使用SUM关键字对学生成绩进行求和,使用COUNT关键字来计算课程的总数:
G=Foreach F Generate group.Sname,(SUM(E.Grade)/COUNT(E));
下面,我们查看一下最终的结果,如图14-7所示:
图 14-7 学生平均成绩
因为Grade字段的数据类型为int,所以这里计算出的结果均为向下取整后的值。如果想要得到更为准确的数据,大家可以将Grade字段的数据类型设为Long或Float。
2.找出有不及格成绩的学生
这部分要求找出有不及格成绩的学生,并且输出学生的姓名和不及格的课程和成绩。
现在对问题进行分析。我们需要使用学生表来获取学生的姓名,使用课程表来获取学生的成绩和对应成绩的课程。
首先,我们还是需要读取源数据,然后使用连接字段将数据连接在一起,接着使用FILTER关键字过滤出我们需要的数据,最后提取需要的字段将数据输出。
这里我们不再像上面那样一步步地对数据进行分析了,下面给出Pig Latin操作语句:
A=load'/pigTmp/Student'using PigStorage(':')as(Sno:chararray, Sname:chararra
y, Ssex:chararray, Sage:int, Sdept:chararray);—读取学生表
B=load'/pigTmp/SC'using PigStorage(',')as(Sno:chararray, Cno:chararray, Grade
:int);—读取选课表
C=load'/pigTmp/Course'using PigStorage(',')as(Cno:chararray, Cname:chararray,
Cpno:chararray, Ccredit:int);—读取课程表
D=Filter B By Grade<60;—提前对B进行分析,过滤出需要的结果,减少操作的数据量
E=Join D By Sno, A By Sno;—连接操作
F=Join E By Cno, C By Cno;—连接操作
G=Foreach F Generate Sname, Cname, Grade;—输出结果
最后我们使用DUMP命令查看操作的结果,如图14-8所示:
图 14-8 不及格成绩的学生
3.找出修了先修课为“C Language”的学生
这里要求找出修了先修课为“C Language”的学生,并且输出学生的姓名。
现在,我们先对问题进行分析,从课程表的数据结构可以看出:我们需要找出“C Language”这门课的课程号,然后找对应“Cpno”(此课程号的课程),最后找出修了此门课程的学生,并输出学生的姓名。
Pig Latin语言支持嵌套的操作,所以在这一部分,我们使用嵌套语句来对数据进行操作,这样能够使Pig Latin语言的书写更加简便,更加有便于理解。因为嵌套的语句能够使程序的执行更加有层次感,使我们理解起来一目了然。
为了让大家便于理解,我们给出单步的操作:
A=load'/pigTmp/Student'using PigStorage(':')as(Sno:chararray, Sname:chararra
y, Ssex:chararray, Sage:int, Sdept:chararray);
B=load'/pigTmp/SC'using PigStorage(',')as(Sno:chararray, Cno:chararray, Grade:int);
C=load'/pigTmp/Course'using PigStorage(',')as(Cno:chararray, Cname:chararray,
Cpno:chararray, Ccredit:int);
D=load'/pigTmp/Course'using PigStorage(',')as(Cno:chararray, Cname:chararray,
Cpno:chararray, Ccredit:int);
E=Join C By Cpno, D By Cno;—连接数据
F=Filter E By D:Cname=='C Language';—过滤出先修课名为C Language的记录
G=Foreach F Generate C:Cno;—找出先修课为C Language课程的课程号
H=Join G By Cno, B By Cno;—选课表和C Language课程的课程号做连接操作
I=Join H By Sno, A By Sno;—选课表与目标课程号连接结果与学生表作连接操作
J=Foreach I Generate Sname—输出结果
可以明显地看出,上面的操作十分地繁琐,下面我们将上面的语句嵌套起来。
因为等号左面和右面的操作是完全等价的,也就是说,可以将模式名用对应的表达式替换。比如对于下面的句子:
E=Join C By Cpno, D By Cno;—连接数据
F=Filter E By D:Cname=='C Language';—过滤出先修课名为C Language的记录
我们可以这样写:
F=Filter(Join C By Cpno, D By Cno;)By D:Cname=='C Language';—过滤出先修课名为C Language的记录
所以,这一问题可以按下面的Pig Latin语句来进行操作:
A=load'/pigTmp/Student'using PigStorage(':')as(Sno:chararray, Sname:chararra
y, Ssex:chararray, Sage:int, Sdept:chararray);
B=load'/pigTmp/SC'using PigStorage(',')as(Sno:chararray, Cno:chararray, Grade:int);
C=load'/pigTmp/Course'using PigStorage(',')as(Cno:chararray, Cname:chararray,
Cpno:chararray, Ccredit:int);
D=load'/pigTmp/Course'using PigStorage(',')as(Cno:chararray, Cname:chararray,
Cpno:chararray, Ccredit:int);
E=Foreach(Filter(Join C By Cpno, D By Cno)By D:Cname=='C Language')
Generate C:Cno;
F=Foreach(Join(Join B By Cno, E By Cno)By Sno, A By Sno)Generate Sname;
当然,如果想一步执行完也是可以的,只需要将上面操作的后两步再嵌套起来即可:
A=load'/pigTmp/Student'using PigStorage(':')as(Sno:chararray, Sname:chararra
y, Ssex:chararray, Sage:int, Sdept:chararray);
B=load'/pigTmp/SC'using PigStorage(',')as(Sno:chararray, Cno:chararray, Grade:int);
C=load'/pigTmp/Course'using PigStorage(',')as(Cno:chararray, Cname:chararray,
Cpno:chararray, Ccredit:int);
D=load'/pigTmp/Course'using PigStorage(',')as(Cno:chararray, Cname:chararray,
Cpno:chararray, Ccredit:int);
E=Foreach(Join(Join B By Cno,(Foreach(Filter(Join C By Cpno, D By Cno)By
D:Cname=='C Language')Generate C:Cno)By Cno)By Sno, A By Sno)Generate Sname;
下面,我们使用DUMP关键字来分别对上面三种方式查看下运行结果,发现输出结果是完全相同的,如图14-9所示:
图 14-9 修了先修课为“C Language”的学生
14.6 节通过一个简单的例子,让用户了解如何在Local模式和MapReduce模式下对数据进行操作。14.7节则进一步通过一组复杂的例子,对如何使用Pig Latin语言进行复杂的操作做了更深入的介绍。
从14.6和14.7这两节实例操作中,我们可以看出,Pig Latin语言更擅长对海量数据进行分析。另外,Pig Latin语言还支持嵌套的操作,这样可以让Pig Latin语言编写的程序更加易于理解。
鉴于Pig Latin语言的如上特点,我们可以使用Pig于对诸如日志等规则的、海量的并且需要定期维护的数据进行分析处理操作,这样可以大大地提高系统的工作效率。