目录
概述
Spark SQL是Spark处理结构化数据的模块。不同于基础的Spark RDD的API,Spark SQL提供的接口为Spark提供了更多关于数据结构和正在执行计算的信息。在内部,Spark SQL利用这些额外的信息做额外的优化。和Spark SQL交互的方式有很多种,包括SQL和Dataset的API。将使用同样的执行引擎去计算结果,与你使用哪种API或者语言无关。这种统一意味着开发者能够很容易的在不同的API来回切换,从而提供更自然的方式去表达一个给定的变换。
此页面上的所有示例均使用Spark发行版中包含的示例数据,并且可以在spark-shell,pyspark shell或sparkR shell中运行。
Spark SQL
Spark SQL的一种用途就是执行SQL查询。Spark SQL能够读取Hive中的数据。有关更多配置这项功能的信息,请参考Hive Tables部分。另一种编程语言运行SQL的结果将会作为Dataset或者DataFrame的形式返回。 你还可以使用命令行或通过JDBC / ODBC与SQL接口进行交互。
Dataset and DataFrame
Dataset是分布式数据集。Dataset是Spark1.6添加的新接口,它有RDD(强类型,使用lambda函数强大的能力)的优点以及Spark SQL优化执行引擎的优点。能够从JVM中构造Dataset,然后使用transformation函数(map,flatMap,filter等等)操作它。Dataset的API在java和scala中是可用的,python不支持Dataset的API。但是由于python的动态特性,Dataset的API的很多优点已经可用(你可以通过row.columnName来访问行的字段),R的情况类似。
DataFrame是一个组织成命名列的DataSet。它在概念上等同于关系数据库中的表或R/Python中的数据框。但是在底层进行了更丰富的优化。有多种方式构造DataFrame,比如结构化数据文件、hive中的表、外部的数据库、或者已有的RDD。DataFrame的API在scala、java、python和R中都是可用的。在scala和java中,DataFrame可以通过由Row组成的Dataset来表示。在scala的API中,DataFrame只是Dataset[Row]的类型别名,而在java的API中,用户需要使用Dataset<Row>来表示一个DataFrame。
入门
起点:SparkSession
Spark所有功能的入口SparkSession类。使用SparkSession.builder()创建最基本的SparkSession:
import org.apache.spark.sql.SparkSession
val spark = SparkSession
.builder()
.appName("Spark SQL basic example")
.config("spark.some.config.option", "some-value")
.getOrCreate()
// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._
在Spark仓库的"examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala"可以找到完整的实例代码。
SparkSession在Spark2.0为Hive功能提供了内置支持,包括使用HiveQL编写查询、访问Hive UDFS和从Hive表中读取数据。要使用这些功能,你不需要对现有的Hive设置。
创建DataFrame
应用程序可以从已有的RDD创建DataFrame,或者Hive表,以及从Spark的数据源。
作为一个例子,下面将基于内容为json的文件来创建DataFrame:
val 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|
// +----+-------+
people.json文件内容:
{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}
DataFrame的操作
DataFrame为Scala,Java,Python和R中的结构化数据的操作提供了一种领域相关的语言。
如上所述,在Spark2.0中,在java和scala的API里DataFrame只是由Row组成的DataSet。与强类型的scala/java DataSet的“类型转换”相反,这些操作称为“非类型转换”。
这里包括一些使用数据集进行结构化数据处理的基本示例:
// This import is needed to use the $-notation
import spark.implicits._
// 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($"name", $"age" + 1).show()
// +-------+---------+
// | name|(age + 1)|
// +-------+---------+
// |Michael| null|
// | Andy| 31|
// | Justin| 20|
// +-------+---------+
// Select people older than 21
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+
// Count people by age
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// | 19| 1|
// |null| 1|
// | 30| 1|
// +----+-----+
有关可对数据集执行的操作类型的完整列表,请参阅API文档
除了简单的列引用和表达式外,还具有丰富的函数库,包括字符串处理,日期算术,通用数学运算等,请参阅 DataFrame函数参考
编程方式运行SQL查询
SparkSession的sql函数能够使应用去执行SQL查询。并且返回一个DataFrame
// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
全局临时视图
Spark SQL的临时视图是会话域的,如果创建它的会话终止,临时视图将会消失。如果你想临时视图在所有的会话共享,并且一直存活直到应用终止,你可以创建一个全局的临时视图。全局视图保存在系统维护的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。虽然编码器和标准序列化都负责将对象转化成字节,编码器是动态生成代码并且使用一种允许Spark执行很多操作(如过滤、排序、哈希处理)的格式,无需将字节反序列化成对象。
// Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,
// you can use custom classes that implement the Product interface
case class Person(name: String, age: Long)
// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+
// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)
// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
与RDD的互操作
Spark SQL支持两种不同的方式去将已有的RDD转化成为DataSet。第一种方法使用反射来推断包含特定对象类型的RDD的Schema信息。 这种基于反射的方法可以使代码更简洁,当你在编写Spark应用程序时已经了解schema信息时,可以很好地工作。
创建DataSet的第二种方法是通过编程界面,该界面允许你去构造一个schema,然后将其应用到已有的RDD上。尽管此方法较冗长,但可以在运行时才知道列及其类型的情况下构造DataSet。
使用反射推断schema
Spark SQL的Scala接口支持将包含案例类的RDD自动转化成DataFrame。案例类定义了表的schema信息。 案例类的参数名称利用反射读取,并成为列的名称。案例类也能够嵌套或者包含复杂类型,例如:Seqs或者Arrays。RDD可以被隐式转化为DataFrame,然后注册成表,可以在后续的SQL语句中该表。
// For implicit conversions from RDDs to DataFrames
import spark.implicits._
// Create an RDD of Person objects from a text file, convert it to a Dataframe
val peopleDF = spark.sparkContext
.textFile("examples/src/main/resources/people.txt")
.map(_.split(","))
.map(attributes => Person(attributes(0), attributes(1).trim.toInt))
.toDF()
// Register the DataFrame as a temporary view
peopleDF.createOrReplaceTempView("people")
// SQL statements can be run by using the sql methods provided by Spark
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")
// The columns of a row in the result can be accessed by field index
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
// or by field name
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
// No pre-defined encoders for Dataset[Map[K,V]], define explicitly
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// Primitive types and case classes can be also defined as
// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()
// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name" -> "Justin", "age" -> 19))
编程方式指定schema
如果无法提前定义案例类,则可以通过3个步骤创建DataFrame:
1. 从原始的RDD创建基于行的RDD
2. 基于在第1步中创建的RDD,创建一个由StructType表示的schema,该schema与Rows的结构相匹配。
3. 通过SparkSession提供的createDataFrame方法将schema应用于行的RDD
例如:
import org.apache.spark.sql.types._
// Create an RDD
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")
// The schema is encoded in a string
val schemaString = "name age"
// Generate the schema based on the string of schema
val fields = schemaString.split(" ")
.map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)
// Convert records of the RDD (people) to Rows
val rowRDD = peopleRDD
.map(_.split(","))
.map(attributes => Row(attributes(0), attributes(1).trim))
// Apply the schema to the RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)
// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")
// SQL can be run over a temporary view created using DataFrames
val results = spark.sql("SELECT name FROM people")
// The results of SQL queries are DataFrames and support all the normal RDD operations
// The columns of a row in the result can be accessed by field index or by field name
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// | value|
// +-------------+
// |Name: Michael|
// | Name: Andy|
// | Name: Justin|
// +-------------+
翻译水平有限,翻译不当之处还请读者指正!