Spark学习之Spark SQL

Spark SQL是Spark中用于结构化数据处理的组件,提供了一种通用的访问多种数据源的方式,可以访问的数据源包括Hive,Avro,Parquet,ORC,JSON和JDBC等等.Spark SQL采用了DataFrame数据模型(即带有Schema信息的RDD),支持用户在Spark SQL中执行SQL语句,实现对结构化数据的处理。

数据帧概述

Spark SQL所用的数据抽象并非RDD,而是DataFrame.DataFrame的推出,让Spark具备了处理大规模结构化数据的能力,它不仅比原有的RDD转换方式更加简单易用,而且获得了更高的计算性能.Spark能够轻松实现从Mysql的到数据帧的转化,并且支持SQL查询。

RDD是分布式的的Java对象的集合,但是,对象内部结构对于RDD而言却是不可知的。

数据帧是一种以RDD为基础的分布式数据集,提供了详细的结构信息,就相当于关系数据库的一张表。如下图所示,当采用RDD时,每个RDD元素都是一个Java的对象,即人对象,但是,无法直接看到人物对象的内部结构信息。而采用数据帧时,人对象内部结构信息就一目了然,它包含了姓名,年龄和Height3个字段,并且可以知道每个字段的数据类型。

数据帧的创建

从Spark2.0以上版本开始,星火使用全新的SparkSession接口替代Spark1.6中的SQLContext及HiveContext接口,来实现其对数据加载,转换,处理等功能.SparkSession实现了SQLContext及HiveContext所有功能。

   可以通过如下语句创建一个SparkSession对象:

实际上, 在启动进入spark-shell以后,spark-shell就默认提供了一个SparkContext对象(名称为sc)和一个SparkSession对象(名称为spark),因此,也可以不用自己声明一个SparkSession对象,而是直接使用spark-shell提供的SparkSession对象,即spark。

在创建DataFrame之前,为了支持RDD转换为DataFrame及后续的SQL操作,需要通过import语句(即import spark.implicits._)导入相应的包,启用隐式转换。

在创建DataFrame时,可以使用spark.read操作,从不同类型的文件中加载数据创建DataFrame,例如:

  • spark.read.json("people.json"):读取people.json文件创建DataFrame;在读取本地文件或HDFS文件时,要注意给出正确的文件路径;
  • spark.read.parquet("people.parquet"):读取people.parquet文件创建DataFrame;
  • spark.read.csv("people.csv"):读取people.csv文件创建DataFrame。
  • spark.read.format("json").load("people.json"):读取people.json文件创建DataFrame;
  • spark.read.format("csv").load("people.csv"):读取people.csv文件创建DataFrame;
  • spark.read.format("parquet").load("people.parquet"):读取people.parquet文件创建DataFrame。

需要指出的是,从文本文件中读取数据创建DataFrame,无法直接使用上述类似的方法,需要使用后面介绍的“从RDD转换得到DataFrame"。

扫描二维码关注公众号,回复: 4525931 查看本文章

从people.json文件中生成DataFrame的过程。执行"val df=spark.read.json(...)"语句后,系统就会自动从people.json文件中加载数据,并生成一个DataFrame(名称为df),从系统返回信息可以看出,df中包括两个字段,分别为age和name。最后,执行df.show()把df的记录都显示出来。

DataFrame的保存 

可以使用spark.write操作,把一个DataFrame保存成不同格式的文件。例如,把一个名称为df的DataFrame保存到不同格式文件中,方法如下:

  • df.write.json("people.json");
  • df.write.parquet("people.parquet");
  • df.write.csv("people.csv");

DataFrame的常用操作

DataFrame创建好以后,可以执行一些常用的DataFrame操作,包括printSchema()、select()、filter()、groupBy()和sort()等。

  • printSchema()

可以使用printSchema()操作,打印出DataFrame的模式(Schema)信息

  • select()

select()操作的功能,是从DataFrame中选取部分列的数据。如下图所示,select()操作选取了name和age这两个列,并且把age这个列的值加1.

select()操作还可以实现对列名称进行重命名的操作。如下图所示,name列名称被重命名为username。

  • filter()

filter()操作可以实现条件查询,找到满足条件要求的记录。如下图所示,df.filter(df("age")>20)用于查询所有age字段大于20的记录。

  • groupBy()

groupBy()操作用于对记录进行分组。如下图所示,可以根据age字段进行分组,并对每个分组中包含的记录数量进行统计。

  • sort() 

sort()操作用于对记录进行排序。如下图所示,df.sort(df("age").desc)表示根据age字段进行降序排序。

从RDD转换得到DataFrame

Spark提供了两种方法来实现从RDD转换得到DataFrame。

  • 利用反射机制推断RDD模式:利用反射机制来推断包含特定类型对象的RDD模式(Schema),适合用于对已知数据结构的RDD转换
  • 使用编程方式定义RDD模式:使用编程接口构造一个模式(Schema),并将其应用在已知的RDD上。
  1. 利用反射机制推断RDD模式

现在把一个people.txt文件加载到内存中生成一个DataFrame,并查询其中的数据,如下所示:

在上面的代码中,首先通过import语句导入所需的包,然后,d定义了一个名称为Person的case class,也就是说,在利用反射机制推断RDD模式时,需要先定义一个case class,因为,只有case class才能被Spark隐式地转换为DataFrame。spark.sparkContext.textFile()执行以后,系统会把people.txt文件加载到内存中生成一个RDD,每个RDD元素都是String类型,3个元素分别是"Michael,29""Andy,30"和"Justin,19"。然后,对这个RDD调用map(_.split(","))方法得到一个新的RDD,这个RDD中的3个元素分别是Array("Michael",29)、Array("Andy","30")和Array("Justin","19")。接下来,继续对RDD执行map(attributes=>Person(attributes(0),attributes(1).trim.toInt))操作,这时得到新的RDD,每个元素都是一个Person对象,3个元素分别是Person(“Michael”,29)、Person(“Andy”,30)和Person(“Justin”,19)。然后,在这个RDD上执行toDF()操作,把RDD转换成DataFrame。从toDF()操作执行后系统返回的信息可以看出,新生成的名称为peopleDF的DataFrame,每条记录的模式(schema)信息[name:string,age:bigint]。

生成DataFrame以后,可以进行SQL查询。但是,Spark要求必须把DataFrame注册为临时表,才能供后面的查询使用。因此,通过peopleDF.createOrReplaceTempView("people")这条语句,把peopleDF注册为临时表,这个临时表的名称是people。

val personRDD=spark.sql("select name,age from people where age>20")这条语句的功能是从临时表people中查询所有age字段的值大于20的记录。从语句执行后返回的信息可以看出,personRDD也是一个DataFrame。最终通过personRDD.map(t=>"Name:"+t(0)+“,”+“Age:"+t(1)).show()操作,把personsRDD中的元素进行格式化以后再输出。

2.使用编程方式定义RDD

当无法提前定义case class 时,就需要采用编程方式定义RDD模式。例如,现在需要通过编程方式把”people.txt"文件加载进来生成DataFrame,并完成SQL查询。完成这项工作主要包含3个步骤。

  • 第一步:制作“表头";
  • 第二步:制作”表中的记录“;
  • 第三步:把”表头“和”表中的记录“拼装在一起。

表头也就是表的模式(Schema),需要包含字段名称、字段类型和是否允许空值等信息,SparkSQL提供了StructType(fields:Seq[StructField])类来表示表的模式信息。生成一个StructType对象时,需要提供fields作为输入参数,fields是一个集合类型,里面的每个集合元素都是StructField类型。Spark SQL中的StructField(name,dataType,nullable)是用来表示表的字段信息的,其中,name表示字段名称,dataType表示字段的数据类型,nullable表示字段的值是否允许为空值。

在制作”表中的记录“时,每条记录都应该被封装到一个Row对象中,并把所有记录的Row对象一起保存到一个RDD中。

制作完”表头“和表中记录以后,可以通过spark.createDataFrame()语句,把表头和表中的记录拼装在一起,得到一个DataFrame,用于后续的SQL查询。

运行结果:

 

在上述代码中,数组fields是Arrray(StructField("name",StringType,true),StructField("age",IntergerType,true)),里面包含了字段的描述信息。val schema=StructType(fields)语句把fields作为输入,生成一个StructType对象,即schema,里面包含了表的模式信息,也就是”表头“。

通过上述步骤,就得到了表的模式信息,相当于做好了”表头“,下面需要制作”表中的记录“。val peopleRDD=spark.sparkContext.textFile()语句从people.txt文件中加载数据生成RDD,名称为peopleRDD,每个RDD元素都是String类型,3个元素分别是”Michael,29“,”Andy,30“和”Justin,19“。然后,对这个RDD调用map(_.split(","))方法得到一个新的RDD,这个RDD中的3个元素分别是Array("Michael","29")、Array("Andy","30")和Array("Justin","19”)。接下来,对这个RDD调用map(attributes=>Row(attributes(0),attributes(1).trim.toInt())操作得到一个新的RDD,即rowRDD,这个RDD中的每个元素都是一个Row对象,也就是说,经过map()操作以后,Array("Michael""29")被转换成了Row(“Michael”,29)。这样就完成了记录的操,这时rowRDD包含了3个Row对象。

下面需要把“表头”和“表中的记录”进行拼装,val peopleDF=spark.createDataFrame(rowRDD,schema)语句就实现了这个功能,它把表头schema和表中的记录rowRDD拼装在一起,得到一个DataFrame,名称为peopleDF。

peopleDF.createOrReplaceTempView("people")语句把peopleDF注册为临时表,从而可以支持SQL查询。最后,执行spark.sql("SELECT name,age FROM people")语句,查询得到结果results,并使用map()方法对记录进行格式化,由于results里面的每条记录都包含两个字段,即name和age,因此,attribute(0)表示name的值,attribute(1)表示age的值。

猜你喜欢

转载自blog.csdn.net/qq_41338249/article/details/84836515