sparkSQL是Spark的一个模块,用于处理海量结构化数据
限定:结构化数据处理
学习SparkSQL主要有2点:
1.SparkSQL本身十分优秀,支持SQL语言、性能强、可以自动优化、API简单、兼容HIVE等
2.企业大面积使用SparkSQL处理业务数据:离线开发、数仓搭建、科学计算、数据分析
特点
融合性:SQL可以无缝继承在代码中,随时用SQL处理数据
统一数据访问:一套标准API可读写不用数据源
Hive兼容:可以使用SparkSQL直接计算并生成Hive数据表
标准化连接:支持标准化JDBC、ODBC连接,方便和各种数据库进行数据交互
SparkSQL和Hive的异同
同:都运行在YARN上,都是分布式SQL计算引擎
异:
Spark:内存计算,无元数据管理、SQL\代码混合执行、底层运行SparkRDD
Hive:磁盘迭代、Metastore、仅能以SQL开发、底层运行MapReduce
SparkSQL的数据抽象
pandas - DataFrame:二维表结构、本地集合
SparkCore - RDD:无标准数据结构,分布式集合
SparkSQL - DataFrame:二维表结构、分布式集合
SparkSession对象
在RDD阶段,程序的执行入口对象是SparkContext
在Spark2.0后推出了SparkSession对象,作为Spark编码的统一入口对象
SparkSession对象可以:用于SparkSQL编程作为入口对象;用于SparkCore编程,可以通过SparkSession获取到SparkContext
SparkSQL测试
from pyspark.sql import SparkSession
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
# appName设置程序名称,config设置一些常用属性
# 最后通过getOrCreate()方法 创建SparkSession对象
# 通过SparkSession对象,回去SparkContext对象
sc = spark.sparkContext
# sparkSQL的Helloword
df = spark.read.csv('stu_score.txt',sep=',',header=False)
df2 = df.toDF("id","name","score")
df2.printSchema()
df2.show()
df2.createTempView("score")
# SQL 风格
spark.sql("""
SELECT * FROM score WHERE name='语文' LIMIT 5
""").show()
# DSL风格
df2.where("name='语文'").limit(5).show()
DataFrame是一个二维表结构(行、列、表结构描述)
在结构层面上:
StructType对象描述整个DataFrame的表结构
StructField对象描述一个列的信息
在数据层面:
Row对象记录一行数据
Column对象记录一列数据并包含列的信息
基于RDD的方式1
DataFrame可以从RDD转换而来,都是分布式数据集,其实就是内部存储的结构,转换为二维表结构
from pyspark.sql import SparkSession
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 基于RDD转换成DataFrame
# 数据示例:Alen,18
rdd = sc.textFile("people.txt").map(lambda x: x.split(',')).\
map(lambda x: (x[0], int(x[1])))
# 构建DataFrame对象
# 参数1 被转换的RDD
# 参数2 指定列名,通过list的i形式指定,按照规定依次提供字符串名称
df = spark.createDataFrame(rdd, schema=["name","age"])
# 打印DataFrame的表结构
df.printSchema()
# 打印df中的数据
# 参数1 表示展示数据条数,默认20
# 参数2 表示是否队列进行截断,如果列的数据长度超过20个字符长度,后续的内容以...代替
# 如果给False 表示不截断, 默认True
df.show(20, False)
# 将DF对象转换成临时是图标,可供sql语句查询
df.createOrReplaceTempView("people")
spark.sql("SELECT * FROM people WHERE age < 30").show()
基于RDD的方式2
通过StructType对象来定义DataFrame的表结构转换RDD
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 基于RDD转换成DataFrame
# 数据示例:Alen,18
rdd = sc.textFile("people.txt").map(lambda x: x.split(',')).\
map(lambda x: (x[0], int(x[1])))
# 构建表结构的描述对象:StructType对象
schema = StructType().add("name", StringType(), nullable=True).add("age", IntegerType(), nullable=False)
# 基于StructType对象取构建RDD到DF的转换
df = spark.createDataFrame(rdd, schema=schema)
df.printSchema()
df.show()
基于RDD的方式3
使用RDD的toDF方法转换RDD
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 基于RDD转换成DataFrame
# 数据示例:Alen,18
rdd = sc.textFile("people.txt").map(lambda x: x.split(',')).\
map(lambda x: (x[0], int(x[1])))
# toDF的方式构建DataFrame
df1 = rdd.toDF(["name","age"])
df1.printSchema()
df1.show()
# toDF的方式2 通过StructType构建
schema = StructType().add("name", StringType(), nullable=True).add("age", IntegerType(), nullable=False)
df2 = rdd.toDF(schema=schema)
df2.printSchema()
df2.show()
基于Pandas的DataFrame
将Pandas的DataFrame对象,转变为分布式的SparkSQL DataFrame对象
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 基于Pandas的DataFrame构建SparkSQL的DataFrame对象
pdf = pd.DataFrame({
{
"id": [1, 2, 3],
"name": ["张大仙", "王晓晓", "吕不为"],
"age": [11, 22, 13]
}
})
df = spark.createDataFrame(pdf)
df.printSchema()
df.show()
读取外部数据
通过SparkSQL的统一API进行数据读取构建DataFrame
统一API示例代码:
sparksession.read.format("text|csv|json|parquet|orc|avro|jdbc|...")
.option("K", "V") # option可选
.schema(StructType|String) # STRING的语法如,schema("name STRING", "age INT")
.load("被读取文件的路径,支持本地文件系统的HDFS")
读取txt数据源
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 构建StructType, text数据源,读取数据的特点是,将一整行只作为”一个列“读取,默认列名是value 类型是string
schema = StructType().add("data", StringType(), nullable=True)
df = spark.read.format("text").schema(schema=schema).load("people.txt")
df.printSchema()
df.show()
读取json数据源
使用format(“json”)读取json数据
示例代码:
df = spark.read.format("json").load("people.json")
# JSON类型一般不用写schema,json自带,json带有列名和列类型(字符串和数字)
df.printSchema()
df.show()
读取csv数据源
使用format(“csv”)读取csv数据
示例代码:
df = spark.read.format("csv") \
.option("sep",";") \
.option("header",False) \
.option("encoding","utf-8") \
.schema("name STRING, age INT, job STRING") \
.load("people.csv")
读取parquet数据源
使用format(“parquet”)读取parquet数据
parquet:是spark中常见的一种列式存储文件格式
和Hive中的ORC差不多,都是列存储格式
parquet对比普通的文本文件的区别:
parquet内置schema(列名\列类型\是否为空)
存储是以列作为存储格式
存储是序列化存储在文件中的
# parquet 自带schema,直接load啥也不需要
df = spark.read.format("parquet").load("users.parquet")
DataFrame支持两种风格进行编码分别是DSL和SQL
DSL风格
DSL称为领域特定语言
指定DataFrame的特有API
DSL风格意思是以API调用的方式来处理数据
比如:df.where().limit()
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
df = spark.read.format("csv").schema("id INT, subject STRING, score INT").load("stu_score.txt")
# Column对象的获取
id_column = df['id']
subject_column = df['subject']
# DLS风格
df.select(["id","subject"]).show()
df.select("id","subject").show()
df.select(id_column,subject_column).show()
# filter API
df.filter("score < 99").show()
df.filter(df["score"] < 99).show()
# where API
df.where("score < 99").show()
df.where(df["score"] < 99).show()
# groupBy
df.groupBy("subject").count().show()
df.groupBy(df["subject"]).count().show()
# df.groupBy API的返回值GroupeedData(一个有分组关系的数据结构,使用一些API对分组做聚合)
r = df.groupBy("subject")
print(type(r))
# 使用聚合方法后返回的才是DataFrame
r.sum().show()
SQL语法风格
SQL风格就是使用SQL语句处理DataFrame的数据
比如:spark.sql(“SELECT * FROM XXX”)
如果想要使用SQL风格的语法,需要将DataFrame注册成表:
df.createTempView("score") # 注册一个临时视图
df.createOrReplaceTempView("score") # 注册一个临时表,如果存在进行替换
df.createGlobalTempView("score") # 注册一个全局表
全局表:跨SparkSession对象使用,在一个程序内的多个SparkSession中均可调用,查询带上前缀:global_temp.
临时表:只在当前SparkSession中可用
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
df = spark.read.format("csv").schema("id INT, subject STRING, score INT").load("stu_score.txt")
# 注册临时表
df.createTempView("score")
df.createOrReplaceTempView("score_2")
df.createGlobalTempView("score_3")
# 可以通过SparkSession对象的sql api来完成sql语句的执行
spark.sql("SELECT subject, COUNT(*) AS cnt FROM score GROUP BY subject").show()
spark.sql("SELECT subject, COUNT(*) AS cnt FROM score_2 GROUP BY subject").show()
spark.sql("SELECT subject, COUNT(*) AS cnt FROM global_temp.score_3 GROUP BY subject").show()
pyspark.sql.functions包
这个包里提供了一系列的计算函数供SparkSQL使用
导包
from pyspark.sql import functions as F
单词计数需求,使用DSL和SQL两种风格实现
在实现的过程中,会出现新的API,边写边学
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
from pyspark.sql import functions as F
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# TODO 1: SQL风格进行处理
rdd = sc.textFile("words.txt").flatMap(lambda x: x.split(" ")).map(lambda x: [x])
df = rdd.toDF(["word"])
# 注册DF为表
df.createTempView("words")
spark.sql("SELECT word, COUNT(*) AS cnt FROM words GROUP BY word ORDER BY cnt DESC").show()
# TODO 2:DSL 风格处理
df = spark.read.format("text").load("words.txt")
# withColumn方法
# 方法功能:对已存在的列进行操作,返回一个新的列,如果名字和老列相同,那么替换,否则作为新列存在
df2 = df.withColumn("value", F.explode(F.split(df['value'], " ")))
df2.groupBy("value","word")\
.count()\
.withColumnRenamed("count","cnt")\
.orderBy("cnt", ascending=False)\
.show()
MovieLens数据集,包含多个用户对多部电影的评级数据,也包括电影元数据信息和用户属性信息
需求:
1.查询用户平均分
2.查询电影平均分
3.查询大于平均分的电影数量
4.查询高分电影中(>3)打分次数最多的用户,并求出此人打的平均分
5.查询每个用户的平均打分,最低打分,最高打分
6.查询被评分超过100次的电影的平均分排名TOP10
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
from pyspark.sql import functions as F
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 1.读取数据集
schema = StructType().add("user_id", StringType(), nullable=True)\
.add("movie_id", IntegerType(), nullable=True)\
.add("rank", IntegerType(), nullable=True)\
.add("ts", StringType(), nullable=True)
df = spark.read.format("csv")\
.option("sep","\t")\
.option("header", False)\
.option("encoding", "utf-8")\
.schema(schema=schema)\
.load("u.data")
# TODO 1:用户评分
df.groupBy("user_id").avg("rank")\
.withColumnRenamed("avg(rank)","avg_rank")\
.withColumn("avg_rank", F.round("avg_rank", 2))\
.orderBy("avg_rank", ascending=False).show()
# TODO 2: 电影平均分查询
df.createTempView("movie")
spark.sql("""
SELECT movie_id, ROUND(AVG(rank), 2) AS avg_rank FROM movie GROUP BY avg_rank DESC
""").show()
# TODO 3: 查询大于平均分的电影数量
print("大于平均分电影的数量:", df.where(df['rank'] > df.select(F.avg(df['rank'])).first()['avg(rank)']).count())
# TODO 4: 查询高分电影中(>3)打分次数最多的用户,此人打分的平均分
# 先找人
user_id = df.where("rank > 3") \
.groupBy("user_id") \
.count() \
.withColumnRenamed("count","cnt") \
.orderBy("cnt", ascending=False) \
.limit(1).first()["user_id"]
# 计算这个人的打分平均分
df.filter(df['user_id'] == user_id).select(F.round(F.avg("rank"), 2)).show()
# TODO 5:查询每个用户的平均打分,最低打分,最高打分
# agg里可以写多个聚合
df.groupBy("user_id") \
.agg(
F.round(F.avg("rank"), 2).alias("avg_rank"),
F.min("rank").alias("min_rank"),
F.max("rank").alias("max_rank")
).show()
# TODO 6:查询平均分超过100次的电影的平均分及排名TOP10
df.groupBy("movie_id") \
.agg(
F.count("movie_id").alias("cnt"),
F.round(F.avg("rank"), 2).alias("avg_rank")
).where("cnt > 100").orderBy("avg_rank",ascending=False).limit(10).show()
新的API
在SparkSQL中当Job中产生Shuffle时,默认的分区数(spark.sql.shuffle.partitions)为200,在实际项目中要合理设置
设置:
1. 配置文件:conf/spark-defaults.conf: spark.sql.shuffle.partitions 100
2. 在客户端提交参数:bin/spark-submit --conf "spark.sql.shuffle.partitions=100"
3. 在代码中可以设置:spark = SparkSession.builder.\
appName("create df").\
master("local[*]").\
config("spark.sql.shuffle.partitions", "2").\
getOrCreate()
去重方法dropDuplicates
功能:对DF的数据进行去重,如果重复数据有多条,取第一条
# 去重API dropDuplicates,无参数时对数据进行整体去重
df.dropDuplicates().show()
# API 同样可以针对字段进行去重,如下传入age字段,表示只要年龄一样,就认为是重复数据
df.dropDuplicates(['age','job'].show()
缺失值处理
# dropna API可以对缺失值的数据进行删除
# 无参数使用,只要列中有null就删除这一行
df.dropna().show()
# thresh = 3 表示最少满足3个有效列,不满足就删除当前行
df.dropna(thresh=3).show()
df.dropna(thresh=2,subset=['name','age']).show()
# 也可以填充缺失值
# DataFrame的fillna 对缺失的列进行填充
df.fillna("loss").show()
# 指定列填充
df.fillna("N/A", subset=['job']).show()
# 设定一个字典,对所有的列提供填充规则
df.fillna({"name":"未知姓名", "age": 1, "jon":"worker"}).show()
SparkSQL统一API写出DataFrame数据
统一API语法
df.write.mode().format().option(K, V).save(PATH)
# mode 传入模式,可选:append 追加,overwrite 覆盖,ignore 忽略,error 重复就报错(默认)
# format 传入格式字符串,可选:text, csv, json, parquet, orc, avro, jdbc
# 注意text源只支持单列df写出
# option 设置属性,如 .option("seq",",")
# save 写出的路径,支持本地和HDFS
读取JDBC是需要有驱动的,读取jdbc:mysql://
这个协议,需要mysql的驱动jar包给spark使用
# 写DF通过JDBC到数据库中
df.write.mode("overwrite").\
format("jdbc").\
option("url","jdbc:mysql://node1:3306/test?useSSL=false&useUnicode=true").\
option("dbtable","u_data").\
option("user","root").\
option("password","123456").\
save()
注意:
在SparkSQL中,目前仅支持UDF函数和UDAF函数,目前python仅支持UDF
定义方式
1.sparksession.udf.register()
注册的udf可以用于DSL和SQL,返回值用于DSL风格,传参内给的名字用于SQL风格
2.pyspark.sql.functions.udf
仅能用于DSL风格
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
from pyspark.sql import functions as F
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 创建一个RDD
rdd = sc.parallelize([1,2,3,4,5,6,7]).map(lambda x: [x])
df = rdd.toDF(["num"])
# TODO 1: 方式1 sparksession.udf.register(), DSL和SQL风格均可以使用
# UDF的处理函数
def num_ride_10(num):
return num * 10
# 参数1:注册UDF的名称,仅可用于SQL风格
# 参数2:UDF的处理逻辑,是一个单独的方法
# 参数3:声明UDF的返回值类型,注意:UDF注册时必须声明返回值类型,并且UDF的真实返回值一定要和声明的返回值一致
# 返回值对象:这是一个UDF对象,仅可以用于DSL语法
# 当前这种方式定义的UDF,可以通过参数1的名称用于SQL风格,通过返回值对象用户DSL风格
udf2 = spark.udf.register("udf1", num_ride_10, IntegerType())
# SQL风格使用
# selectExpr 以SELECT的表达式执行,表达式SQl风格的表达式
# select方法,接受普通字符串字段名,或返回Column对象的计算
df.selectExpr("udf1(num)").show()
# DSL风格中使用
# 返回值UDF对象 如果作为方法使用,传入的参数一定是Column对象
df.select(udf2(df['num'])).show()
# TODO 2: 方式2注册,仅能用于DSL风格
udf3 = F.udf(num_ride_10, IntegerType())
df.select(udf3(df['num'])).show()
注:返回值也可以是复杂数据结构
UDAF
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
from pyspark.sql import functions as F
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
# 创建一个RDD
rdd = sc.parallelize([1,2,3,4,5,6,7],3).map(lambda x: [x])
df = rdd.toDF(["num"])
# 这种的方式,使用RDD的mapPartitions算子来完成聚合操作
# 如果用mapPartitions API 完成UDAF聚合,一定要单分区
single_partition_rdd = df.rdd.repartition(1)
def process(iter):
sum = 0
for row in iter:
sum += row['num']
return [sum] # 一定要list
print(single_partition_rdd.mapPartitions(process).collect())
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StringType, IntegerType
from pyspark.sql import functions as F
if __name__ == '__main__':
# 构建SparkSession对象,这个对象是构建器模式 通过builder方法来构建
spark = SparkSession.builder.appName("test").master("local[*]").\
config("spark.sql.shuffle.partitions",'4').getOrCreate()
sc = spark.sparkContext
rdd = sc.parallelize([
('张三', 'class_1',99),
('王五', 'class_2', 35),
('李四', 'class_3', 17),
('赵六', 'class_1', 66),
('王丽', 'class_4', 33),
('王军', 'class_1', 22)
])
schema = StructType().add("name", StringType()).add("class",StringType()).add("score", IntegerType())
df = rdd.toDF(schema)
df.createTempView("stu")
# TODO 聚合窗口函数的演示
spark.sql("""
SELECT *, AVG(score) OVER() AS avg_score FROM stu
""").show()
# TODO 排序相关的窗口函数计算
# RANK over, DENSE_RANK over, ROW_NUMBER over
spark.sql("""
SELECT *, ROW_NUMBER() OVER(ORDER BY score DESC) AS row_number_rank,
DENSE_RANK() OVER(PARTITION BY class ORDER BY score DESC) AS dense_rank,
RANK() OVER(ORDER BY score) AS rank
FROM stu
""").show()
# TODO NTILE
# NTILE 均分6份
spark.sql("""
SELECT *, NTILE(6) OVER(ORDER BY score DESC) FROM stu
""").show()
RDD的而运行会完全按照开发者的代码执行,如果开发者水平有限,RDD的执行效率会受到影响,而SparkSQL会对写完的代码,执行“自动优化”,以提升代码运行效率,避免开发者水平影响到代码执行效率
DataFrame是二维表结构,可以针对优化
SparkSQL自动优化依赖于Catalyst优化器
为了解决过多依赖Hive的问题,SparkSQL使用了一个新的SQL优化器代替Hive中的优化器,这个优化器就是Catalyst(和sql优化类似)
catalyst的优点:
1.谓词下推、断言下推:将逻辑判断提前到前面,以减少shuffle阶段的数据量
2.列值裁剪:将加载的列进行裁剪,尽量减少被处理数据的宽度
原理
原理就是spark能够连接上Hive的MetaStore
条件:
1.MetaStoer需要存在并开机
2.Spark知道MetaStore在哪里(IP 端口)
配置
在spark的conf目录中,创建hive-site.xml
hive.metastore.warehouse.dir
/usr/hive/warehouse
hive.metastore.local
false
hive.metastore.uris
thrift://node1:9083
在代码中集成Spark On Hive
前提:确保MetaStore服务是启动好的
spark = SparkSession.builder\
.appName("create df")\
.master("local[*]")\
.config("spark.sql.shuffle.paratitions","4")\
.config("spark.warehouse.dir","hdfs://node1:8020/user/hive/warehouse")\
.config("hive.metastore.uris","thrift://node1:9083")\
.enableHiveSupport().getOrCreate()
spark.sql("""SELECT * FROM test.student""").show()
Spark中有一个服务叫做:ThriftServer服务,可以启动并监听10000端口
这个服务对外提供功能,可以用数据库工具或代码连接上,直接写sql操作spark
1.确保已经配置好Spark On Hive
2.启动ThriftServer
$SPARK_HOME/sbin/start-thriftserver.sh \
--hiveconf hive.server2.thrift.port=10000 \
--hiveconf hive.server2.thrift.bind.host=node1 \
--master local[2]
# master选择local,每条sql都是local进程执行
# master选择yarn,每条sql都在YARN集群中执行