大数据开发之Spark篇----SparkSQL入门(2)

DataFrame详解

DataFrame与RDD之间的差异

RDD的回顾

我们先回顾一下我们前几篇对RDD的描述再从这些特性进入到DataFrame的讲解。首先,我们知道RDD的全称是弹性分布式数据集,重点是RDD是一个数据集,其里面是由一个个元素构成的,所以我们操作的时候其实就是对元素进行操作,比如各种的map,filter,reduce操作,但是我们好像并不知道RDD里面元素的具体类型到底是什么。我们只知道RDD的元素可以是一个类型的对象,但是这个类型的对象里面的具体东西在RDD的操作算子里面是没有得到具体的体现的。
这样我们其实可以总结起来RDD的操作算子都是作用到元素级别的,但具体要对每一个元素进行怎么样的操作实际上是由我们自己开发的代码来完成的,这样我们就没有办法以一种统一的方式来处理RDD中的元素了。这样我们在开发的过程当中是非常困难的。

DataFrame的特性

使用过Python的小伙伴都知道在使用Python进行数据分析时,我们都是点用pandas这个类库来对数据进行包装的,数据都是以DataFrame这种对象的形式统一呈现出来。同样的SparkSQL中也提供了DataFrame这种类来对数据进行封装并为我们的编程提供了统一的API。
DataFrame是一个类似于2维表的数据类型,其有着行和列的概念,每一列都有其自己的数据类型和名称,而每一行就相当于一条记录(也和数据集中的一个元素类型),这么说来DataFrame在数据结构层面上就为我们提供了对对于元素层面更深入的信息(这就是Schema信息),所以DataFrame就是一种结构化数据。由于它暴露了更多的信息,那么意味着有更多的API可用,所以我们的编程过程将会更轻松一点。

具体的体现

在使用textFile来读取一个文本文件的时候,我们得到的返回值都是RDD[String]这种类型,就是每一个元素都是一个String的字符串。然后我们再更具里面具体的内容进行数据的转化操作,如:map(_.split("\t")).map((x._1,x._2))…。如果每一次编程时里面的元素都不是同一种类型的话我们将要不停地进行新的代码开发。
如果是DataFrame的话,我们在构建之初就对数据的schema进行设定,在后面的开发过程之中我们就可以使用统一的API进行编程了,如:select("…").filter("…"),show。我们甚至可以直接使用SQL进行行过滤和列裁剪来得到我们想要的数据。

底层的区别

在使用RDD编程的过程中,使用不同的编程语言将会得到不同的执行性能,Java比Scala快,Scala比Python快。但是当使用DataFrame编程的时候,无论我们使用的是Java,Scala还是Python都是想调用DataFrame的API来编程,其底层会将我们的代码转换为一种logisticalPlan(逻辑执行计划),然后在构建一种physicalExecution(物理执行引擎)来执行的。这样无论我们使用什么语言最终的执行性能都一样的。甚至我们知道即使要得到相同的结构但是使用不同的SQL,这个过程的执行性能也是差很远的,但这在SparkSQL里面是不存在的,因为我们的执行逻辑将会由Spark的catalyst优化器来提升执行效率的。

DataFrame的构建

吹了这么就的DataFrame,现在我们来了解一下构建DataFrame的几个途径:

  1. RDD转DataFrame
  2. HiveTable
  3. External Data Source
    下面我们分别对这几种方式进行详细介绍。

HiveTable

如果我们要想读取到Hive上的表的时候,我们首先就是要能访问得到Hive的metastore地址,所以我们需要一个hive-site.xml的配置文件并将其放到$SPARK_HOME/conf目录下。然后我们就可以直接访问Hive上面的表了。
具体操作:

//我们读取了Hive中doudou这个数据库里面的text这个表,其返回了一个DataFrame对象给我们
val df = spark.table("doudou.text")
//这里是两种方式将DF重新保存到Hive中,后一种是通过一个option的方法重新配置了要表格的数据实际是存储在HDFS上的路径
df.write.saveAsTable("t")
df.write.option("path","/some/path").saveAsTable("t")

RDD转DataFrame

我们也可以使用已有的RDD来构建DF的

reflaction infer 反射推导

这种方式是我们需要在RDD中提前构建好schema信息的,下面是一个完整的例子;

//首先要定义一个case Class来为RDD表明字段的名称和类型
case Clasa Person(id:Int,name:String,age:Int)
//将这个case Class作用到RDD上面,返回一个RDD[Person]的带泛型参数的RDD
val rdd1 = rdd.map(x => Person(x(0),x(1),x(2))
//调用RDD的toDF方法来构建DataFrame
val df = rdd1.toDF()

在Spark1.x版本里面这种方法只能构建20来个字段,太多就不行了。但是,在2.x版本里面就没有限制了。生产上面其实我们很少使用到这种方法的。

编程定义DataFrame

下面这个例子是使用编程的方式来构建RDD的:

//这个方式是固定写法,可能和官网上的有一点不同,但生产上就是这么写,反而官网上的不是很推荐
val struct = StructType(Array(
     StructField("id",IntType,nullable = True),
     StructField("name",StringType,nullable = True),
     StructField("age",IntType,nullable = True)
     )
 )
 //构建一个RDD[Row],过程中记得对RDD中的数据进行转换
val rdd1= rdd.map(x => Row(x(0).toInt,x(1),x(2).toInt)
//使用SparkSession的createDataFrame方法来构建DF
val df = spark.createDataFrame(struct,rdd1)

使用外部数据源来构建DataFrame

其实就是从不同的文件系统中读取不同的格式的文件,包括jdbc
如果是使用外部数据源来构建的话,我们使用以下几种方式:

  1. 读取
//标准型,format定义了输入类型,load定义了输入的路径
val df = spark.read.format("...").load(".....")
//简写型,以json格式的文件为例子,后面括号里填的是路径
val df1 = spark.read.json("......")
//更简版,但这种方式默认只能读取parquet文件,想要更改这种默认设置可以在spark-default.xml中配置spark.sql.sources.default这个参数
val df2 = spark.read.load("...")

format这个方法里面可以传入各种内建的文件类型,有:json,csv,parquet,orc,jdbc,text(文本)…

  1. 写入
//标准型
df.write.format("...").save("....")
//如果文件已存在可以选择重写或者追加,甚至报错
df.write.format("...").mode("overwrite").save("...")
import org.apache.spark.sql.SaveMode
df.write.format("....").mode(SaveMode.Overwrite).save("....")

使用option方法来设置,这个一般我们在读取csv和jdbc时候用得多

//这里三个option分别定义了分隔符使用逗号,是否推到schema信息和是否保留header
val df = spark.read.format("text").option("sep",";").option("inferSchema","true").option("header","true).load("....")
//如果是使用jdbc读入数据的话,你就需要有url,username等用户信息,以及库和表的名称才能准确定位
val df = spark.read.format("jdbc")
.option(“url","jdbc:mysql://....")
.option("usrname","doudou")
.option("password","123456")
.option("dbtable","xxxx.xxx").load()

DataFrame的使用

常用的方法是:

//select和filter里面是可以传入字符串或者Column对象的·
df.select("name").filter("age > 18").show()
//传入的是Column对象的情况
df.select($"name").filter($"age" > 18).show()
df.select('name).filter('age> 18).show()

以上就是完成了行过滤和列裁剪的过程,当然还有什么grouBy等使用

//传统的写法,这种只能用一个聚合函数,实在不方便
df.groupBy("name").sum("age")
//这种就可以用多个了
df.groupBy("name").agg(max("age),avg("age"))

注册方法

//首先定义好你要使用的函数
def func1(ip:String):(String,String) = {
   val array = ip.split("@")
   var start = ""
   var end = ""
   if(array(0)=="186"){
      start = "nanjing"
      }else{
      start = "beijing"
      }
   if(array(1)=="0.0"){
      end = "liantong"
      }else{
      end = "dianying"
      }
   (start,end)
}
//注册方法,里面传入方法名和一个lambda表达式,式子里面放你定义的方法
udf.register("parseIP",(ip:String) => { func1(ip) })
//在sql中使用,下起博文将接介绍withColumns这个方法的使用
spark.sql("select ip,parseIP(ip)(0) as city,parseIP(ip)(1) as carrier from table1").show
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值