实现student和course数据表的join操作,以学生编号(sno)为连接字段
测试数据
- student.txt文件
#以一个空格分隔 #学生编号 姓名 #sno sname 01 lily 02 tom 03 jack 04 rose
course.txt文件
#以一个空格分隔 #学生编号 课程名 课程成绩 #sno cname grade 01 English 80 01 Math 90 02 English 82 02 Math 95
最终reduce端连接结果
sno sname cname grade 01 lily English 80 01 lily Math 90 02 tom English 82 02 tom Math 95
思路1
按照不同的文件输出时对数据添加标记,然后通过配置map阶段的排序字段,保证学生信息出现在课程成绩信息的最上面,reduce中,发现是学生信息时,对后面接收的课程信息进行join并输出。步骤如下:
1:map端有多个输入文件,以os.environ[‘map_input_file’]获取输入的文件信息,如果是student.txt文件的输入,则在输出中增加一个’0’,是course.txt的输入,在输出中增加一个’1’.
2: 以sno为key进行partation,确保将同一个学生的信息和课程信息分配到同一个reduce中。同时以”sno,标识” 作为map阶段输出的排序依据(以\t分隔的前两列),确保在reduce中同一个学生的学生信息在课程信息的上面(sno,0在所有sno,1的行前面)。
3: 在reduce中,遇到0标记的行保存sno和sname信息,遇到1标记的行将sno和sname和当前行的课程信息一起输出。
示例代码
mapper和reducer代码
#! /usr/bin/env python # coding:utf-8 import os import sys def read_input(file,sepr=' '): for line in file: line = line.split(sepr) yield line def mapper(): filepath = os.environ['map_input_file'] filename = os.path.split(filepath)[1] lines = read_input(sys.stdin) for data in lines: if data[0].strip() == "": continue if "student.txt" == filename: if len(data) != 2: continue else: print "%s\t%s\t%s" % (data[0],"0",data[1].strip()) else: if len(data) != 4: continue else: print "%s\t%s\t%s\t%s" %(data[0],"1",data[2].strip(),data[3].strip()) def reducer(): sno = None sname = None line = read_input(sys.stdin,'\t') for data in line: if (len(data) !=3 and len(data) !=4 ) or data[0].split() == "": continue if data[0] != sno: sno = data[0] if data[1] == "0": sname = data[2].strip('\n') else: cname = data[2] cnum = data[3].strip('\n') print "%s\t%s\t%s\t%s" % (sno,sname,cname,cnum) if __name__ == "__main__": d = {"mapper":mapper,"reducer":reducer} if sys.argv[1] in d: d[sys.argv[1]]()
work.bash
#! /bin/bash #定义map和reduce的目录 export WORK_PATH=/var/tmp/reduce_join #执行stream的jar包地址 stream=$HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.7.5.jar #数据输入目录 input=/lcy/reduce_join/input #输出结果目录 output=/lcy/reduce_join/output #删除mr输出目录 if $HADOOP_HOME/bin/hdfs dfs -test -d $output then $HADOOP_HOME/bin/hdfs dfs -rm -r $output fi #执行mapreduce程序 $HADOOP_HOME/bin/hadoop jar $stream \ -D mapreduce.job.reduces=2 \ #设置reduce个数 -D num.key.fields.for.partition=1 \ #设置第一个字段为分区字段(默认使用\t作为map输出分隔符) -D stream.num.map.output.key.fields=2 \ #设置map的输出以前两个字段作为排序依据,这个配置很重要,否则reduce的输入格式不正确 -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner \ #和partition参数一起使用 -input $input/* \ -output $output \ -mapper "python mr.py mapper" \ -reducer "python mr.py reducer" \ -file $WORK_PATH/mr.py
思路2
在reduce中,将学生信息和课程成绩信息保存到内存中,等遍历完一个学生后,对两个数据进行join操作。这种方式可以让学生信息在课程信息的后面(因为经过partation和shuffle后,同一个sno的学生信息和课程成绩信息会是连续的,要么学生信息在上面,要么学生信息在下面)步骤如下:
1:同思路1的第一步
2:以sno为key进行partation,确保将同一个学生的信息和课程信息分配到同一个reduce中。其中map端的排序可以不用设置了。
3:reduce中,遇到0标记的行保存sno和sname信息,遇到1标记的行,将cname和grade组成list并追加到arr中。遇到新的sno时遍历arr,并和sno,sname一起输出。
- 测试代码
mapper和reducer
#! /usr/bin/env python # coding:utf-8 import os import sys def read_input(file,sepr=' '): for line in file: line = line.split(sepr) yield line def mapper(): filepath = os.environ['map_input_file'] filename = os.path.split(filepath)[1] lines = read_input(sys.stdin) for data in lines: if data[0].strip() == "": continue if "student.txt" == filename: if len(data) != 2: continue else: print "%s\t%s\t%s" % (data[0],"0",data[1].strip()) else: if len(data) != 3: continue else: print "%s\t%s\t%s\t%s" %(data[0],"1",data[1].strip(),data[2].strip()) def reducer(): sno = None sname = None tpl = [] line = read_input(sys.stdin,'\t') for data in line: if (len(data) !=3 and len(data) !=4 ) or data[0].split() == "": continue if data[0] != sno: #遇到新的sno时,进行连接输出 if sno != None and len(tpl) > 0: for row in tpl: print '%s\t%s\t%s\t%s' % (sno,sname,row[0],row[1]) tpl = [] #清空list,否则会变成笛卡尔积 sno = data[0] if data[1] == "0": sname = data[2].strip('\n') else: tpl.append([data[2].strip(),data[3].strip()]) else: if data[1] == "0": sname = data[2].strip('\n') else: tpl.append([data[2].strip(),data[3].strip()]) if sno != None and len(tpl) > 0: for row in tpl: print '%s\t%s\t%s\t%s' % (sno,sname,row[0],row[1]) if __name__ == "__main__": d = {"mapper":mapper,"reducer":reducer} if sys.argv[1] in d: d[sys.argv[1]]()
work.bash
#! /bin/bash #定义map和reduce的目录 export WORK_PATH=/var/tmp/reduce_join2 #执行stream的jar包地址 stream=$HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.7.5.jar #数据输入目录 input=/lcy/reduce_join2/input #输出结果目录 output=/lcy/reduce_join2/output #删除mr输出目录 if $HADOOP_HOME/bin/hdfs dfs -test -d $output then $HADOOP_HOME/bin/hdfs dfs -rm -r $output fi #执行mapreduce程序 $HADOOP_HOME/bin/hadoop jar $stream \ -D mapreduce.job.reduces=2 \ -D num.key.fields.for.partition=1 \ -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner \ -input $input/* \ -output $output \ -mapper "python mr.py mapper" \ -reducer "python mr.py reducer" \ -file $WORK_PATH/mr.py