spark sql的特点:
1.易整合
将sql查询与spark程序无缝混合,可以使用java、scala、python、R等语言的API操作。
2.统一的数据访问
以相同的方式连接到任何数据源。
3.兼容Hive
支持hiveSQL的语法。
4.标准的数据连接
可以使用行业标准的JDBC或ODBC连接。
在sparkSQL当中,spark为我们提供了两个操作sparkSQL的抽象,分别是DataFrame和DataSet。也就是说我们操作sparkSQL一般都是使用DataFrame或者DataSet来实现的,就类似于我们SparkCore模块当中,我们的抽象是RDD,我们使用SparkContext来实现操作RDD一样。
对于在版本上面,spark也有一些历史变动
RDD(Spark1.0) ==> DataFrame(1.3) ==> DataSet(1.6)
如果同样的数据都给到这三个数据结构,他们分别计算后,都会出相同的计算结果,只不过执行速度和执行效率不太一样。
在后期的spark发布版本当中,DataSet会逐步的取代RDD和DataFrame成为spark的唯一API接口。
RDD:
RDD是一个懒执行的不可变的可以支持Lambda表达式的并行数据集合。
RDD的最大好处就是简单,API的人性化程度很高。
RDD的劣势是性能限制,它是一个JVM驻内存对象,这也就决定了存在GC的限制和数据增加时Java序列化成本的升高。
Dataframe基本概述
DataFrame是一个分布式数据容器。然而DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即schema。同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。
与RDD的对比:
DataFrame是为数据提供了Schema的视图。可以把它当做数据库中的一张表来对待
DataFrame也是懒执行的。
性能上比RDD要高,主要有两方面原因:
定制化内存管理,数据以二进制的方式存在于非堆内存,节省了大量空间之外,还摆脱了GC的限制。
优化的执行计划,查询计划通过Spark catalyst optimiser进行优化.
Dataframe的缺点: 在于在编译期缺少类型安全检查,导致运行时出错.
Dataset:
1)是Dataframe API的一个扩展,是Spark最新的数据抽象
2)用户友好的API风格,既具有类型安全检查也具有Dataframe的查询优化特性。
3)Dataset支持编解码器,当需要访问非堆上的数据时可以避免反序列化整个对象,提高了效率。
4)样例类被用来在Dataset中定义数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称。
5)Dataframe是Dataset的特列,DataFrame=Dataset[Row] ,所以可以通过as方法将Dataframe转换为Dataset。Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息我都用Row来表示。
6)DataSet是强类型的。比如可以有Dataset[Car],Dataset[Person].
DataFrame只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法在编译的时候检查是否类型失败的,比如你可以对一个String进行减法操作,在执行的时候才报错,而DataSet不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。就跟JSON对象和类对象之间的类比。
RDD让我们能够决定怎么做,而DataFrame和DataSet让我们决定做什么,控制的粒度不一样。
1、RDD、DataFrame、Dataset全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利
2、三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算,极端情况下,如果代码里面有创建、转换,但是后面没有在Action中使用对应的结果,在执行时会被直接跳过.。
3、三者都会根据spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
4、三者都有partition的概念
5、三者有许多共同的函数,如filter,排序等
6、在对DataFrame和Dataset进行操作许多操作都需要这个包进行支持
import spark.implicits._
7、DataFrame和Dataset均可使用模式匹配获取各个字段的值和类型
DataFrame
RDD:
1、RDD一般和spark mlib同时使用
2、RDD不支持sparksql操作
DataFrame:
1、与RDD和Dataset不同,DataFrame每一行的类型固定为Row,只有通过解析才能获取各个字段的值,如
testDF.foreach{
line =>
val col1=line.getAs[String]("col1")
val col2=line.getAs[String]("col2")
}
每一列的值没法直接访问
2、DataFrame与Dataset一般不与spark ml同时使用
3、DataFrame与Dataset均支持sparksql的操作,比如select,groupby之类,还能注册临时表/视窗,进行sql语句操作,如
dataDF.createOrReplaceTempView(“tmp”)
spark.sql(“select ROW,DATE from tmp where DATE is not null order by DATE”).show(100,false)
4、DataFrame与Dataset支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然
//保存
val saveoptions = Map("header" -> "true", "delimiter" -> "\t", "path" -> "hdfs://node01:8020/test")
datawDF.write.format("com.atguigu.spark.csv").mode(SaveMode.Overwrite).options(saveoptions).save()
//读取
val options = Map("header" -> "true", "delimiter" -> "\t", "path" -> "hdfs://node01:8020/test")
val datarDF= spark.read.options(options).format("com.atguigu.spark.csv").load()
利用这样的保存方式,可以方便的获得字段名和列的对应,而且分隔符(delimiter)可以自由指定。
Dataset:
Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同。
DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段
而Dataset中,每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息
case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型
/**
rdd
("a", 1)
("b", 1)
("a", 1)
**/
val test: Dataset[Coltest]=rdd.map{line=>
Coltest(line._1,line._2)
}.toDS
test.map{
line=>
println(line.col1)
println(line.col2)
}
DataFrame创建
构建DF的两种方式
第一种方式:通过RDD配合case class进行转换DF
第一步:创建文本文件
在linux的/export/servers/路径下创建文本文件
cd /export/servers/
vim person.txt
1 zhangsan 20
2 lisi 29
3 wangwu 25
4 zhaoliu 30
5 tianqi 35
6 kobe 40
第二步:定义RDD
使用spark-shell 进入spark客户端
cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/
bin/spark-shell --master local[2]
val lineRDD = sc.textFile("file:///export/servers/person.txt").map(x => x.split(" "))
第三步:定义case class样例类
case class Person(id:Int,name:String,age:Int)
第四步:关联RDD与case class
val personRDD = lineRDD.map(x => Person(x(0).toInt,x(1),x(2).toInt))
第五步:将RDD转换成DF
val personDF = personRDD.toDF
注意:DF也可以转换成为RDD,直接使用DF调用rdd方法即可
scala> personDF.rdd.collect
res38: Array[org.apache.spark.sql.Row] = Array([1,zhangsan,20], [2,lisi,29], [3,wangwu,25], [4,zhaoliu,30], [5,tianqi,35], [6,kobe,40])
第二种方式:通过sparkSession构建DataFrame
val personDF2 = spark.read.text("file:///export/servers/person.txt")
第二种; 读取json文件创建DataFrame
spark给我们提供了json格式的示例文件,路径在
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/src/main/resources/people.json
我们可以直接通过spark解析json数据进行创建DF
val jsonDF = spark.read.json("file:///export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/src/main/resources/people.json")
3、 读取parquet列式存储格式文件创建DataFrame
spark也给我们提供了parquet格式的数据,我们也可以通过spark直接解析parquet格式的数据来进行创建DF,示例文件的路径在
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/src/main/resources/users.parquet
val parquetDF = spark.read.parquet("file:///export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/src/main/resources/users.parquet")
DSL风格语法
DataFrame提供了一个领域特定语言(DSL)来操作结构化数据。
下面是一些使用示例
1、查看DataFrame当中的数据
查看DataFrame中的内容,通过调用show方法
scala> personDF.show
2、查看DataFram当中部分字段的数据
第一种方式查看name字段数据
查看name字段的数据
scala> personDF.select(personDF.col("name")).show
第二种方式查看name字段数据
查看name字段的另一种写法
scala> personDF.select("name").show
第三种方式查看name和age字段数据
查看 name 和age字段数据
scala> personDF.select(col("name"),col("age")).show
第四种方式查看字段数据
查看name和age的数据字段
通过 来 进 行 s c a l a 当 中 字 符 串 的 引 用 , 可 以 使 用 来进行scala当中字符串的引用,可以使用 来进行scala当中字符串的引用,可以使用来进行字段操作
scala> personDF.select($"name",$"age").show
(3)打印DataFrame的Schema信息
personDF.printSchema
(4)查询所有的name和age,并将age+1
personDF.select(col("id"), col("name"), col("age") + 1).show
````
也可以这样:
`````scala
personDF.select(personDF("id"), personDF("name"), personDF("age") + 1).show
````
(5)过滤age大于等于25的,使用filter方法过滤
````scala
personDF.filter(col("age") >= 25).show
````
(6)统计年龄大于30的人数
````scala
personDF.filter(col("age")>30).count()
(7)按年龄进行分组并统计相同年龄的人数
personDF.groupBy("age").count().show
SQL风格语法
DataFrame的一个强大之处就是我们可以将它看作是一个关系型数据表,然后可以通过在程序中使用spark.sql() 来执行SQL语句查询,结果返回一个DataFrame。
如果想使用SQL风格的语法,需要将DataFrame注册成表,采用如下的方式:
1、将DataFrame注册成表
scala> personDF.registerTempTable("t_person")
warning: there was one deprecation warning; re-run with -deprecation for details
2、查询年龄最大的前两名
scala> spark.sql("select * from t_person order by age desc limit 2 ").show
3、显示表的Schema信息
scala> spark.sql("desc t_person").show
4、查询年龄大于30的人的信息
scala> spark.sql("select * from t_person where age > 30").show
创建dataSet一共可以有四种方式
第一种方式:spark.createDataset(“已经存在的scala集合”)
第二种方式:spark.createDataset(“已经存在的RDD”)
第三种方式:已经存在的scala集合调用toDS
第四种方式:通过DataFrame转换变成DataSet
第一种方式创建DataSet
通过spark.createDataset通过集合进行创建dataSet
scala> val ds1 = spark.createDataset(1 to 10)
ds1: org.apache.spark.sql.Dataset[Int] = [value: int]
从已经存在的rdd当中构建dataSet
scala> val ds2 = spark.createDataset(sc.textFile("file:///export/servers/person.txt"))
ds2: org.apache.spark.sql.Dataset[String] = [value: string]
scala> ds2.show
通过样例类配合创建DataSet
scala> case class Person(name:String,age:Int)
scala> val personDataList = List(Person("zhangsan",18),Person("lisi",28))
scala> val personDS = personDataList.toDS
personDS: org.apache.spark.sql.Dataset[Person] = [name: string, age: int]
scala> personDS.show
+--------+---+
| name|age|
+--------+---+
|zhangsan| 18|
| lisi| 28|
+--------+---+
第三种方式创建DataSet
通过DataFrame转化生成
使用as[类型]转换为DataSet
scala> case class Person(name:String,age:Long)
defined class Person
scala> val jsonDF = spark.read.json("file:///export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/src/main/resources/people.json")
jsonDF: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> val jsonDS = jsonDF.as[Person]
jsonDS: org.apache.spark.sql.Dataset[Person] = [age: bigint, name: string]
scala> jsonDS.show
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
DataFrame和DataSet可以相互转化。
(1)DataFrame转为 DataSet
df.as[ElementType] 这样可以把DataFrame转化为DataSet。
(2)DataSet转为DataFrame
ds.toDF() 这样可以把DataSet转化为DataFrame。
注意:RDD也可以与DataSet互相转换
RDD转换成为DataSet直接通过rdd调用toDS方法即可
scala> val lineRDD = sc.textFile("file:///export/servers/person.txt")
scala> lineRDD.toDS.show
DataSet转换成为RDD直接通过dataSet调用rdd方法即可
scala> val lineRDD = sc.textFile("file:///export/servers/person.txt")
scala> val lineDataSet = lineRDD.toDS
scala> val lineRDD = lineDataSet.rdd
编写Spark SQL程序实现RDD转换成DataFrame
通过IDEA编写Spark SQL查询程序。
Spark官网提供了两种方法来实现从RDD转换得到DataFrame,第一种方法是利用反射机制,推导包含某种类型的RDD,通过反射将其转换为指定类型的DataFrame,适用于提前知道RDD的schema。第二种方法通过编程接口与RDD进行交互获取schema,并动态创建DataFrame,在运行时决定列及其类型。
第一步:创建maven工程并导入依赖jar包
<properties>
<scala.version>2.11.8scala.version>
<spark.version>2.2.0spark.version>
properties>
<dependencies>
<dependency>
<groupId>org.scala-langgroupId>
<artifactId>scala-libraryartifactId>
<version>${scala.version}version>
dependency>
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-core_2.11artifactId>
<version>${spark.version}version>
dependency>
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-sql_2.11artifactId>
<version>${spark.version}version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-clientartifactId>
<version>2.7.5version>
dependency>
dependencies>
<build>
<sourceDirectory>src/main/scalasourceDirectory>
<testSourceDirectory>src/test/scalatestSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.0version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>net.alchim31.mavengroupId>
<artifactId>scala-maven-pluginartifactId>
<version>3.2.0version>
<executions>
<execution>
<goals>
<goal>compilegoal>
<goal>testCompilegoal>
goals>
<configuration>
<args>
<arg>-dependencyfilearg>
<arg>${project.build.directory}/.scala_dependenciesarg>
args>
configuration>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<version>3.1.1version>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
<configuration>
<filters>
<filter>
<artifact>*:*artifact>
<excludes>
<exclude>META-INF/*.SFexclude>
<exclude>META-INF/*.DSAexclude>
<exclude>META-INF/*.RSAexclude>
excludes>
filter>
filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>mainClass>
transformer>
transformers>
configuration>
execution>
executions>
plugin>
plugins>
build>
第一种方式创建DF:通过反射配合样例类推断Schema
Scala支持使用case class类型导入RDD转换为DataFrame,通过case class创建schema,case class的参数名称会被利用反射机制作为列名。这种RDD可以高效的转换为DataFrame并注册为表。
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{Column, DataFrame, Dataset, SparkSession}
case class Person(id:Int,name:String,age:Int)
object SparkSqlStudy {
def main(args: Array[String]): Unit = {
//第一步:获取sparkSession对象
val sparkSession: SparkSession = SparkSession.builder().appName("sparkSqlStudy").master("local[2]").getOrCreate()
//第二步:获取sparkContext
val sparkContext: SparkContext = sparkSession.sparkContext
sparkContext.setLogLevel("WARN")
//读取文件,生成RDD
val rddFile: RDD[String] = sparkContext.textFile("file:///F:\\scala与spark课件资料教案\\spark课程\\3、spark第三天\\资料\\person.txt")
//将每一行的内容按照空格切分,生成一个单词数组
val wordArray: RDD[Array[String]] = rddFile.map(_.split(" "))
val personRDD: RDD[Person] = wordArray.map(x => Person(x(0).toInt,x(1),x(2).toInt))
//导入隐式转换的类,我们的RDD才可以转换成DataFrame
import sparkSession.implicits._
//将我们的RDD转换成DataFrame
val personDF: DataFrame = personRDD.toDF()
/*********************************sparkSql DSL风格语法 开始**************************************/
personDF.printSchema()
personDF.show()
personDF.show(2)
println(personDF.head())
//查询name字段的所有值
personDF.select("name").show()
personDF.select($"name").show()
personDF.select(personDF.col("name")).show()
personDF.select(new Column("name")).show()
//将年龄的值进行 + 1
personDF.select($"id",$"name",$"age",$"age"+1).show()
//按照年龄进行分组
personDF.groupBy($"age").count().show()
/*********************************sparkSql DSL风格语法 结束**************************************/
/*********************************sparkSql sql风格语法 开始**************************************/
println("**************************")
val personTable: Unit = personDF.registerTempTable("t_person")
val personView: Unit = personDF.createTempView("t_person_view")
//查询表当中所有的数据
sparkSession.sql("select * from t_person").show()
sparkSession.sql("select * from t_person_view").show()
sparkSession.sql("select * from t_person_view where name = 'wangwu'").show()
/*********************************sparkSql sql风格语法 结束**************************************/
sparkContext.stop()
sparkSession.close()
}
}
第二种方式创建DF:通过StructType配合Row直接指定Schema
当case class不能提前定义好时,可以通过以下三步创建DataFrame
(1)将RDD转为包含Row对象的RDD
(2)基于StructType类型创建schema,与第一步创建的RDD相匹配
(3)通过sparkSession的createDataFrame方法对第一步的RDD应用schema创建DataFrame
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{IntegerType, LongType, StringType, StructType}
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
object SparkSqlSchema {
def main(args: Array[String]): Unit = {
//创建sparkSession
val sparkSession: SparkSession = SparkSession.builder().appName("SparkSqlSchema").master("local[2]").getOrCreate()
//通过sparkSession构建SparkContext对象
val sparkContext: SparkContext = sparkSession.sparkContext
//设置日志级别
sparkContext.setLogLevel("WARN")
//读取文件内容,通过文件内容构建RDD
val fileRdd: RDD[String] = sparkContext.textFile("file:///F:\\scala与spark课件资料教案\\spark课程\\3、spark第三天\\资料\\person.txt")
//文本内容的每一行进行切分
val arrayRdd: RDD[Array[String]] = fileRdd.map(x => x.split(" "))
//将每一个数组里面的三个值取出来,构建row对象
val rowRdd: RDD[Row] = arrayRdd.map(x => Row(x(0).toInt,x(1),x(2).toInt))
//构建StructType对象
val struct:StructType = (new StructType).add("id", IntegerType, true, "id第一个字段").add("name", StringType, true, "name第二个字段").add("age", IntegerType, true, "age第三个字段")
//通过row以及structType来得到DataFrame
val personDataFrame: DataFrame = sparkSession.createDataFrame(rowRdd,struct)
//打印schema信息
personDataFrame.printSchema()
//将我们获取的DataFrame
val personView: Unit = personDataFrame.createTempView("t_person_view")
sparkSession.sql("select * from t_person_view").show()
sparkContext.stop()
sparkSession.close()
}
}