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提供的对外接口比较人性化,使用起来很方便。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容