Spark之Spark SQL、DataFrame和Dataset

原文鏈接:http://spark.apache.org/docs/2.2.0/sql-programming-guide.html

目錄

概述

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|
// +-------------+

翻譯水平有限,翻譯不當之處還請讀者指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章