一, 简介
Spark SQL是用于结构化数据处理的Spark模块。与基本的Spark RDD API不同,Spark SQL提供的接口为Spark提供了关于数据结构和正在执行的计算的更多信息。在内部,Spark SQL使用这些额外的信息来执行额外的优化。有几种与Spark SQL进行交互的方式,包括SQL和Dataset API。在计算结果时,使用相同的执行引擎,而不管使用哪种API /语言表示计算。这种统一意味着开发人员可以轻松地在不同的API之间来回切换,基于这些API提供了表达给定转换的最自然的方式。
Spark SQL的一个用途是执行SQL查询。Spark SQL也可以用来从现有的Hive安装中读取数据。从另一种编程语言中运行SQL时,结果将作为数据集/数据框返回。您还可以使用命令行 或通过JDBC / ODBC与SQL接口进行交互。
二, 数据集和数据框(Datasets and DataFrames)
数据集Datasets 是分布式数据集合。数据集Datasets 是Spark 1.6中添加的一个新接口,它提供了RDD(强类型,使用强大的lambda函数的功能)的好处以及Spark SQL优化执行引擎的优点。数据集可以被构造从JVM对象,然后使用功能性的转换操作(map,flatMap,filter等等)。数据集API可用于Scala和 Java。Python不支持数据集API。但是由于Python的动态特性,数据集API的许多优点已经可用(例如,您可以自然地通过名称访问行的字段 row.columnName)。R的情况类似。
一个DataFrame是一个数据集组织成命名列。它在概念上等同于关系数据库中的表格或R / Python中的数据框架,但具有更丰富的优化。DataFrame可以从各种各样的源构建,例如:结构化数据文件,Hive中的表,外部数据库或现有的RDD。数据帧API是Scala,Java的,可用的Python和[R 。在Scala和Java中,DataFrame由Rows 的数据集表示。在Scala API中,DataFrame只是一个类型的别名Dataset[Row]。而在Java API中,用户需要Dataset<Row>用来表示一个DataFrame。
1,创建Spark Sql的入口
Spark中所有功能的入口点就是这个SparkSession类。要创建一个基本的SparkSession,只需使用SparkSession.builder():
import org.apache.spark.sql.SparkSession val spark = SparkSession .builder() .appName("Spark SQL basic example") .config("spark.some.config.option", "some-value") .getOrCreate()
import spark.implicits._
SparkSession
在Spark 2.0中为Hive特性提供内置支持,包括使用HiveQL编写查询,访问Hive UDF以及从Hive表读取数据的能力。要使用这些功能,您不需要有现有的Hive安装程序。2,创建数据框DataFrame
使用a SparkSession,应用程序可以从现有的RDD,Hive表或Spark数据源创建DataFrame 举个例子,下面根据JSON文件的内容创建一个DataFrame:
其中people.json中的数据为:val df = spark.read.json("examples/src/main/resources/people.json") // 显示数据框中的数据 df.show() // +----+-------+ // | age| name| // +----+-------+ // |null|Michael| // | 30| Andy| // | 19| Justin| // +----+-------+
3,无类型数据集Dataset操作(又名DataFrame操作){"name":"Michael"} {"name":"Andy", "age":30} {"name":"Justin", "age":19}
DataFrames为Scala,Java,Python和R中的结构化数据操作提供了一个特定领域的语言。
如上所述,在Spark 2.0中,DataFrames只是Row,Scala和Java API中的数据集。这些操作也被称为“无类型转换”,与强类型的Scala / Java数据集中的“类型转换”不同。这里我们包含一些使用数据集的结构化数据处理的基本示例:
import spark.implicits._ // 以树形格式打印数据集的 Schema 格式 df.printSchema() // root // |-- age: long (nullable = true) // |-- name: string (nullable = true) // 选择 name 的列数据 df.select("name").show() // +-------+ // | name| // +-------+ // |Michael| // | Andy| // | Justin| // +-------+ // 选择所有的人,并且 age 加一, df.select($"name", $"age" + 1).show() // +-------+---------+ // | name|(age + 1)| // +-------+---------+ // |Michael| null| // | Andy| 31| // | Justin| 20| // +-------+---------+ // 选出年龄大于21的人信息 df.filter($"age" > 21).show() // +---+----+ // |age|name| // +---+----+ // | 30|Andy| // +---+----+ // 通过年龄的升序排序,并计数每个人的数量 df.groupBy("age").count().show() // +----+-----+ // | age|count| // +----+-----+ // | 19| 1| // |null| 1| // | 30| 1| // +----+-----+
有关可在数据集上执行的操作类型的完整列表,请参阅API文档。
除了简单的列引用和表达式之外,数据集还具有丰富的函数库,包括字符串操作,日期算术,通用数学运算等等。DataFrame函数参考中提供了完整的列表。
4,以编程方式运行SQL查询
通过以上的例子利用sql函数,SparkSession使应用程序能够以编程方式运行SQL查询,并将结果返回为DataFrame。
5,全局临时视图df.createOrReplaceTempView("people") // 创建临时视图 val 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。
6,创建数据集Dataset// 创建全局临时试图 df.createGlobalTempView("people") // 全局临时视图绑定到系统数据库中保存`global_temp` spark.sql("SELECT * FROM global_temp.people").show() // +----+-------+ // | age| name| // +----+-------+ // |null|Michael| // | 30| Andy| // | 19| Justin| // +----+-------+ // 全局临时视图是跨会话的 spark.newSession().sql("SELECT * FROM global_temp.people").show() // +----+-------+ // | age| name| // +----+-------+ // |null|Michael| // | 30| Andy| // | 19| Justin| // +----+-------+
数据集类似于RDD,但是,不使用Java序列化或Kryo,而是使用专门的编码器对对象进行序列化以便通过网络进行处理或传输。虽然编码器和标准序列化都负责将对象转换为字节,但编码器是动态生成的代码,并且使用允许Spark执行许多操作(如过滤,排序和散列)的格式,而无需将字节反序列化回对象。
7,与RDD进行互操作case class Person(name: String, age: Long) // 强转类型 import spark.implicits._ // 自定义类//创建case类的编码器 val caseClassDS = Seq(Person("Andy", 32)).toDS() caseClassDS.show() // +----+---+ // |name|age| // +----+---+ // |Andy| 32| // +----+---+ // 对于最常见的类型//编码器是通过导入spark.implicits._自动提供 val primitiveDS = Seq(1, 2, 3).toDS() primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4) //通过提供一个类可以将DataFrames转换为Dataset。映射将通过名称 val path = "epeople.json" val peopleDS = spark.read.json(path).as[Person] peopleDS.show() // +----+-------+ // | age| name| // +----+-------+ // |null|Michael| // | 30| Andy| // | 19| Justin| // +----+-------+
Spark SQL支持将现有RDD转换为Datasets的两种不同方法。第一种方法使用反射来推断包含特定类型对象的RDD的模式。这种基于反射的方法导致更简洁的代码,并且在编写Spark应用程序时已经知道模式的情况下工作良好。
创建数据集的第二种方法是通过编程接口,允许您构建模式,然后将其应用于现有的RDD。虽然这个方法比较冗长,但是它允许你在构造数据集的时候直到运行时才知道列和它们的类型。
8,以编程方式指定模式// 用于从RDD到DataFrames的隐式转换 import spark.implicits._ // 从文本文件创建Person对象的RDD,将其转换为Dataframe val peopleDF = spark.sparkContext .textFile("people.txt") .map(_.split(",")) .map(attributes => Person(attributes(0), attributes(1).trim.toInt)) .toDF() //将DataFrame注册为临时视图 peopleDF.createOrReplaceTempView("people") //SQL语句可以通过使用 Spark 提供的SQL方法运行 val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19") // 结果中的行的列可以通过字段索引 teenagersDF.map(teenager => "Name: " + teenager(0)).show() // +------------+ // | value| // +------------+ // |Name: Justin| // +------------+ // 或通过字段名称 teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show() // +------------+ // | value| // +------------+ // |Name: Justin| // +------------+ // 没有为数据集[Map [K,V]]预定义的编码器,明确定义了 implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]] // 原始类型和大小写类型也可以定义为 // implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder() // row.getValuesMap [T]一栏立刻变成一个地图[String,T] teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect() // Array(Map("name" -> "Justin", "age" -> 19))
当案例类不能提前定义时(例如,记录的结构是用字符串编码的,或者文本数据集将被解析,字段对不同的用户来说投影方式不同),DataFrame可以用三个步骤以编程方式创建一个。
1,Row从原RDD 创建一个RDD;
2, 创建在步骤1中创建的RDD中StructType与Rows 结构匹配 的模式。
3,Row通过createDataFrame提供的方法将模式应用于s 的RDD SparkSession。import spark.implicits._ // 创建一个 RDD val peopleRDD = spark.sparkContext.textFile("people.txt") // 模式用字符串编码 val schemaString = "name age" // 根据模式字符串 val fields = schemaString.split(" ") .map(fieldName => StructField(fieldName, StringType, nullable = true)) val schema = StructType(fields) // 将RDD(人的记录)行 val rowRDD = peopleRDD .map(_.split(",")) .map(attributes => Row(attributes(0), attributes(1).trim)) // 应用架构的RDD val peopleDF = spark.createDataFrame(rowRDD, schema) //使用DataFrame peopleDF.createOrReplaceTempView("people") // SQL可以在使用DataFrames val results = spark.sql("SELECT name FROM people") // SQL查询的结果是DataFrame并支持所有正常的RDD操作 // 结果中一行的列可以通过字段索引或字段名称 results.map(attributes => "Name: " + attributes(0)).show() // +-------------+ // | value| // +-------------+ // |Name: Michael| // | Name: Andy| // | Name: Justin| // +-------------+