Spark SQL,DataFrame,Dataset
Spark SQL是一个结构化数据处理模块。不像Spark RDD API,Spark SQL提供的接口使Spark更加熟悉数据和执行的计算的结构。在内部,Spark SQL使用额外的信息来执行额外的优化。可以通过SQL和Dataset API来和Spark SQL交互。不管使用什么API/语言来表达计算,都是用的一个计算引擎。这种统一给了开发者很大的自由度来在不同的API之间选择最容易表达计算转换方式的方式。
本页中所有的采样数据使用的都是采样数据,可以在spark-shell,pyspark,sparKR中运行。
SQL
Spark SQL用处之一就是执行SQL查询,还可以从HIve中读取数据。如果想直到如何配置这些功能请查阅Hive Tables部分。当使用另一种编程语言来运行SQL时返回结果时Dataset/DataFrame。还可以采用命令行甚至JDBC/ODBC来和SQL接口交互。
Datasets和DataFrame
Dataset是分布式数据集合,从Spark1.6开始支持,它拥有RDD的优势(强类型,使用lambda函数)以及Spark SQL优化过的执行引擎。Dataset可以来源于JVM对象,可以采用转换函数操作(map.flatMap,filter等)。Scala和Python支持Dataset,而Python不支持。但由于Python动态的特性很多优势已经有了(可以通过row.columnName获取某行的某列值)。R也类似。
DataFrame就是有命名列的Dataset。概念上它等价于关系型数据库中的表或Python/R中的data frame,而其中包含了更多的优化。DataFrame的构建来源很广:结构化数据文件,Hive表,外部数据库或者RDD。四种语言都支持DataFrame。Scala和Java中DataFrame由Row构成的Dataset。Scala中DataFrame就是Dataset[Row],Java中则是Dataset。
这篇文档中就把Row组成的Dataset当作DataFrame。
Start
出发吧:SparkSession
Spark中所有功能的入口是SparkSession类。要想创建基本的SparkSession可以直接使用SparkSession.builder:
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("Python Spark SQL basic example").config("spark.some.config.option","some-value").getOrCreate()
所有的样例代码位于Sprak repo中的examples/src/main/python/sql/basic.py。Spark2.0的SparkSession内置支持使用HiveQL写查询,使用Hive UDF以及从Hive表中读取数据的功能。有了这些特性你就不必非得启动Hive了。
创建DataFrame
有了SparkSession,应用就可以由现存的RDD,Hive表或Spark数据源中创建DataFrame。
下面的例子是基于JSON文件创建DataFrame:
# spark is an existing SparkSession
df = spark.read.json("examples/src/main/resources/people.json")
# Displays the content of the DataFrame to stdout
df.show()
# +----+-------+
# | age| name|
# +----+-------+
# |null|Michael|
# | 30| Andy|
# | 19| Justin|
# +----+-------+
全部代码在Spark repo中的examples/src/main/python/sql/basic.py。
非泛型 Dataset操作(即DataFrame操作)
DataFrame实际是为Scala,Java,Python,R提供了处理结构化数据的领域内的语言。
如上所述,Spark2.0中,Java和Scala的DataFrame就是Row组成的Dataset。Scala/Java Dataset属于强泛型,而这种DataFrame就是非泛型操作了。
这里展示一些使用Dataset完成的结构化数据处理样例。
Python中可以通过属性或索引来获取DataFrame的列。尽管前者很方便,但是仍建议使用后者
# spark, df are from the previous example
# Print the schema in a tree format
df.printSchema()
# root
# |-- age: long (nullable = true)
# |-- name: string (nullable = true)
# Select only the "name" column
df.select("name").show()
# +-------+
# | name|
# +-------+
# |Michael|
# | Andy|
# | Justin|
# +-------+
# Select everybody, but increment the age by 1
df.select(df['name'], df['age'] + 1).show()
# +-------+---------+
# | name|(age + 1)|
# +-------+---------+
# |Michael| null|
# | Andy| 31|
# | Justin| 20|
# +-------+---------+
# Select people older than 21
df.filter(df['age'] > 21).show()
# +---+----+
# |age|name|
# +---+----+
# | 30|Andy|
# +---+----+
# Count people by age
df.groupBy("age").count().show()
# +----+-----+
# | age|count|
# +----+-----+
# | 19| 1|
# |null| 1|
# | 30| 1|
# +----+-----+
DataFrame全部操作可见于API Document。
除了上面的列引用和表达式,DataFrame有丰富的库函数用于处理字符串操作,日期计算,常用的数学运算等。完整列表见于DataFrame Function Reference。
SQL查询
SparkSession的sql函数使应用能够运行SQL查询并返回DataFrame。
# Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")
sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
# +----+-------+
# | age| name|
# +----+-------+
# |null|Michael|
# | 30| Andy|
# | 19| Justin|
# +----+-------+
全局临时视图
Spark SQL的临时视图属于会话期间有效,一旦会话结束则消失地无影无踪。如果想要创建一个会话间共享甚至Spark应用结束了还能访问的临时视图那就创建个全局临时视图吧。全局临时视图关联于系统保存的数据库global_temp,所以需要使用该名来索引:SELECT * FROM global_temp.view1
# Register the DataFrame as a global temporary view
df.createGlobalTempView("people")
# Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show()
# +----+-------+
# | age| name|
# +----+-------+
# |null|Michael|
# | 30| Andy|
# | 19| Justin|
# +----+-------+
# Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show()
# +----+-------+
# | age| name|
# +----+-------+
# |null|Michael|
# | 30| Andy|
# | 19| Justin|
# +----+-------+
创建Dataset
Dataset类似于RDD,但不是使用Java序列化或Kryo,Dataset使用特殊的编码器来序列化对用从而用于处理或网络间传输。尽管这种编码器和标准序列化都会将对象转换为字节,但编码器动态编码并且允许Spark在没有将字节反序列化回对象的时候就执行很多像filtering,sorting,hashing这样的操作。
import java.util.Arrays;
import java.util.Collections;
import java.io.Serializable;
import org.apache.spark.api.java.function.MapFunction;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.Encoder;
import org.apache.spark.sql.Encoders;
public static class Person implements Serializable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// Create an instance of a Bean class
Person person = new Person();
person.setName("Andy");
person.setAge(32);
// Encoders are created for Java beans
Encoder<Person> personEncoder = Encoders.bean(Person.class);
Dataset<Person> javaBeanDS = spark.createDataset(
Collections.singletonList(person),
personEncoder
);
javaBeanDS.show();
// +---+----+
// |age|name|
// +---+----+
// | 32|Andy|
// +---+----+
// Encoders for most common types are provided in class Encoders
Encoder<Integer> integerEncoder = Encoders.INT();
Dataset<Integer> primitiveDS = spark.createDataset(Arrays.asList(1, 2, 3), integerEncoder);
Dataset<Integer> transformedDS = primitiveDS.map(
(MapFunction<Integer, Integer>) value -> value + 1,
integerEncoder);
transformedDS.collect(); // Returns [2, 3, 4]
// DataFrames can be converted to a Dataset by providing a class. Mapping based on name
String path = "examples/src/main/resources/people.json";
Dataset<Person> peopleDS = spark.read().json(path).as(personEncoder);
peopleDS.show();
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
与RDD交互
Spark SQL支持两种不同的方法将现存的RDD转换为Dataset。其一是使用反射机制来推理RDD中具体对象类型的schema。如果编写Spark应用代码时已经知道了schema那这种反射机制就很不错。
其二是通过接口创建schema应用到RDD上来创建Dataset。尽管比第一种冗长,但可以直到运行时才知道列和类型进而构建Dataset。
使用反射机制进行推理schema
Spark SQL会将Row对象组成的RDD转换为DataFrame,并自动推理数据类型。Row对象就是由Row类构造,其参数是一系列键值对。一系列键代表表中的列名,泛型由对整个数据集采样而推理得来,这很像JSON文件上的推理过程。
from pyspark.sql import Row
sc = spark.sparkContext
# Load a text file and convert each line to a Row.
lines = sc.textFile("examples/src/main/resources/people.txt")
parts = lines.map(lambda l: l.split(","))
people = parts.map(lambda p: Row(name=p[0], age=int(p[1])))