Spark之Spark SQL、DataFrame和Dataset

目录

概述

Spark SQL

Dataset and DataFrame

入门

起点:SparkSession

创建DataFrame

DataFrame的操作

编程方式运行SQL查询

全局临时视图

创建DataSet

与RDD的互操作

使用反射推断schema

编程方式指定schema


概述

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查询

SparkSessionsql函数能够使应用去执行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|
// +-------------+

翻译水平有限,翻译不当之处还请读者指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值