整理下一些常用到的DataFream和DataSet的API,如何从RDD产生DataFrame和DataSet可以看下之前写的文章“SparkSQL使用整理(一)”。
本文中读取的people.json文件就是spark例子中自带的people.json文件,本文接下来举的例子都是对这个json文件生成的DataFrame进行操作。
一些算子使用示例:
1.select 选取列
$“age”是一个语法糖,如果没有$,那么“age”就代表一个字符串,加上之后就表示列了,可以对列进行操作,例如+1(建议别这么搞,列名都变了)。
加载json文件的话,可以自行解析出Schema信息,不需要另外指定了。这个可能应该是和DataSource API有关,这个后续再确认。
2.filter/where 过滤数据
还有一种写法是jsonDF.where($”age”>21),两种算子效果是一样的,where算子底层调用的就是filter,只不过where更加偏向SQL的写法。
3.groupBy 聚合
可以按照一/多列进行聚合,此处的聚合之后会返回聚合的列以及count结果列。groupBy()之后,还可以跟不同算子以实现不同功能,如下所示:(注意:下面提到的算子,除了count,其他在使用时,指定的列必须是numeric column)
count(): 统计每个group中组员的个数
sum(): 计算分组中,指定列列值的总数
不指定具体列时,默认会计算所有的numeric column。例如:
groupBy(“age”).sum()
返回列:age sum(age) sum(money)
max():计算列的最大值
min():计算列的最小值
avg()/mean():计算列的平均值
agg():集成查询,可以一次性调用以上多个算子
4.sort/orderBy 排序
两个算子效果是一样的,orderBy算子底层调用的就是sort。
默认null值是排在最后面,可以指定按照多列进行排序。
默认是升序排序(asc),建议还是在写的时候指定一下吧,代码理解起来也方便点。
5.distinct/dropDuplicates 单个DataFrame数据去重
6.intersect 取两个DataFrame交集
两个DataFrame列的数量,列名,列的顺序必须相同,才能正确调用这个算子。
7.union 合并两个集合
union和取交集的要求一样,两个DataFrame列的数量,列名,列的顺序必须相同。
实际上就是将两个DF头尾合在一起,变成一个DataFrame。
8.crossJoin 笛卡尔积
笛卡尔积就是两个DataFrame每一行两两排列组合,如果匹配到之后,列没有值,会被赋值为null。
个人觉得,笛卡尔积的时间复杂度是n平方,不适合在大规模数据下应用,十来万撑死了,所以才会有了各种聚类的算法。
9.join 基于两个DataFrame的共同字段,将它们的行结合起来
除了笛卡尔积,SparkSQL还支持如下join操作:
使用示例代码如下:
DF1.join(DF2, Seq("col1", "col2"…), "inner")
和上面的笛卡尔积一样,如果列没有值,会被赋值为null。执行结果会返回两个DataFrame所有的列,例如:
但是,先返回左边的列还是右边的列,好像是不固定的…这个应该是和join内部的机制有关,后续有时间再看源码确认下
各种joinType实现的功能如下:
inner: 只连接匹配的行。
outer: 返回左右所有的行,如果某一行在另外一张表中没有匹配的行,用null代替。
leftouter: 返回左表所有的行和右边满足条件的行,如果右边没有匹配的行或者某一列没有,用null代替。
rightouter: 和上面的leftouter相反。
leftsemi: 仅返回左边的列,如果右边存在有匹配则返回,否则抛弃。IN/EXISTS的一种更高效的实现—hive,类似于:
Select A.Key A.VALUE from A where A.KEY IN (select B.Key from B)
leftanti: 仅返回左边的列,功能和上边的刚好相反。
各种Join图示如下:
10.joinWith 和join的区别是schema会不一样
支持`inner`, `outer`, `left_outer`, `right_outer`, `leftsemi`,本人目前还没用到过这个算子。按照源码的说明是为了保护原始数据的类型,特别是两个DataFrame有同名列的情况:
This type of join can be useful both for preserving type-safety with the original object types as well as working with relational data where either side of the join has column names in common.
11.coalesce/repartition 重分区
调用这两个算子之后,返回的是一个新的DataSet[T],所以需要将返回结果赋值给新的变量。
DataFrame的这两个算子区别在与是否真的触发shuffle。
因为coalesce的shuffle是false(窄依赖),所以coalesce只能用于减少partition的数量,因为增加Partition数量肯定是个宽依赖。
直接使用SQL语句:
使用SQL语句的时候需要向将DataFrame注册成类似数据库表的形式。
表有两种形式,一种是临时表,一种是全局表,两者的区别在于临时表只能在当前的SparkSession中使用,而全局表是绑定到系统数据库:global_temp中的,可以在所有的SparkSession中使用,但是在使用的时候,需要在表名前面加上限定名。
DataFrame与DataSet的区别
DataFrame就是DataSet[Row],所以DataSet的算子和DataFrame是一样。两者的区别在于,DataFrame对于Row(一行数据)中到底有哪些字段,每个字段分别又是什么类型是不知道的,只能通过getString或者getAs[类型]或者get(i)这样方式来获取特定字段的内容,而DataSet可以使用._1/._2之类的方式,访问起来比较方便(个人感觉是差不多),两者的区别差不多就是这样。
DataSet有groupByKey算子可以支持更加灵活的聚合操作。例如:
txtDF.groupByKey(x => x.getLong(1)).mapGroups((key,group) => group.map(****))
参考:
https://blog.csdn.net/moakun/article/details/80429267(SQL中的Join示例)