Spark SQL与 DataFrame

12 篇文章 0 订阅
9 篇文章 3 订阅

Spark SQL是Spark生态系统中非常重要的组件,其前身为Shark。Shark是Spark上的数据仓库,最初设计成与Hive兼容,但是该项目于2014年开始停止开发,转向Spark SQL。Spark SQL全面继承了Shark,并进行了优化。

Shark即Hive on Spark,为了实现与Hive兼容,Shark在HiveQL方面重用了Hive中的HiveQL解析、逻辑执行计划翻译、执行计划优化等逻辑,可以近似认为仅将物理执行计划从MapReduce作业替换成了Spark作业,通过Hive的HiveQL解析,把HiveQL翻译成Spark上的RDD操作。

Shark的设计导致了两个问题:

一是执行计划优化完全依赖于Hive,不方便添加新的优化策略;

二是因为Spark是线程级并行,而MapReduce是进程级并行,因此,Spark在兼容Hive的实现上存在线程安全问题,导致Shark不得不使用另外一套独立维护的打了补丁的Hive源码分支。

Shark的实现继承了大量的Hive代码,因而给优化和维护带来了大量的麻烦,特别是基于MapReduce设计的部分,成为整个项目的瓶颈。因此,在2014年的时候,Shark项目中止,并转向Spark SQL的开发。

Spark SQL在Hive兼容层面仅依赖HiveQL解析和Hive元数据,也就是说,从HQL被解析成抽象语法树(AST)起,就全部由Spark SQL接管了。Spark SQL执行计划生成和优化都由Catalyst(函数式关系查询优化框架)负责。

SparkSQL支持多种不同的外部数据源,将其转化为DataFrame,然后进行SQL查询。Spark SQL目前支持Scala、Java、Python三种语言,支持SQL-92规范。

 

1.DataFrame概述

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

DataFrame与RDD的区别

RDD是分布式的 Java对象的集合,比如,RDD[Person]是以Person为类型参数,但是,Person类的内部结构对于RDD而言却是不可知的。DataFrame是一种以RDD为基础的分布式数据集,也就是分布式的Row对象的集合(每个Row对象代表一行记录),提供了详细的结构信息,也就是我们经常说的模式(schema),Spark SQL可以清楚地知道该数据集中包含哪些列、每列的名称和类型。

和RDD一样,DataFrame的各种变换操作也采用惰性机制,只是记录了各种转换的逻辑转换路线图(是一个DAG图),不会发生真正的计算,这个DAG图相当于一个逻辑查询计划,最终,会被翻译成物理查询计划,生成RDD DAG,按照之前介绍的RDD DAG的执行方式去完成最终的计算得到结果。

 

2.dataframe的读取

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

SparkSession支持从不同的数据源加载数据,并把数据转换成DataFrame,并且支持把DataFrame转换成SQLContext自身中的表,然后使用SQL语句来操作数据。SparkSession亦提供了HiveQL以及其他依赖于Hive的功能的支持。

下面介绍如何使用SparkSession来创建DataFrame。

下面三条语句主要用来生成一个sparkSession对象,这个SparkSession对象就是整个sparksql的指挥官。这指挥官在独立应用程序时需要这三条语句生成的,但是在pyspark里,就不需要人工生成,因为启动pyspark可以自动生成。

>>> from pyspark import SparkContext,SparkConf
>>> from pyspark.sql import SparkSession
>>> spark = SparkSession.builder.config(conf=SparkConf()).getOrCreate()

首先,找到样例数据。 Spark已经为我们提供了几个样例数据,就保存在“/usr/local/spark/examples/src/main/resources/”这个目录下,这个目录下有两个样例数据people.json和people.txt。

hadoop@rachel-virtual-machine:/usr/local/spark/examples/src/main/resources$ ll
总用量 48
drwxr-xr-x 2 hadoop rachel 4096 2月   5  2019 ./
drwxr-xr-x 7 hadoop rachel 4096 2月   5  2019 ../
-rw-r--r-- 1 hadoop rachel  130 2月   5  2019 employees.json
-rw-r--r-- 1 hadoop rachel  240 2月   5  2019 full_user.avsc
-rw-r--r-- 1 hadoop rachel 5812 2月   5  2019 kv1.txt
-rw-r--r-- 1 hadoop rachel   49 2月   5  2019 people.csv
-rw-r--r-- 1 hadoop rachel   73 2月   5  2019 people.json
-rw-r--r-- 1 hadoop rachel   32 2月   5  2019 people.txt
-rw-r--r-- 1 hadoop rachel  185 2月   5  2019 user.avsc
-rw-r--r-- 1 hadoop rachel  334 2月   5  2019 users.avro
-rw-r--r-- 1 hadoop rachel  615 2月   5  2019 users.parquet
hadoop@rachel-virtual-machine:/usr/local/spark/examples/src/main/resources$ cat people.json
{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}
hadoop@rachel-virtual-machine:/usr/local/spark/examples/src/main/resources$ cat people.txt
Michael, 29
Andy, 30
Justin, 19
hadoop@rachel-virtual-machine:/usr/local/spark/examples/src/main/resources$ 

spark可通过以下方式读取文件:

DataFrame读取json数据:

>>> df = spark.read.json("file:///usr/local/spark/examples/src/main/resources/people.json")
>>> df.show()                                                                   
+----+-------+
| age|   name|
+----+-------+
|null|Michael|
|  30|   Andy|
|  19| Justin|
+----+-------+

3. DataFrame的保存

如何把RDD保存成文本文件:

注:这里需要给出全程路径

也可以使用下面的格式进行保存

从示例文件people.json中创建DataFrame,保存到另外一个json文件中:

>>> df.select("name","age").write.format("json").save("file:///usr/local/spark/mycode/sparksql/newpeople.json")
>>> df.select("name").write.format("text").save("file:///usr/local/spark/mycode/sparksql/newpeople.txt")

可以看出,这里使用select(“name”, “age”)确定要把哪些列进行保存,然后调用write.format(“json”).save ()保存成json文件。

write.format()支持输出 json,parquet, jdbc, orc, libsvm, csv, text等格式文件,如果要输出文本文件,可以采用write.format(“text”),但是,需要注意,只有select()中只存在一个列时,才允许保存成文本文件,如果存在两个列,比如select(“name”, “age”),就不能保存成文本文件。

在Shell命令提示符下查看新生成的newpeople.json、newpeople.txt:

hadoop@rachel-virtual-machine:/usr/local/spark/mycode/sparksql$ ll
总用量 16
drwxrwxr-x 4 hadoop hadoop 4096 8月  29 10:52 ./
drwxrwxr-x 4 hadoop hadoop 4096 8月  29 10:50 ../
drwxrwxr-x 2 hadoop hadoop 4096 8月  29 10:50 newpeople.json/
drwxrwxr-x 2 hadoop hadoop 4096 8月  29 10:52 newpeople.txt/
hadoop@rachel-virtual-machine:/usr/local/spark/mycode/sparksql$ cd newpeople.json
hadoop@rachel-virtual-machine:/usr/local/spark/mycode/sparksql/newpeople.json$ ll
总用量 20
drwxrwxr-x 2 hadoop hadoop 4096 8月  29 10:50 ./
drwxrwxr-x 4 hadoop hadoop 4096 8月  29 10:52 ../
-rw-r--r-- 1 hadoop hadoop   71 8月  29 10:50 part-00000-ff096ad7-a016-49cc-9d68-2ff83ed1336a-c000.json
-rw-r--r-- 1 hadoop hadoop   12 8月  29 10:50 .part-00000-ff096ad7-a016-49cc-9d68-2ff83ed1336a-c000.json.crc
-rw-r--r-- 1 hadoop hadoop    0 8月  29 10:50 _SUCCESS
-rw-r--r-- 1 hadoop hadoop    8 8月  29 10:50 ._SUCCESS.crc

hadoop@rachel-virtual-machine:/usr/local/spark/mycode/sparksql/newpeople.json$ cat part-00000-ff096ad7-a016-49cc-9d68-2ff83ed1336a-c000.json
{"name":"Michael"}
{"name":"Andy","age":30}
{"name":"Justin","age":19}
hadoop@rachel-virtual-machine:/usr/local/spark/mycode/sparksql/newpeople.json$ 

从上可以看出生成了一个newpeople.json目录和一个newpeople.txt目录。

注:newpeople.json是一个目录,不是文件。而下面的part-00000-ff096ad7-a016-49cc-9d68-2ff83ed1336a-c000.json才是我们存取的文件。

如果我们要再次把newpeople.json中的数据加载到RDD中,可以直接使用newpeople.json目录名称,而不需要使用part-00000-ff096ad7-a016-49cc-9d68-2ff83ed1336a-c000.json 文件,如下:


>>> textFile = sc.textFile("file:///usr/local/spark/mycode/sparksql/newpeople.json")
>>> textFile.foreach(print)                                                     
{"name":"Justin","age":19}
{"name":"Michael"}
{"name":"Andy","age":30}

 

4.Dataframe的常用操作

 

>>> df.printSchema() #打印dataframe的模式信息
root
 |-- age: long (nullable = true)
 |-- name: string (nullable = true)

#显示某列
>>> df.select(df["name"],df["age"]+1).show()
+-------+---------+
|   name|(age + 1)|
+-------+---------+
|Michael|     null|
|   Andy|       31|
| Justin|       20|
+-------+---------+
>>> df.filter(df["age"]>20).show()#筛选大于20的用户
+---+----+
|age|name|
+---+----+
| 30|Andy|
+---+----+

>>> df.groupBy("age").count().show()#统计每个年龄出现的次数
+----+-----+                                                                    
| age|count|
+----+-----+
|  19|    1|
|null|    1|
|  30|    1|
+----+-----+
>>> df.sort(df["age"].desc()).show() #根据age列进行降序排序
+----+-------+
| age|   name|
+----+-------+
|  30|   Andy|
|  19| Justin|
|null|Michael|
+----+-------+

>>> df.sort(df["age"].desc(),df["name"].asc()).show()#选择两列进行排序
+----+-------+
| age|   name|
+----+-------+
|  30|   Andy|
|  19| Justin|
|null|Michael|
+----+-------+

5.利用反射机制推断RDD模式

如何从RDD转换得到DataFrame,因为很多时候数据是以RDD形式存在的。Spark提供了两种方法来实现从RDD转换得到DataFrame,第一种方法是,利用反射来推断包含特定类型对象的RDD的schema,适用对已知数据结构的RDD转换;第二种方法是,使用编程接口,构造一个schema并将其应用在已知的RDD上。

现以下面的数据进行分析:

>>> from pyspark.sql import Row #用Row对象封装数据
#生成 RDD
>>> people = spark.sparkContext.textFile("file:///usr/local/spark/examples/src/main/resources/people.txt").map(lambda line:line.split(",")).map(lambda p:Row(name=p[0],age=int(p[1])))
>>> schemaPeople = spark.createDataFrame(people)#将RDD转换为DataFrame,并命名为schemaPeople

在上面的代码中,.map(lambda line:line.split(",")) 作用是对读入的这个RDD中的每一行元素都进行解析。这行内容经过 .map(lambda line:line.split(",")) 操作后,就得到一个集合{Michael,29}。后面经过 .map(lambda p:Row(name=p[0],age=int(p[1]))) 操作时,这时的p就是这个集合{Michael,29},这时p[0]就是Micheael,p[1]就是29,map(lambda p :Row(p[0],p[1].strip())) 就会生成一个Row对象,这个对象里面包含了两个字段的值,这个Row对象就构成了rowRDD中的其中一个元素。因为people 有3行文本,所以,最终,rowRDD中会包含3个元素,每个元素都是org.apache.spark.sql.Row类型。实际上,Row对象只是对基本数据类型(比如整型或字符串)的数组的封装,本质就是一个定长的字段数组。

转化为DataFrame后,不能进行查询,必须注册为临时表才能供下面查询

>>> schemaPeople.createOrReplaceTempView("people")#括号里的people并不是上面读取的people,而是将临时表命名为people

然后就可基于这个临时表进行查询

>>> peosonsDF = spark.sql("select name,age from people where age>20")

查询的结果返回的是一个DataFrame。

下面将DataFrame转换为RDD:

>>> personsDFRDD = peosonsDF.rdd.map(lambda p:"Name:"+p.name+","+"Age:"+str(p.age))

>>> personsDFRDD.foreach(print)                                                 
Name:Michael,Age:29
Name:Andy,Age:30

上面peosonsDF.rdd就把DataFrame转换为RDD了,然后才执行RDD中方法map。

 

6.使用编程方式定义RDD模式

当我们提前知道数据的结构时,可以反射机制得到RDD模式,但当无法提前获知数据结构时,采用编程方式定义RDD模式。

使用createDataFrame(rdd, schema)编程方式定义RDD模式。

1)先生成表头

>>> from pyspark.sql.types import *
>>> from pyspark.sql import Row
# 定义一个模式字符串
>>> schemaString = "name age"
# 根据模式字符串生成模式
>>> fileds = [StructField(field_name,StringType(),True) for field_name in schemaString.split(" ")] 
>>> schema = StructType(fileds)
#从上面信息可以看出,schema描述了模式信息,模式中包含name和age两个字段

fileds是一个列表,列表包含两个元素,每个元素是一个StructField对象,每个StructField对象都是来描述一个字段。

2)生成表中记录

>>> lines = spark.sparkContext.textFile("file:///usr/local/spark/examples/src/main/resources/people.txt")
>>> parts = lines.map(lambda x:x.split(","))
>>> people = parts.map(lambda p :Row(p[0],p[1].strip()))

在上面的代码中,lines.map(lambda x:x.split(",")) 作用是对lines这个RDD中的每一行元素都进行解析。这行内容经过 lines.map(lambda x:x.split(",")) 操作后,就得到一个集合{Michael,29}。后面经过 map(lambda p :Row(p[0],p[1].strip())) 操作时,这时的p就是这个集合{Michael,29},这时p[0]就是Micheael,p[1]就是29,map(lambda p :Row(p[0],p[1].strip())) 就会生成一个Row对象,这个对象里面包含了两个字段的值,这个Row对象就构成了rowRDD中的其中一个元素。因为lines 有3行文本,所以,最终,rowRDD中会包含3个元素,每个元素都是org.apache.spark.sql.Row类型。实际上,Row对象只是对基本数据类型(比如整型或字符串)的数组的封装,本质就是一个定长的字段数组。

3)下面进行拼接

>>> schemaPelple = spark.createDataFrame(people,schema)

这条语句就相当于建立了rowRDD数据集和模式之间的对应关系,从而我们就知道对于rowRDD的每行记录,第一个字段的名称是schema中的“name”,第二个字段的名称是schema中的“age”。

4)进行查询

必须将这个DataFrame注册成为一个临时表才能够进行查询

#必须注册为临时表才能供下面查询使用
>>> schemaPeople.createOrReplaceTempView("people")     
>>> results = spark.sql("select name,age from people")
>>> results
DataFrame[name: string, age: bigint]
>>> results.show()
+-------+---+
|   name|age|
+-------+---+
|Michael| 29|
|   Andy| 30|
| Justin| 19|
+-------+---+

 

http://ifeve.com/spark-sql-dataframes/

http://spark.apache.org/docs/1.6.2/api/scala/index.html#org.apache.spark.sql.AnalysisException

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值