spark教程(11)-sparkSQL 数据抽象
数据抽象
sparkSQL 的数据抽象是 DataFrame,df 相当于表格,它的每一行是一条信息,形成了一个 Row
Row
它是 sparkSQL 的一个抽象,用于表示一行数据,从表现形式上看,相当于一个 tuple 或者 表中的一行;
from pyspark.sql import Row ##### 创建 Row
#### method 1
row = Row(name="Alice", age=11)
print row # Row(age=11, name='Alice')
print row['name'], row['age'] # ('Alice', 11)
print row.name, row.age # ('Alice', 11)
print 'name' in row # True
print 'wrong_key' in row # False #### method 2
Person = Row("name", "age")
print Person # <Row(name, age)>
print 'name' in Person # True
print 'wrong_key' in Person # False
print Person("Alice", 11) # Row(name='Alice', age=11)
DataFrame (DF)
与 RDD 类似,df 也是分布式的数据容器,不同的是,df 更像一个 二维数据表,除了数据本身外,还包含了数据的结构信息,即 schema;

df 的 API 提供了更高层的关系操作,比函数式的 RDD API 更加友好;
df 的底层仍是 RDD,所以 df 也是惰性执行的,但值得注意的是,它比 RDD 性能更高;
问题来了:为什么底层实现是 RDD,却比 RDD 更快,不合常理啊
其实是这样的,因为 df 是由 spark 自己转换成 RDD 的,那么 spark 自然会用最合适的、最优化的方式转换成 RDD,因为它比任何人都清楚怎么才能更高效,
对比我们自己操作 RDD 去实现各种功能,大部分情况下我们的作法可能不是最优,自己玩不如作者玩,所以说 df 性能高于 RDD
举个简单例子:
data1 = sc.parallelize([('','a'), ('', 'b'), ('', 'c')])
data2 = sc.parallelize([('',''), ('', ''), ('', '')])
### 找到两个list中 key 为 1 的对应值的集合
## 自己写可能这么写
data1.join(data2).collect() # [('1', ('a', '1')), ('3', ('c', '3')), ('2', ('b', '2'))]
data1.join(data2).filter(lambda x: x[0] == '').collect() # [('1', ('a', '1'))]
## spark 可能这么写
data1.filter(lambda x: x[0] == '').join(data2.filter(lambda x: x[0] == '')).collect() # [('1', ('a', '1'))]
为什么 spark 这么写快呢?这里简单解释下
join 是把 两个元素做 笛卡尔內积,生成了 3x3=9 个元素,然后 shuffle,每个分区分别比较 key 是否相同,如果相同,合并,然后合并分区结果;
我们自己写的就是这样,shuffle 了 9 个元素;
而 spark 是先 filter,每个 list 变成了 一个元素,然后 join,join 的结果直接就是所需,不用 shuffle;
shuffle 本身是耗时的,而 filter 无需 shuffle,所以效率高 【join 是个 低效方法的原因】
小结
1. df 也是一个查询优化的手段
2. df 允许我们像操作数据库一样操作它
DataSet
DataSet 是 DataFrame 的扩展,是 spark 最新的数据抽象;
dataSet 像个对象,允许我们像操作类一样操作它,通过属性查看数据;
实际上 DataSet 是在 df 的基础上增加了数据类型;
df 只指定了字段名,而没有指定字段类型,sparkSQL 需要自动推断数据集的格式,这也是一种消耗,而 dataSet 直接指定了字段名和字段属性,效率更高
python 目前不支持 dataSet,所以后续支持了再说
SparkSession
在老版本中,sparkSQL 提供了两种 SQL 查询的起始点:
SQLContext,用于 spark 自己提供的 SQL 查询;
HiveContext,用于连接 hive 的查询
sparkSession 是新版的 SQL 查询起始点,实质上是组合了 SQLContext 和 HiveContext;
sparkSession 只是封装了 sparkContext,sparkContext 包含 SQLContext 和 HiveContext;
所以 sparkSession 实际上还是 依靠 sparkContext 实现了 SQLContext 和 HiveContext,故老版本用法也适用新版本。
DataFrame 的创建
sparkSession 直接生成 df
df 的创建有 3 种方式
从 spark 的数据源创建:读取 spark 支持的文件
从内部 RDD 创建:RDD 转换成 df
从 hive 创建:hive 查询
spark 数据源创建 DF
spark 支持的文件格式都有统一的入口
>>> dir(spark.read)
[ 'csv', 'format', 'jdbc', 'json', 'load', 'option', 'options', 'orc', 'parquet', 'schema', 'table', 'text']
sparkSQL 定义了一个 DataFrameReader 的类,在这个类中定义了所有数据源的接口,spark.read 是这个类的入口
创建 df 的方法也是惰性的
json
json 文件必须每行是一个 json 对象
## json 文件如下
# {'age': '10','name': 'zhangsan'}
# {'age': '20','name': 'lisi'} #### method 1
df1 = spark.read.json('data.json') # 相对路径
# >>> df1 # DataFrame[age: string, name: string] 可以看到 df 具备了字段名和字段属性 df1.show()
# +---+--------+
# |age| name|
# +---+--------+
# | 10|zhangsan|
# | 20| lisi|
# +---+--------+ df2 = spark.read.json('file:///usr/lib/spark/data.json') # 绝对路径 #### method 2
spark.read.format('json').load('/data.json').show()
其他文件读取方式与 json 完全相同
jdbc
spark.read.jdbc('jdbc:postgresql://172.16.89.80:5432/postgres', 'subtable', 'max_lng', 5, 10, 3, properties={'user':'postgres', 'password':'postgres'}).show()
注意
sparkSQL 会自动推断数据集的格式,以 json 为例,sparkSQL 会扫描数据集的每一项从而推断数据格式,
如果我们已经知道数据格式,可以在创建 df 时指定数据格式,从而加速创建过程且避免扫描数据集
from pyspark.sql.types import StructType, StructField, StringType, IntegerType
schema = StructType([StructField("age", StringType(), True),
StructField("name", StringType(), True)])
spark.read.schema(schema).json('/data.json').show()
RDD 创建 DF
见下面的格式互转
hive 创建 DF
有两种方式从 hive 创建 df
1. 使用 DataFrameReader 中定义的 table 方法
注意这种方式不只适用于 hive,也用于其他表
spark.read.table('hive1101.person').show()
2. 使用 HiveContext 或者 SparkSession 中的 sql 方法,直接运行 hql
DF 操作
sparkSQL 对 DF 的操作有两种风格,一种是类 sql 的方式,一种是 领域专属语言 DSL
SQL 风格操作 DF
df 并不是一张数据表,而 sql 风格需要一张表;
如果有 hive 环境,可以直接用 hive 中的表,
如果没有,需要把 df 当成一个临时表注册到应用上,而且只有注册到的应用正在运行,这个临时表才可以使用
注册方法不止一种,比如 createTempView、registerTempTable、
### 创建临时视图
df1.createTempView('student')
# df1.createOrReplaceTempView('student') # ok
spark.sql('select * from student').show()
# +---+--------+
# |age| name|
# +---+--------+
# | 10|zhangsan|
# | 20| lisi|
# +---+--------+ spark.sql('select age from student').show()
spark.sql('select avg(age) from student').show()
# +------------------------+
# |avg(CAST(age AS DOUBLE))|
# +------------------------+
# | 15.0|
# +------------------------+
关闭 SparkSession 后这张表无法使用
session
这里穿插讲下 session 的概念;
session 的本意是会话,我们在多个场合都见过 session,如 web,如 tensorflow,但是在 web 中貌似不是 会话啊;
其实是这样的,session 有广义和狭义之分
广义 session:就是我们说的会话
狭义 session:它是一个存储位置,和 cookie 相对,cookie 是把某个信息存在客户度,session 是把 某个信息存在服务器上
全局表
临时表是在 session 范围内的,session 关闭后,临时表失效,如果想应用范围内有效,可以使用全局表,
全局表需要全路径访问
### 为了在应用范围内使用数据表,创建全局表
df1.createGlobalTempView('people')
## 查询
spark.sql('select * from global_temp.people').show() # global_temp.people 全路径访问表 ## 在另一个 session 中查询该表
spark.newSession().sql('select * from global_temp.people').show()
DSL 风格操作 DF
df 知道每列的名字和数据类型,可以提供用于数据处理的领域专属语言DSL 【这种方式不常用】
df1.printSchema() # 打印表结构
# root
# |-- age: string (nullable = true)
# |-- name: string (nullable = true) df1.select('name').show() # 查询name字段
df1.select("name", df1.age + 1).show() # age 字段的值都 加1,scala 中是用 $'age' 代替 df.age
# +--------+---------+
# | name|(age + 1)|
# +--------+---------+
# |zhangsan| 11.0|
# | lisi| 21.0|
# +--------+---------+ df1.filter(df1.age > 15).show() # 查看 age 大于 15
# +---+----+
# |age|name|
# +---+----+
# | 20|lisi|
# +---+----+
RDD-DF-dataSet
rdd、dataFrame、dataSet 相当于 spark 中三种数据类型,简单总结几点:
1. rdd 是 df、ds 的底层实现
2. df 在 rdd 的基础上添加了结构,可以像数据表一个进行字段操作,易用,且高效
3. ds 在 df 的基础上添加了数据类型,并且可以像操作类一样进行属性操作,目前 python 不支持
4. 三者可互相转换
5. df、ds 是 sparkSQL 中的数据类型,准确的说叫数据抽象,在 sparkSQL 中他们被转换成 table,进行 sql 操作
6. 三者的计算逻辑并无差异,也就是说相同的数据,结果是相同的
7. 三者的计算效率和执行方式不同
8. 在未来 spark 演进过程中, ds 会逐步取代 df、rdd
发展历程
RDD(spark1.0) ===> DataFrame(spark1.3) ===> DataSet(spark1.6)
转换逻辑
rdd + 表结构 = df
rdd + 表结构 + 数据类型 = ds
df + 数据类型 = ds
ds - 数据类型 - 表结构 = rdd
ds - 数据类型 = df
df - 表结构 = rdd
转换方法

RDD to DF 之 toDF
dataFrame 类似于数据表,数据表有行的概念,df 也有 Row 的概念,也就是说 df 必须是有行有列,二维的概念;
如果 RDD 不是二维,或者说没有 Row 的概念,需要显示的构建 Row 的格式;
## 手动构建 Row 的概念
rdd1 = sc.parallelize(range(5))
df1_1 = rdd1.map(lambda x: Row(id = x)).toDF() # 先加入结构,即字段,或者说 key,然后调用 toDF
# >>> df1
# DataFrame[id: bigint]
RDD to DF 之 spark.createDataFrame
该方法有两个输入:一个由行构成的RDD,一个数据格式;
数据格式可以是一个 StructType 类实例;
一个 StructType 对象包含一个 StructField 对象序列;
一个StructField 对象用于指定一列的名字、数据类型,并可选择的指定这一列是否包含空值及其元数据
### 方法2
rdd1 = sc.parallelize([range(5)]) # 注意必须是 二维 的,sc.parallelize(range(5)) 是不行的
df2_1 = spark.createDataFrame(rdd1).collect() # 没有显示地添加字段,以 默认值为 字段名
# [Row(_1=0, _2=1, _3=2, _4=3, _5=4)] rdd2 = sc.parallelize([('a', 1), ('b', 2)]) # 二维数据
df2_2 = spark.createDataFrame(rdd2, ['label', 'num']).collect() # 显示地添加字段
# [Row(label=u'a', num=1), Row(label=u'b', num=2)] ### 方法3
rdd3 = sc.parallelize([('zhangsan', 20), ('lisi', 30)])
Person = Row('name', 'age') # 格式化 Row,每行代表一个 Person
person = rdd3.map(lambda x: Person(*x)) # 把 RDD 格式化成 新的 RDD,并加入 Row 的概念
df3_1 = spark.createDataFrame(person).show()
# +--------+---+
# | name|age|
# +--------+---+
# |zhangsan| 20|
# | lisi| 30|
# +--------+---+ ### 方法4
from pyspark.sql.types import StructType, StructField, StringType, IntegerType
schema = StructType([StructField("name", StringType(), True),
StructField("age", IntegerType(), True)])
df3 = spark.createDataFrame(rdd3, schema).collect()
# [Row(name=u'zhangsan', age=20), Row(name=u'lisi', age=30)]
toDF() vs createDataFrame()
1. 前者需要自己推断数据集的数据格式,因为并没有指定,后者则需要指定数据格式;
2. 前者易用;
3. 后者更加灵活,可以根据需要对同一数据设定多个数据格式,满足不同需求
DF to RDD
只需调用 rdd 属性即可
rdd = sc.parallelize([('a', 1), ('b', 2)]) # 二维数据
df = spark.createDataFrame(rdd2, ['label', 'num'])
df.rdd.collect()
# [Row(label=u'a', num=1), Row(label=u'b', num=2)]
参考资料:
https://spark.apache.org/docs/latest/api/python/pyspark.sql.html#pyspark.sql.SQLContext 官网 rdd to df
spark教程(11)-sparkSQL 数据抽象的更多相关文章
- spark教程(18)-sparkSQL 自定义函数
sparkSQL 也允许用户自定义函数,包括 UDF.UDAF,但没有 UDTF 官方 API class pyspark.sql.UDFRegistration(sparkSession)[sour ...
- spark教程(10)-sparkSQL
sparkSQL 的由来 我们知道最初的计算框架叫 mapreduce,他的缺点是计算速度慢,还有一个就是代码比较麻烦,所以有了 hive: hive 是把类 sql 的语句转换成 mapreduce ...
- spark教程(19)-sparkSQL 性能优化之谓词下推
在 sql 语言中,where 表示的是过滤,这部分语句被 sql 层解析后,在数据库内部以谓词的形式出现: 在 sparkSQL 中,如果出现 where,它会现在数据库层面进行过滤,一般数据库会有 ...
- Spark教程——(11)Spark程序local模式执行、cluster模式执行以及Oozie/Hue执行的设置方式
本地执行Spark SQL程序: package com.fc //import common.util.{phoenixConnectMode, timeUtil} import org.apach ...
- node-webkit教程(11)Platform Service之shell
node-webkit教程(11)Platform Service之shell 文/玄魂 目录 node-webkit教程(10)Platform Service之shell 前言 11.1 She ...
- 【译】ASP.NET MVC 5 教程 - 11:Details 和 Delete 方法详解
原文:[译]ASP.NET MVC 5 教程 - 11:Details 和 Delete 方法详解 在教程的这一部分,我们将研究一下自动生成的 Details 和Delete 方法. Details ...
- spark教程
某大神总结的spark教程, 地址 http://litaotao.github.io/introduction-to-spark?s=inner
- Expression Blend实例中文教程(11) - 视觉管理器快速入门Visual State Manager(VSM)
Expression Blend实例中文教程(11) - 视觉管理器快速入门Visual State Manager(V 时间:2010-04-12 16:06来源:SilverlightChina. ...
- [转帖]Linux教程(11)- linux中的计划作业
Linux教程(11)- linux中的计划作业 2018-08-21 17:13:36 钱婷婷 阅读数 160更多 分类专栏: Linux教程与操作 Linux教程与使用 版权声明:本文为博主原 ...
随机推荐
- js获取键盘编码
原理:键盘上的按键都有各自的键码,通过这个键码可以来判断按下的是哪个键,下面函数可以获取键盘的键码,按下键盘按键就会在控制台打印出相应的键码 document.addEventListener(&qu ...
- Vue_(基础)Vue中的事件
Vue.js中文文档 传送门 Vue@事件绑定 v-show:通过切换元素的display CSS属性实现显示隐藏: v-if:根据表达式的真假实现显示隐藏,如果隐藏,它绑定的元素都会销毁,显示的时候 ...
- Java基础__Integer类型中的自动装箱
Integer类型的自动装箱:就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱. ...
- hadoop-httpfs
Hadoop-httpfs: client向httpfs提交文件操作,由httpfs和集群交互: 优势:client不必访问集群 WebHDFS API: https://archive.cloude ...
- HNOI2012排队
排列组合题(本文A(n,m)表示从n个元素里选m个的排列数). 首先,老师和女生有不能相邻的限制条件,应该用插空法.而且老师人数较少且固定,把老师和男生进行混合,对女生用插空. 我先来一手错误做法,n ...
- CentOS 安装 Mongodb详解 --- 有Linux基础
安装包:https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-3.4.1.tgz 安装过程 安装pstree小工具,以及其使用 关闭 ...
- SVN服务器的安装
运行VisualSVN-Server-2.7.3.msi程序, 如下图 点击Next, 下一步 选中 I accept选项, 点击Next, 下一步 选择默认配置, 服务和控制台组件方式, 点击Nex ...
- 按二次back键退出程序应用的实现
package com.loaderman.twoexitdemo; import android.os.Bundle; import android.os.Handler; import andro ...
- 从GoogleClusterData统计每个用户的使用率、平均每次出价
之前将google cluster data导入了Azure上的MySQL数据库,下一步就是对这些数据进行分析, 挖掘用户的使用规律了. 首先,为了加快执行速度,对user,time等加入索引. 然后 ...
- Centos7 Devstack [Rocky] 重启后无法联网
部署devstack-rocky版本后网络,可以 Ping 通自己的 IP,但 Ping 不同网关,ping不通同网段主机,查看网卡和ovs信息如下 解决 第一步 按造网上教程,修改br-ex,ens ...