连接类型:
- inner join :内部连接(保留左右数据集内某个键都存在的行)
- outer join:外部连接(保留左侧或右侧数据集中具有某个键的行)
- left outer join :左外部连接(保留左侧数据集中具有某个键的行)
- right outer join:右外部连接(保留右侧数据集中具有某个键的行)
- left semi join :左半连接(如果某键在右侧数据行中出现,则保留且仅保留左侧数据行)
- left anti join:左反连接(如果某键在右侧数据行中没出现,则保留且仅保留左侧数据行)
- natural join:自然连接(通过隐式匹配两个的数据集之间具有相同名称的列来执行连接)
- cross join:笛卡尔链接
首先创建数据
Logger.getLogger("org").setLevel(Level.ERROR)
val conf = new SparkConf().setAppName("C01_Joins_API").setMaster("local[*]")
val spark = SparkSession.builder()
.config(conf)
.config("spark.sql.shuffle.partitions", 100)
.getOrCreate()
import spark.implicits._
val person = Seq(
(0, "Bill Chambers", 0, Seq(100)),
(1, "Matei Zaharia", 1, Seq(500, 250, 100)),
(2, "Michael Armbrust", 1, Seq(250, 100)))
.toDF("id", "name", "graduate_program", "spark_status")
val graduateProgram = Seq(
(0, "Masters", "School of Information", "UC Berkeley"),
(2, "Masters", "EECS", "UC Berkeley"),
(1, "Ph.D.", "EECS", "UC Berkeley"))
.toDF("id", "degree", "department", "school")
val sparkStatus = Seq(
(500, "Vice President"),
(250, "PMC Member"),
(100, "Contributor"))
.toDF("id", "status")
person.createOrReplaceTempView("person")
graduateProgram.createOrReplaceTempView("graduateProgram")
sparkStatus.createOrReplaceTempView("sparkStatus")
例子:内连接 inner join
用例:在下面的例子中,我们将graduateProgram 的DataFrame 与 person 的 DataFrame 连接在一起,创建一个新的DataFrame。
/**
* 内连接 inner join
* 判断来自两个DataFrame 或表中两行的指定键是否相等,
* 如果相等则将这两行连接在一起并返回(匹配两个DataFrame 中指定键相等的任意两行,并将其连接后返回)
* 例子: 在下面的例子中,我们将graduateProgram 的DataFrame 与 person 的 DataFrame 连接在一起,创建一个新的DataFrame
* */
val joinExpression = person.col("graduate_program") === graduateProgram.col("id")
// 没有同时出现在两个DataFrame 中的键不会出现在结果DataFrame 中,例如下面的表达式会导致生成的DataFrame 为空
val wrongJoinExpression = person.col("name") === graduateProgram.col("school")
// 内连接是默认的连接操作,因此我们只要指定左侧DataFrame 并在join 表达式中连接右侧即可
person.join(graduateProgram, joinExpression).show()
// 还可以通过传入第三个参数来显示指定连接类型JoinType
var joinType = "inner"
person.join(graduateProgram, joinExpression, joinType).show()
例子: 外连接 outer join
/**
* 外连接 outer join
* 是指两个DataFrame或表中两行的指定键是否相等,
* 如果相等则将这两行连接在一起并返回,
* 如果不相等,将左侧或右侧DataFrame 中没有匹配的行的各列用null替换,在与左侧或右侧DataFrame 中已有的行连接在一起返回
*/
joinType = "outer"
person.join(graduateProgram, joinExpression, joinType).show()
例子:左外连接 left outer join
/**
* 左外连接 left outer join
* 是检查两个DataFrame 或表中的键,并包括左侧DataFrame中所有行以及右侧DataFrame 中与左侧DataFrame 有匹配的行,如果在右侧DataFrame 中没有对应的行,则Spark 将插入null
*/
joinType = "left_outer"
graduateProgram.join(person, joinExpression, joinType).show()
例子:右外连接 right outer join、
/**
* 右外连接 right outer join、
* 是检查两个DataFrame 或表中的键,并包括左侧DataFrame中所有行以及左侧DataFrame 中与左侧DataFrame 有匹配的行,如果在左侧DataFrame 中没有对应的行,则Spark 将插入null
*/
joinType = "right_outer"
person.join(graduateProgram, joinExpression, joinType).show()
例子:左半连接
/**
* 左半连接
* 实际上并不包括右侧DataFrame 中的任何值,他只是保留左侧的DataFrame 的值是否存在于右侧DataFrame 中,如果存在则在连接结果中保留,即使左侧DataFrame 中存在重复键,对应行也将保留在结果中
*
*/
joinType = "left_semi"
graduateProgram.join(person, joinExpression, joinType).show()
val gradProgram2 = graduateProgram.union(Seq(
(0, "Masters", "Duplicated Row", "Duplicated School")).toDF())
gradProgram2.createOrReplaceTempView("gradProgram2")
gradProgram2.join(person, joinExpression, joinType).show()
例子:左反连接
/**
* 左反连接
* 左反连接与左半连接相反,
* 只是查看该值是否在于右侧DataFrame 中。但是左反连接并不保留第二个DataFrame 中存在的值,而是只保留在第二个DataFrame 中没有相应键的值,可以将反连接视为一个not in sql 类型的过滤器
*/
joinType = "left_anti"
graduateProgram.join(person, joinExpression, joinType).show()
例子:交叉连接 笛卡尔连接
/**
* 交叉连接 笛卡尔连接
*/
joinType = "cross"
graduateProgram.join(person, joinExpression, joinType).show()
例子:处理重复列名
/**
* 处理重复列名
*/
// 创建一个问题数据集
val gradProgramDupe = graduateProgram.withColumnRenamed("id", "graduate_program")
val joinExpr = gradProgramDupe.col("graduate_program") === person.col(
"graduate_program")
// 注意到有两个 graduate_program 列
person.join(gradProgramDupe, joinExpr).show()
// 当引用其中一个列时,就会出现问题
// person.join(gradProgramDupe, joinExpr).select("graduate_program").show()
// 解决方案
// 1. 采用不同的连接表达式:当有两个同名的键时,最简单的解决方法是将连接表达式从布尔表达式更改为字符串或序列, 这会在连接过程中自动删除其中一个列
person.join(gradProgramDupe,"graduate_program").select("graduate_program").show()
// 2. 连接后删除列
val joinExpr2 = person.col("graduate_program") === graduateProgram.col("id")
person.join(graduateProgram, joinExpr2).drop(graduateProgram.col("id")).show()
// 3. 在连接前重命名列
val gradProgram3 = graduateProgram.withColumnRenamed("id", "grad_id")
val joinExpr3 = person.col("graduate_program") === gradProgram3.col("grad_id")
person.join(gradProgram3, joinExpr3).show()