作者|Andrea Ialenti 编译|VK 来源|Towards Datas Science
正如在我几乎所有关于这个工具的文章中都写到,Spark和SQL一样非常容易使用。但不管我花多少时间写代码,我只是无法在我的大脑中永久性地存储Spark API(有人会说我的记忆就像RAM一样,小而易失)。
无论你是想快速入门介绍sparksql,还是急于编写你的程序,还是像我一样需要一份备忘单,我相信你会发现这篇文章很有用。
这篇文章的目的是介绍sparksql的所有主要函数/特性,在片段中,你将始终看到原始的SQL查询及其在PySpark中的翻译。
我将在这个数据集上执行我的代码:https://drive.google.com/file/d/1kCXnIeoPT6p9kS_ANJ0mmpxlfDwK1yio/view
在几个月前,我为另一篇文章创建了这个数据集,它由三个简单的表组成:
基础知识
Apache Spark是一个用于大规模并行数据处理的引擎。这个框架的一个令人惊奇的特性是它以多种语言公开api:我通常使用Scala与它交互,但是也可以使用SQL、Python甚至Java和R。
当我们编写Spark程序时,首先要知道的是,当我们执行代码时,我们不一定要对数据执行任何操作。实际上,该工具有两种类型的API调用:转换和操作。
Spark转换背后的范例被称为“延后计算”,这意味着实际的数据计算在我们要求采取行动之前不会开始。
为了理解这一概念,设想一下你需要对一个列执行SELECT和重命名的情况:如果不调用某个操作(例如collect或count),那么你的代码只不过是定义了所谓的Spark执行计划。
Spark以有向无环图(非常著名的DAG)组织执行计划。此结构描述将要执行的确切操作,并使调度器能够决定在给定时间执行哪个任务。
正如Miyagi先生告诉我们的:
上蜡:定义DAG(变换)
脱蜡:执行DAG(动作)
与Spark交互
太好了,我们从哪里开始交互?使用Spark有多种方法:
使用IDE:我建议使用IntelliJ或PyCharm,但我想你可以选择任何你想要的东西。查看附录中的PyCharm快速入门(在本地运行查询)。我认为可以从你的本地环境使用远程Spark executor,但说实话,我从来没有进行过这种配置。
Jupyter Notebooks+Sparkmagic:Sparkmagic是一组工具,用于通过Spark REST服务器Livy与远程Spark集群交互工作[1]。这是在AWS、Azure或googlecloud等云系统上工作时使用Spark的主要方式。大多数云提供商都有一项服务,可以在大约10分钟内配置集群和notebooks 。
通过使用spark shell的终端:有时你不希望在你和数据之间有任何东西(例如,对一个表进行超级快速的检查);在这种情况下,你只需打开一个终端并启动spark shell。
文章的代码主要用于IDE。
在编写任何查询之前,我们需要导入一些库并启动一个Spark会话(使用Dataset和DataFrame 的API编程)。下面的PySpark和Scala代码段将加载你需要的所有内容(假设你已经配置了系统)。之后,为了简单起见,我们将只看到PySpark代码。除了一些细微差别外,scalaapi基本相同。
PySpark
# 导入Spark
import pyspark
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
# 初始化Spark会话
spark = SparkSession.builder \
.master("local") \
.appName("SparkLikeABoss") \
.getOrCreate()
Scala
// 导入Spark
import org.apache.spark.sql._
import org.apache.spark.sql.functions._
// 初始化Spark会话
val spark = SparkSession.builder.
master("local")
.appName("spark session example")
.getOrCreate()
解释数据集、数据帧和RDD之间的差异篇幅将过长,所以我跳过这一部分,假装它不存在。
基本操作
你能写的最简单的查询可能是你所用过的最重要的查询。让我们看看如何使用Sales表进行基本操作。
简单的Select语句和显示数据
# 以Parquet格式读取源表
sales_table = spark.read.parquet("./data/sales_parquet")
'''
SELECT *
FROM sales_table
'''
# 执行计划
sales_table_execution_plan = sales_table.select(col("*"))
# Show (Action) - 显示5行,列宽不受限制
sales_table_execution_plan.show(5, True)
# 以Parquet格式读取源表
sales_table = spark.read.parquet("./data/sales_parquet")
'''
SELECT order_id AS the_order_id,
seller_id AS the_seller_id,
num_pieces_sold AS the_number_of_pieces_sold
FROM sales_table
'''
# 以一行代码执行计划和显示出来
sales_table_execution_plan = sales_table.select(
col("order_id").alias("the_order_id"),
col("seller_id").alias("the_seller_id"),
col("num_pieces_sold").alias("the_number_of_pieces_sold")
).show(5, True)
我们在代码片段中所做的第一件事是定义执行计划;只有当我们获得show操作时,才会执行该计划。
我们可以在Spark计划中调用的其他操作示例包括:
collect()—返回整个数据集
count()—返回行数
take(n)-从数据集中返回n行
show(n,truncate=False)-显示n行。你可以决定截断结果或显示字段的所有长度
另一个值得注意的有趣的事情是列是由col对象标识的。在本例中,我们让Spark推断这些列属于哪个数据帧。
我们可以使用语法execution_plan_variable[“column_name”]来指定列来自哪个执行计划。使用此替代语法,我们可以得到:
# 以Parquet格式读取源表
sales_table = spark.read.parquet("./data/sales_parquet")
'''
SELECT order_id AS the_order_id,
seller_id AS the_seller_id,
num_pieces_sold AS the_number_of_pieces_sold
FROM sales_table
'''
# 以一行代码执行计划和显示出来
sales_table_execution_plan = sales_table.select(
sales_table["order_id"].alias("the_order_id"),
sales_table["seller_id"].alias("the_seller_id"),
sales_table["num_pieces_sold"].alias("the_number_of_pieces_sold")
).show(5, True)
在处理连接时,限定字段的源表尤为重要(例如,两个表可能有两个同名字段,因此仅使用col对象不足以消除歧义)。Scala中的语法略有不同:
// Qualify the source execution plan in Scala
sales_table.col("order_id")
重命名和添加列
有时我们只想重命名一个列,或者我们想添加一个新的列并进行一些计算(例如,在以下情况下):
# 以Parquet格式读取源表
sales_table = spark.read.parquet("./data/sales_parquet")
''