Spark SQL简介

Spark SQL是Spark处理数据的一个模块,跟基本的Spark RDD的API不同,Spark SQL中提供的接口将会提供给Spark更多关于结构化数据和计算的信息。

Spark SQL is not about SQL
Spark SQL is about more than SQL
从严格意义上来说sparkSQL不仅仅是SQL,更加准确的来说,他是超乎SQL的作用。
Spark SQL is Apache Spark’s module for working with structured data.
Spice SQL是Apache Spark的用于处理结构化数据的模块

RDD、DataFrame和Dataset区别

RDD和Dataset、DataFrame的主要区别是Dataset和DataFrame带有数据的schema。
Dataset和DataFrame的区别是:

DataFrame = Dataset[Row]

从SparkSession开始

SparkSession是Spark中所有功能的入口类。官网:http://spark.apache.org/docs/latest/sql-getting-started.html import org.apache.spark.sql.SparkSession

val spark = SparkSession
  .builder()
  .appName("Spark SQL basic example")
  .config("spark.some.config.option", "some-value")
  .getOrCreate()

创建DataFrame

要想创建DataFrame,需要先导入隐式转换,然后可以从现有的RDD,Hive表或Spark数据源创建DataFrame 。

现有的RDD转换成DataFrame

用RDD转成DataFrame,有两种转换方式:

使用反射推断机制
import spark.implicits._
case class Person(name: String, age: Long)
val peopleDF = spark.sparkContext
  .textFile("examples/src/main/resources/people.txt")
  .map(_.split(","))
  .map(attributes => Person(attributes(0), attributes(1).trim.toInt))
  .toDF()
  
//查看schema
personDF.printSchema()
以编程方式指定架构
import org.apache.spark.sql.types._

val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")
val schemaString = "name age"
val fields = schemaString.split(" ")
  .map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)

val rowRDD = peopleRDD
  .map(_.split(","))
  .map(attributes => Row(attributes(0), attributes(1).trim))
  
val peopleDF = spark.createDataFrame(rowRDD, schema)
数据源方式

Spark支持多种数据源,并可以从json或parquet等可以推断出schema的文件中直接得到schema。

val df = spark.read.format("json").load("file://./examples/src/main/resources/people.json")

df.show()
在代码中使用SQL

只需要把DataFrame创建一个别名:

df.createOrReplaceTempView("people")
spark.sql("SELECT * FROM global_temp.people").show()

就可以使用SQL了。

在代码中使用算子
df.printSchema()

df.select("name").show()
//还可以使用带$的方式,这种方式可以对结果进行修改
df.select($"name", $"age" + 1).show()

df.filter($"age" > 21).show()
df.groupBy("age").count().show()

//想在结果中加东西还可以这样(按字段名)
df.map(x => "name" + x.getAs[String]("name")).show()
//按下标取单个值(对于单列来说)
df.map(x => "name" + x(0)).show()

//把DataFrame写入本地
df.write.format("json").save("file://./examples/src/main/resources/writePeople.json")

//对于已经处在的路径,save需要设定模式(默认ErrorIfExists),使用时是SaveMode.Append或者"append"(加一个新文件)
//另外还有overwrite(覆盖(所有))和ignore(存在就跳过)
df.write.format("json").mode("ignore").save("file://./examples/src/main/resources/writePeople.json")

df.show(false)//不会把长的数据变成带...的隐藏显示
df.head(10).foreach(println)
df.filter("age > 10")
df.filter("name = '' or name = 'null' or name is null")
df.filter("substring(name, 0, 1) = 'M'")
    
//DataFrame里面orderBy和sort是同一个,orderBy是sort的别名
df.sort("age").show()//得到的是字典顺序(如:1,10,11,2,20,21...)
df.sort(students.col("age").desc).show()
df.sort(students.col("name"), students.col("age")).show()
df.sort(students.col("name"), students.col("age") as "my_age").show()

使用Dataset

case class Sales(transactionId:String)
//读取csv格式的文件
val df = spark.read.format("csv").option("header", "true").option("inferSchema", "true").load("")
val ds = spark.read.format("csv").option("header", "true").option("inferSchema", "true").load("").as[Sales]

//其中,前面一部分是定义DataFrame的语句所以可以直接是
val ds = df.as[Sales]
//也可以这样:val ds = spark.sparkContext.parallelize(List("1","2")).toDS()

//取值方式
df.select("transactionId").show
//Dataset可以使用DataFrame的方式,也可以这样
ds.map(_.transactionId).show

使用catalog查看数据库

val catalog = spark.catalog

catalog.listDatabases().show(false)//显示数据库信息
catalog.listDatabases().map(x=>x.locationUri).show(false)//取某列字段(这里的是locationUri)
catalog.listTables().show(false)
//取出表明带有page_views的表
catalog.listTables().filter(x=>x.name.contains("page_views")).show(false)

catalog.listColumns("page_views").show(false)
catalog.listFunctions().show(false)

catalog.isCached("page_views")
catalog.cacheTable("page_views")
catalog.uncacheTable("page_views")

分区操作

自动推导分区:

比如:定义一个hdfs文件路径:hadoop fs -mkdir -p /parquet_table/gender=male/country=US
放进去一个文件:hadoop fs -put users.parquet /parquet_table/gender=male/country=US/
然后读取(默认是hdfs):val df = spark.read.format("parquet").load("/parquet_table")
df.prientSchema
就可以看到最后两个是gender和country,他们是分区。
如果指定的目录是/parquet_table/gender=male,那么只有country是分区字段

自定义UDF函数

val people = Array("zhangsan", "lisi", "wangwu")

val rdd = spark.sparkContext.parallelize(people)
//rdd.collect().foreach(println)
import spark.implicits._
rdd.toDF("name").createOrReplaceTempView("test")

//注册udf函数(可以是匿名函数)
spark.udf.register("strLen", (str:String) => str.length)

spark.sql("select name, strLen(name) from test").show

注意

spark.stop()