一、多表链接
1、功能
单表关联要求从给出的数据中寻找所关心的数据,是对原始数据所包含信息的挖掘,多表(逻辑)关联和单表关联类似,它也是通过对原始数据进行一定的处理,从其中挖掘出关心的信息。
2、设计
多表关联和单表关联相似,都类似于数据库中的自然连接。相比单表关联,多表关联的左右表和连接列更加清楚。所以可以采用和单表关联的相同的处理方式,map识别出输入的行属于哪个表之后,对其进行分割,将连接的列值保存在key中,另一列和左右表标识保存在value中,然后输出。reduce拿到连接结果之后,解析value内容,根据标志将左右表内容分开存放,然后求笛卡尔积,最后直接输出。
3、代码分析
package com.chinasofti.hadooptest;
import java.io.IOException;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class MTJoin {
public static int time = 0;
/**
* 在map中先区分输入行属于左表还是右表,然后对两列值进行分割, 保存连接列在key值,剩余列和左右表标志在value中,最后输出
*/
public static class Map extends Mapper<Object, Text, Text, Text> {
// 实现map函数
public void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();// 每行文件
String relationtype = "";// 左右表标识
// 输入文件首行,不处理
if (line.contains("factoryname") || line.contains("addressed")) {
return;
}
// 输入的一行预处理文本
//根基第一个数据是否是数字判定当前数据是左表逻辑还是右表逻辑
String[] datas = line.split(" ");
String mapkey = datas[0].charAt(0) >= '0'&& datas[0].charAt(0) <= '9' ? datas[0] : datas[1];
String mapvalue = datas[0].charAt(0) >= '0' && datas[0].charAt(0) <= '9' ? datas[1] : datas[0];
relationtype = datas[0].charAt(0) >= '0'&& datas[0].charAt(0) <= '9' ? "2" : "1";
// 输出左右表
context.write(new Text(mapkey), new Text(relationtype + "+"
+ mapvalue));
}
}
/**
* reduce解析map输出,将value中数据按照左右表分别保存,然后求出笛卡尔积,并输出。
*/
public static class Reduce extends Reducer<Text, Text, Text, Text> {
// 实现reduce函数
public void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
// 输出表头
if (0 == time) {
context.write(new Text("factoryname"), new Text("addressname"));
time++;
}
int factorynum = 0, addressnum = 0;
String[] factory = new String[10], address = new String[10];
Iterator ite = values.iterator();
while (ite.hasNext()) {
String record = ite.next().toString();
int i = 2;
if (0 == record.length()) {
continue;
}
// 取得左右表标识
char relationtype = record.charAt(0);
// 左表
if ('1' == relationtype) {
factory[factorynum] = record.substring(i);
factorynum++;
}
// 右表
if ('2' == relationtype) {
address[addressnum] = record.substring(i);
addressnum++;
}
}
// 求笛卡尔积
//通过笛卡尔积获取结果,由语句"0 != factorynum && 0 != addressnum"得知,只要在"value-list"中没有左表或者右表,则不会做处理
可以根据这条规则去除无效的shuffle连接。
if (0 != factorynum && 0 != addressnum) {
for (int m = 0; m < factorynum; m++) {
for (int n = 0; n < addressnum; n++) {// 输出结果
context.write(new Text(factory[m]), new Text(address[n]));
}
}
}
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args)
.getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
Job job = new Job(conf, "word count");
job.setJarByClass(MTJoin.class);
job.setMapperClass(Map.class);
job.setCombinerClass(Reduce.class);
job.setReducerClass(Reduce.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
二、单表链接
1、功能
"单表关联"这个实例要求从给出的数据中寻找所关心的数据,它是对原始数据所包含信息的挖掘。
2、设计
实例中给出child-parent(孩子——父母)映射表,要求输出grandchild-grandparent(孙子——爷奶)映射表。分析这个实例,显然需要进行类似RDBMS中的单表自连接操作,连接的是同一个表中左表(逻辑)的parent列和右表(逻辑)的child列。连接结果中除去连接的两列就是所需要的结果——"grandchild--grandparent"表。要用MapReduce解决这个实例,首先应该考虑如何将数据看作表,并实现表的自连接;其次就是连接列的设置;最后是结果的整理。考虑到MapReduce的shuffle过程会将相同的key会连接在一起,所以可以将map结果的key设置成待连接的列,然后列中相同的值就自然会连接在一起了。要连接的是左表的parent列和右表的child列,且左表和右表是同一个表,所以在map阶段将读入数据分割成child和parent之后,会将parent设置成key,child设置成value进行输出,并作为左表;再将同一对child和parent中的child设置成key,parent设置成value进行输出,作为右表。为此需要在输出的value中再加上左右表的信息,比如在value的String最开始处加上字符1表示左表,字符2表示右表。这样在map的结果中就形成了左表和右表,然后在shuffle过程中完成连接。reduce接收到连接的结果,其中每个key的value-list就包含了"grandchild--grandparent"关系。取出每个key的value-list进行解析,将左表中的child放入一个数组,右表中的parent放入一个数组,然后对两个数组求笛卡尔积就是最后的结果了。
3、代码分析
package com.chinasofti.hadooptest;
import java.io.IOException;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class STJoin {
public static int time = 0;//定义全局变量用于输出结果文件的表头
/* *
* map将输出分割child和parent,然后正序输出一次作为右表,
* 反序输出一次作为左表,需要注意的是在输出的value中必须加上左右表的区别标识。
*/
public static class Map extends Mapper<Object, Text, Text, Text> {
// 实现map函数
public void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
String childname = "";// 孩子名称
String parentname = "";// 父母名称
String relationtype = "";// 左右表标识
// 输入的一行预处理文本
String[] values = value.toString().split(" ");//对输入的数据进行分割。分割成为孩子和父母名字两部分
if (!values[0].equals("child")) {// 忽略表头。忽略首行无用数据
childname = values[0];
parentname = values[1];//分别获取孩子和父母姓名
// 输出左表
relationtype = "1";
context.write(new Text(values[1]), new Text(relationtype + "+"
+ childname + "+" + parentname));
// 输出右表
relationtype = "2";
context.write(new Text(values[0]), new Text(relationtype + "+"
+ childname + "+" + parentname));
}
}
}
public static class Reduce extends Reducer<Text, Text, Text, Text> {
// 实现reduce函数
public void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
if (0 == time) {// 在Reduce中输出表头,如果是第一次进行reduce操作,则输出表头,并改变全局变量值,下次执行时不再输出表头信息
context.write(new Text("grandchild"), new Text("grandparent"));
time++;
}
int grandchildnum = 0, grandparentnum = 0;
String[] grandchild = new String[10], grandparent = new String[10];
Iterator ite = values.iterator();
while (ite.hasNext()) {
String record = ite.next().toString();
if (0 == record.length()) {
continue;
}
//获取每个值中的父辈名称和孩子名称
String[] datas = record.split("\\+");
char relationtype = datas[0].charAt(0); // 取得左右表标识
String childname = "", parentname = ""; // 定义孩子和父母变量
childname = datas[1];// 获取value-list中value的child
parentname = datas[2];// 获取value-list中value的parent
//通过表示判定是逻辑左表还是逻辑右表
if ('1' == relationtype) { // 左表,取出child放入grandchildren
grandchild[grandchildnum++] = childname;
}
if ('2' == relationtype) {// 右表,取出parent放入grandparent
grandparent[grandparentnum++] = parentname;
}
}
// grandchild和grandparent数组求笛卡尔儿积,通过笛卡尔积获取结果
if (0 != grandchildnum && 0 != grandparentnum) {
//由语句"0 != grandchildnum && 0 != grandparentnum"得知,只要在"value-list"中没有左表或者右表,则不会做处理
可以根据这条规则去除无效的shuffle连接。
for (int m = 0; m < grandchildnum; m++) {
for (int n = 0; n < grandparentnum; n++) {
context.write(new Text(grandchild[m]), new Text(
grandparent[n]));
}
}
}
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args)
.getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
Job job = new Job(conf, "word count");
job.setJarByClass(STJoin.class);
job.setMapperClass(Map.class);
job.setCombinerClass(Reduce.class);
job.setReducerClass(Reduce.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}