spark学习笔记2-Spark SQL

本文是对Spark SQL基础知识的一个学习总结,包含如下几部分的内容:

  • 概述
  • SparkSession类
  • DataFrame类
  • Sql语句操作
  • DataSet类
  • 数据源
  • 小结

预备知识:

1、Spark SQL是在Spark Core基础上的一个扩展库,如果需要了解spark的基础知识,可参考文档《spark学习笔记1-基础部分》。

2、Spark SQL支持类SQL操作,所以对关系数据以及其sql语句提前了解有助于加快对Spark SQL的API的熟悉。

一、概述

Spark SQL是Spark用来操作结构化数据的组件。通过Spark SQL,用户可以使用SQL或者Apache Hive版本的SQL方言(HQL)来查询数据。Spark SQL支持多种数据源类型,例如Hive表、Parquet以及JSON等。Spark SQL不仅为Spark提供了一个SQL接口,还支持开发者将SQL语句融入到Spark应用程序开发过程中,无论是使用Python、Java还是Scala,用户可以在单个的应用中同时进行SQL查询和复杂的数据分析。

Spark SQL是spark扩展库中使用最广泛的库,本文将针对其一些重要的API如何使用进行介绍。

本文涉及的案例,我们会在spark shell命令行下进行代码的编写和演示。

spark下载的版本的examples目录下,提供了一些其扩展功能(包括Spark SQL)的应用举例,含源代码和一些资源文件。为了方便,本文需要使用的一些资源文件会直接使用examples目录中提供的。

Spark SQL提供了scala,java,python,R多种编程语言的API,本文使用的是针对scala的API。其相关API位于 org.apache.spark.sql 包中,编写代码时需要先import相关的类和对象。

二、SparkSession类

Spark中所有功能的入口点都是SparkSession类。要创建基本的SparkSession,只需使用SparkSession.builder()。

我们先启动spark-shell,注意,为了便于对exampels下的资源文件的相对路径引用,我们控制台的当前目录不要使用bin目录,而是使用bin目录的上级目录。

下面我们在spark shell命令行下进行代码的编写和执行。

首先我们执行的代码如下:

scala> import org.apache.spark.sql.SparkSession

import org.apache.spark.sql.SparkSession

scala> val spark = SparkSession.builder().appName("demo").getOrCreate()

spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@2087c939

上面代码我们首先将SparkSession类import进来,然后接连调用多个方法创建SparkSession对象。

三、DataFrame类

我们先看一个简单的创建DataFrame对象的方式,我们可以将列表对象转换为DataFrame对象。如下面例子:

scala> val df = Seq(1,2).toDF()

df: org.apache.spark.sql.DataFrame = [value: int]

scala> df.show()

+-----+

|value|

+-----+

|    1|

|    2|

+-----+

上面代码先创建了一个整型列表,然后调用其toDF方法转换为DataFrame对象。并调用DataFrame的show方法显示数据。可以看出,对于这种基本类型,DataFrame的列名默认为value。

我们再看一个稍微复杂的例子,如下面代码所示:

scala> case class Person(name:String,age:Int)

defined class Person

scala> val df = Seq(Person("jack",12),Person("tom",20)).toDF()

df: org.apache.spark.sql.DataFrame = [name: string, age: int]

scala> df.show()

+----+---+

|name|age|

+----+---+

|jack| 12|

| tom| 20|

+----+---+

上面例子中,将一个自定义的类的列表转换为DataFrame,可以看出,这时类中的字段名自动变为DataFrame的列名。

在实际的项目中,我们一般会从外部数据源(如文件)来创建DataFrame对象。这可以通过Spark SQL的SparkSession对象来完成。Spark SQL支持通过DataFrame接口对各种数据源进行操作,包括本地文件,hdfs上的文件,jdbc数据,hive表等。

为了简单,我们使用spark的安装目录下的examples目录下的josn文件为例进行说明。例子代码如下:

scala> val df = spark.read.json("examples/src/main/resources/people.json")

df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]

上面代码获得了一个DataFrame对象,其引用的数据就是people.json文件中的内容,其包含的信息如下:

{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}

因为DataFrame概念上就是一个二维表,带有schema信息。对照上面json文件的内容,从控制台输出的信息看出,json对象中的name和age属性就会被映射成DataFrame的两个字段,并且都是有类型的,name被设为string类型,age被设为bigint类型。

有了DataFrame对象,我们就可以使用其各种方法来对数据进行操作。下面介绍一些常见的方法:

1、show方法

show方法用于显示DataFrame中的所有数据,代码如:

scala> df.show()

+----+-------+

| age|   name|

+----+-------+

|null|Michael|

|  30|   Andy|

|  19| Justin|

+----+-------+

可以看出,DataFrame以表格的方式显示其中的数据,类似我们在关系数据库操作中执行select语句返回结果的形式。

DataFrame类还提供了很多的特定方法完成对数据的操作,下面继续说明。

2、select方法

DataFrame的select方法用于返回一个新的数据集,可以只包含指定的列的数据。

scala> df.select("name")

res2: org.apache.spark.sql.DataFrame = [name: string]

scala> df.select("name").show()

+-------+

|   name|

+-------+

|Michael|

|   Andy|

| Justin|

+-------+

其select方法,可以返回新的DataFrame对象,其中数据只包含指定的列。

select方法还提供了更强的功能,如下面例子:

scala> df.select($"name",$"age"+1)

res7: org.apache.spark.sql.DataFrame = [name: string, (age + 1): bigint]

scala> df.select($"name",$"age"+1).show()

+-------+---------+

|   name|(age + 1)|

+-------+---------+

|Michael|     null|

|   Andy|       31|

| Justin|       20|

+-------+---------+

可以看出,select方法中,还可以对字段进行表达式操作,类似关系数据库sql语句的select语句中对字段的操作。

3、filter方法

我们还可以利用DataFrame提供的filter方法来实现类似关系数据库中select语句中的where子句功能。如下面例子:

scala> df.filter($"age" > 21)

res9: org.apache.spark.sql.Dataset[org.apache.spark.sql.Row] = [age: bigint, name: string]

scala> df.filter($"age" > 21).show()

+---+----+

|age|name|

+---+----+

| 30|Andy|

+---+----+

可以看出filter方法的参数就是一个条件表达式,满足条件的数据保留,返回一个新的DataFrame对象。

3、汇聚计算

类似关系数据库的select语句,可以使用group by等子句进行一些分组汇总计算,DataFrame对象也有类似功能,如下面例子:

scala> df.groupBy("age")

res11: org.apache.spark.sql.RelationalGroupedDataset = org.apache.spark.sql.RelationalGroupedDataset@1e73e9e2

scala> df.groupBy("age").count()

res12: org.apache.spark.sql.DataFrame = [age: bigint, count: bigint]

scala> df.groupBy("age").count().show()

+----+-----+

| age|count|

+----+-----+

|  19|    1|

|null|    1|

|  30|    1|

+----+-----+

上面输出信息可以看出,DataFrame类的groupBy方法返回的不是DataFrame对象,而是RelationalGroupedDataset类型对象,然后调用RelationalGroupedDataset类的count方法返回一个DataFrame对象。

上面只是列举了DataFrame类的一些简单的列引用和表达式之外,数据集还具有丰富的函数库,包括字符串操作,日期算术,常用数学运算等,这里不再详细介绍。

从上面的例子可以看出,几个常见的操作与关系数据库的select语句非常类似。而Spark SQL的强大之处是其可以直接通过编写类sql语句来访问数据。下面的章节来介绍。

四、Sql语句操作

Spark SQL提供了使用sql语句来对数据进行操作。我们先看一个例子代码:

scala> import org.apache.spark.sql.SparkSession

import org.apache.spark.sql.SparkSession

scala> val spark = SparkSession.builder().appName("demo").getOrCreate()

spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@3c5bb37d

scala> val df = spark.read.json("examples/src/main/resources/people.json")

df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]

scala> df.createOrReplaceTempView("people")

scala> val sqlDF = spark.sql("SELECT * FROM people")

sqlDF: org.apache.spark.sql.DataFrame = [age: bigint, name: string]

scala> sqlDF.show()

+----+-------+

| age|   name|

+----+-------+

|null|Michael|

|  30|   Andy|

|  19| Justin|

+----+-------+

在上面代码中,我们看到df.createOrReplaceTempView("people") 这个语句,该语句的含义是 将创建的DataFrame注册为SQL临时视图,这样下面就可以调用SparkSession对象的sql方法来执行select语句,该方法返回的是DataFrame对象。

这样我们就可以执行各种复杂的的select语句,如下面例子:

spark.sql("SELECT * FROM people where age>20").show()

spark.sql("SELECT count(*) FROM people").show()

五、DataSet类

DataSet是spark在2.0中替换RDD的数据模型,它比RDD执行效率更高。有关DataSet类的介绍我们在《spark学习笔记1-基础部分》文档中已经有介绍。下面主要介绍它和DataFrame的关系。

DataSet和DataFrame非常类似,它们可以相互转换,也有很多相同的操作方法,下面举例来说明。

我们先看下DataSet如何转换为DataFrame对象,如下面例子:

scala> val ds = Seq("hello","world").toDS()

ds: org.apache.spark.sql.Dataset[String] = [value: string]

scala> val df = ds.toDF()

df: org.apache.spark.sql.DataFrame = [value: string]

上面例子代码先创建了一个DataSet对象,然后调用DataSet类的toDF方法转换为DataFrame对象。

我们再看一个例子:

case class Person(name:String,age:Int)

scala> val ds = Seq(Person("jack",12)).toDS()

ds: org.apache.spark.sql.Dataset[Person] = [name: string, age: int]

scala> val df = ds.toDF()

df: org.apache.spark.sql.DataFrame = [name: string, age: int]

scala> df.select("name")

res7: org.apache.spark.sql.DataFrame = [name: string]

scala> ds.select("name")

res8: org.apache.spark.sql.DataFrame = [name: string]

从上面例子可以看出,DataSet和DataFrame都有select方法。

下面我们再来看下如何将DataFrame对象转为为DataSet对象,如下面例子:

scala> val df = Seq("hello","world").toDF()

df: org.apache.spark.sql.DataFrame = [value: string]

scala> val ds = df.as[String]

ds: org.apache.spark.sql.Dataset[String] = [value: string]

scala> ds.show()

+-----+

|value|

+-----+

|hello|

|world|

+-----+

从上面代码可以看出,调用DataFrame的as方法,并指定转换后的数据类型,则可以把DataFrame转换为DataSet

下面再看一个例子:

import org.apache.spark.sql.SparkSession

val spark = SparkSession.builder().appName("newdemo").getOrCreate()

val df = spark.read.json("examples/src/main/resources/people.json")

case class Person(name:String,age:Long)

val ds = df.as[Person]

上面代码可以看出,我们定义了个与所加载数据的格式匹配的类,来作为DataSet中元素的数据类型。

前面的学习我们们知道,可以通过DataFrame创建临时表,然后执行sql语句。通过DataSet也是可以的。如下面例子:

scala> case class Person(name:String,age:Long)

defined class Person

scala> val ds = Seq(Person("jack",20),Person("tom",23)).toDS()

ds: org.apache.spark.sql.Dataset[Person] = [name: string, age: bigint]

scala> ds.createOrReplaceTempView("user")

scala> val re = spark.sql("select * from user where age>20");

re: org.apache.spark.sql.DataFrame = [name: string, age: bigint]

scala> re.show()

+----+---+

|name|age|

+----+---+

| tom| 23|

+----+---+

可以看出,DataSet和DataFrame在使用时其实没有太多的差别。

六、数据源

Spark SQL支持通过DataFrame接口对各种数据源进行操作,不仅可以从各种格式的数据源中获取数据,也可以将计算后的数据保存到各种格式中。本节介绍Spark SQL支持的常见数据源操作。

(一)默认数据源

Spark SQL默认的数据源格式是parquet格式,如下面例子:

scala> import org.apache.spark.sql.SparkSession

import org.apache.spark.sql.SparkSession

scala> val spark = SparkSession.builder().appName("demo").getOrCreate()

spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@3c5bb37d

scala> val df = spark.read.load("examples/src/main/resources/users.parquet")

df: org.apache.spark.sql.DataFrame = [name: string, favorite_color: string ... 1 more field]

scala> df.show()

+------+--------------+----------------+

|  name|favorite_color|favorite_numbers|

+------+--------------+----------------+

|Alyssa|          null|  [3, 9, 15, 20]|

|   Ben|           red|              []|

+------+--------------+----------------+

scala> df.select("name", "favorite_color")

res1: org.apache.spark.sql.DataFrame = [name: string, favorite_color: string]

上面代码中我们看到有这个语句

val df = spark.read.load("examples/src/main/resources/users.parquet")

对比上面章节中的代码

val df = spark.read.json("examples/src/main/resources/people.json")

可以看出,后一语句通过函数名json指定加载的数据是json格式,而上一语句是个通用的load语句,这时默认的是parquet格式。

Parquet是面向分析型业务的列式存储格式,由Twitter和Cloudera合作开发,2015年5月从Apache的孵化器里毕业成为Apache顶级项目。Parquet仅仅是一种存储格式,它是语言、平台无关的,并且不需要和任何一种数据处理框架绑定,可适配多种语言和组件。更详细的关于Parquet格式,可查看相关资料。

我们可以将DataFrame对象中的数据保存到Parquet文件中,举例如下:

scala> df.select("name", "favorite_color").write.save("savedata")

上面代码先从df中获取一个新的数据集(DataFrame),然后调用write和save方法保存新的数据集中数据到本地。需要说明的是,最后在运行spark shell的当前目录生成的并不是save方法参数指定的 savedata 文件,而是生成了一个savedata目录,目录中含一些中间数据(类似Mapreduce计算生成的中间文件)和一个结果文件,文件名如part-00000-a3924506-d3e5-4129-aedc-4b744ce8af7b.snappy.parquet。

我们可以通过代码重新查看该parquet文件中数据,看看是否与预期的一致。代码如下:

scala> val newdf = spark.read.load("savedata/part-00000-a3924506-d3e5-4129-aedc-4b744ce8af7b.snappy.parquet")

newdf: org.apache.spark.sql.DataFrame = [name: string, favorite_color: string]

scala> newdf.show()

+------+--------------+

|  name|favorite_color|

+------+--------------+

|Alyssa|          null|

|   Ben|           red|

+------+--------------+

可以看出,该parquet文件中数据与前面的 df.select("name", "favorite_color")获取的数据一样。说明保存操作是成功的。

(二)指定数据源格式

我们在load数据时可以指定数据源的格式,如指定json格式:

val df = spark.read.format("json").load("examples/src/main/resources/people.json")

上面代码通过调用format函数指定加载数据的格式为json。

同样我们可以写入数据到文件,如:

df.select("name").write.save("peopledata")

上面代码会写入数据到parquet格式的文件中。如果我们希望写入还是json格式,可以按如下方式编写代码:

df.select("name").write.format("json").save("peoplejsondata")

需要说明的是,同前面介绍的一样,写入数据时save方法指定的参数是最后生成的目录名,具体的文件在目录中,除数据文件外,还包括一些中间过程文件。

我们还可以指定其它格式的文件,如csv等,这里不再一一介绍。

(三)利用Jdbc使用关系数据库

Spark SQL支持使用jdbc将关系数据库作为数据源,如果要在spark shell中使用jdbc连接数据库,需要事先加载相应数据库的驱动Jar包。

比如我们要连接mysql数据库,首先要有mysql的jdbc驱动库,然后使用这样的方式启动spark shell。

spark-shell --driver-class-path mysql-connector-java-5.1.5-bin.jar --jars mysql-connector-java-5.1.5-bin.jar

上面命令中的 mysql-connector-java-5.1.5-bin.jar 是连接Mysql数据库的jdbc驱动程序,这里没有指定路径,需要位于当前目录下。

启动spark-shell,然后就可以加载数据库中的表,代码如:

import java.util.Properties

val props = new Properties()

props.put("driver", "com.mysql.jdbc.Driver")

props.put("user", "root")

props.put("password", "")

val df = spark.read.jdbc("jdbc:mysql://localhost:3306/dpms", "test", props)

上述代码中spark.read.jdbc方法的第1个参数是连接mysql数据库的标准url信息,第2个参数是表名,第3个参数是连接数据库的基本信息。所需的信息与我们直接利用Java代码编写jdbc程序一样。

然后我们就可以正常使用上面创建的DataFrame对象了。

七、小结

在本文中,我们对如何使用Spark SQL的核心API进行数据处理进行了基本的介绍,并介绍了Spark SQL如何对接不同的数据源。本文没有涉及到Spark SQL的内部机制和原理。可以看出,站在使用的角度看,Spark SQL提供的对外接口比较人性化,使用起来很方便。

你可能感兴趣的:(spark学习笔记2-Spark SQL)