SparkSQL

SparkSQL

【SparkSQL是Spark用于结构化数据(structured data)处理的Spark模块】

SparkSQL 运行原理

/*
Spark SQL运行原理
	Catalyst优化器是Spark SQL的核心
	SELECT name FROM
       (
           SELECT id, name FROM people
       ) p
       WHERE p.id = 1
*/

SparkSQL API

1、SparkContext

2、SQLContext

SparkSQL的编程入口

3、HiveContext

SQLContext的子集,包含更多功能

4、SparkSession(Spark 2之后推荐)

SparkSession:合并了SQLContext与HiveContext
提供与Spark功能交互单一入口点,并允许使用DataFrame和Dataset API对Spark进行编程

若同时创建SparkContext和SparkSession,必须先创建SparkContext再SparkSession
否则会报出如下异常:即重复创建SparkContext

org.apache.spark.SparkException: Only one SparkContext should be running in this JVM (see SPARK-2243)
# SparkContext的创建
【配置】
	master:
		local[*] : CPU核数为当前环境的最大值
		local[2] : CPU核数为2
		local : CPU核数为1
		yarn
		
# SparkSession的创建
val spark = SparkSession.builder
	.master("master")
	.appName("appName")
	.getOrCreate()

SparkSQL数据集

Dataset【分布式数据集合】

从Spark1.6开始引入的新的抽象,特定领域对象中的【强类型】集合

可以使用函数或者相关操作并行地进行转换等操作(操作map, flatMap, filter等)

可以用样例类来对DataSet中定义数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称

等于 RDD + Schema

Schema 
在Apache SparkSQL中,schema定义了DataFrame的结构,包括列名、数据类型和列的可选描述。
Schema是DataFrame所包含数据的蓝图,它类似于传统数据库中的表模式。
【Dataset 案例】
# 样例类 case class
case class Product(id:Int,name:String,price:Float)
def main(args: Array[String]): Unit = {
	# 配置 SparkContext ==> conf
	val conf = new SparkConf()
		.setMaster("local[4]") 		// local
		.setAppName("spark-sql-01")
	# 创建 SparkSession ==> spark
	val spark: SparkSession = SparkSession
		.builder()
		.config(conf)
		.getOrCreate()
	# 导包
	import spark.implicits._

	val dsPhone: Dataset[Product] = spark.createDataset(Seq(
		Product(1, "Huawei Handset", 5888.0f),
		Product(1, "IPhone", 6666.0f),
		Product(1, "Samsung", 3456.0f)
	))
	dsPhone.printSchema()
}

DataFrame【以RDD为基础的分布式数据集】

DataFrame是特殊的Dataset:DataFrame=Dataset[Row]
	所以可以通过as方法将DataFrame转换为DataSet,
	Row是一个类型,所有的表结构信息都用Row来表示,获取数据时需要指定顺序
	
类似传统数据的二维表格

在RDD基础上加入了Schema(数据结构信息)

DataFrame Schema支持嵌套数据类型
	struct
	map
	array
	
提供更多类似SQL操作的API				

【DataFrame与RDD的区别】			
	DataFrame带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。
	这使得SparkSQL得以洞察更多的数据信息,从而对藏于DataFrame背后的数据源以及作用于DataFrame之上的变换进行了针对性的优化,
	最终达到大幅提升运行时效率的目标。
	由于RDD无从得知锁存数据元素的具体内部结构,Spark Core只能在stage层面进行简单、通用的流水线优化。
	
	同时,DataFrame也支持嵌套数据类型(struct、array和map)。
	从API易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好。		
		
	DataFrame为数据提供了Schema的视图,可以直接当做数据库中的一张表来对待。
	DataFrame也是懒执行的,但性能上比RDD要高,
	主要原因:优化的执行计划,即查询计划通过Spark catalyst optimiser进行优化	
【DataFrame 案例】
# 配置 SparkContext ==> conf
val conf = new SparkConf()
	.setMaster("local[4]") 		// local
	.setAppName("spark-sql-01")
# 创建 SparkSession ==> spark
val spark: SparkSession = SparkSession
	.builder()
	.config(conf)
	.getOrCreate()	
# 定义 schema 
import org.apache.spark.sql.types.{StructType, StructField, StringType, LongType, IntegerType}
val schema : StructType = StructType(
	Seq(
		StructField("user_id",LongType),
		StructField("locale",StringType ),
		StructField("birthYear",IntegerType),
		StructField("gender",StringType),
		StructField("joinedAt",StringType),
		StructField("location",StringType),
		StructField("timezone",StringType)
	)
)
# 创建 DataFrame
val frmUsers: DataFrame = spark.read
	.schema(schema)
	.option("separator", ",")
	.option("header", true)
	.csv("hdfs://single01:9000/spark/cha02/users.csv") // HDFS 上的路径文件
	.repartition(4)
	.cache()

【SQL 简单查询】

select
	col,cols*,agg*
where
	conditionCols
group by
	col,cols*
having
	condition
order by
	col asc|desc
limit
	n

查询 select :$“colName” = col(“colName”),支持各种函数对字段处理

条件筛选 where

# 等于 ===     不等于 =!=   为空 isNull   不为空 isNotNull
frmUsers	
	.where($"birthYear"===1999)
	.where($"location".isNotNull)
	.where($"locale".isin("id_ID","hu_HU"))
	.where($"birthYear">=1995)
	.where($"birthYear".between(1996,1997))
	.where($"location".like("%Indonesia")) // 模糊查询
	.where($"location".rlike(".*Sum.*")) // 正则
	.where($"birthYear".equalTo(1995)) // 等于
	.where($"birthYear".notEqual(1995)) // 不等于
	.where($"birthYear".lt(1995)) // 大于
	.where($"birthYear".leq(1995)) // 大于等于
	.where($"birthYear".gt(1995)) // 小于
	.where($"birthYear".geq(1995)) // 小于等于
	.where($"location".isInCollection(Array("Bekasi","Bangkalan")))
	.where(not($"location".isInCollection(Array("Bekasi","Bangkalan")))) // 非 => not()

多条件

newCol:Column = ColOne and ColTwo			与
   newCol:Column = ColOne or ColTwo			或
   newCol:Column = not(Column)val rddWhere: Dataset[Row] = dfCustomer.where(
	$"cus_state".isin("TX", "PR") and 
	length($"cus_lname").gt(6)
)

分组 group by

frmUsers
	.select($"locale",$"birthYear")
	.where($"birthYear">1990)
	.groupBy($"gender",$"locale")
	.count() // 单一聚合

聚合 agg(aggregate)

agg 允许在一个函数中调用中使用多个聚合函数

agg 通常在 groupBy 之后使用,首先按照某些列对数据进行分组,然后对每个分组应用聚合函数

内置聚合函数:count, sum, avg, min, max, first, last 等,还支持自定义聚合函数

agg 的结果是一个包含聚合结果的新 DataFrame,其列数取决于你使用的聚合函数的数量

frmUsers
	.select($"locale",$"birthYear")
	.where($"birthYear">1990)
	.groupBy($"gender",$"locale")
	.agg(
		count($"locale").as("locale_count"),
		round(avg($"birthYear"),2).as("avg_birth_year")
	)

二次筛选 having = where

frmUsers
	.select($"locale",$"birthYear")
	.where($"birthYear">1990)
	.groupBy($"gender",$"locale")
	.agg(
		count($"locale").as("locale_count"),
		round(avg($"birthYear"),2).as("avg_birth_year")
	)
	.where($"locale_count">=10 and $"avg_birth_year">=1993)

排序 order by

支持原始字段和聚合字段($“field”.asc | desc)

frmUsers
	.select($"locale",$"birthYear")
	.where($"birthYear">1990)
	.groupBy($"gender",$"locale")
	.agg(
		count($"locale").as("locale_count"),
		round(avg($"birthYear"),2).as("avg_birth_year")
	)
	.where($"locale_count">=10 and $"avg_birth_year">=1993)
	.orderBy($"locale_count".desc)

限制 limit ==> show(10)

frmUsers
	.select($"locale",$"birthYear")
	.where($"birthYear">1990)
	.groupBy($"gender",$"locale")
	.agg(
		count($"locale").as("locale_count"),
		round(avg($"birthYear"),2).as("avg_birth_year")
	)
	.where($"locale_count">=10 and $"avg_birth_year">=1993)
	.orderBy($"locale_count".desc)
	.show(10)

【多重分组】<==>【多字段多维度分组并表】

等同于将多个不同维度聚合的数据纵向组合成一个数据集

/**
select ... from T group by a
union all
select ... from T group by a,b
...
*/
# 语法:	
grouping sets 定制分组
	如 grouping sets(a,b,c,(a,c))
rollup 右滚动排列组合
	如 rollup(a,b,c)  <=> grouping sets(a,(a,b),(a,b,c))
cube  全量排列组合
	如 cube(a,b,c) <=> grouping sets(,a,b,c,(a,b),(a,c),(b,c),(a,b,c))
	
select grouping__id,a,b,c,count(*) as cnt from T
group by a,b,c grouping sets(a,b,c,(a,c))

select grouping__id,a,b,c,count(*) as cnt from T
group by rollup(a,b,c)

select grouping__id,a,b,c,count(*) as cnt from T
group by cube(a,b,c)	
Spark 不支持 grouping sets API, 可以通过spark.sql(sql:String)来实现
spark.sql(
  """
	|select gid
	|from (
	|select grouping__id as gid, gender, birthYear, count(*) as cnt from user_info
	|group by cube(gender,birthYear)
	|)A group by gid
	|""".stripMargin)
  .show(100)
frmUsers
	.rollup($"gender",$"birthYear")
	.agg(count("*").as("cnt"))
	.select(grouping_id().as("group_id"),$"gender",$"birthYear",$"cnt")
	.show(100)	

关联查询

指定连接类型:JoinType: 
	inner 							内部连接:【交集】  缺省默认 ✔
	outer = full = fullouter		全外连接:【双全集】
	left = leftouter 				左外连接:【左全集】:先返回两表笛卡尔积,再进行ON条件筛选
										若右表的数据也需要提取,则只能使用left
	semi = leftsemi 				左外连接:【左交集】:右表只做存在性检查,代替子查询in
										只返回左表在右表中有匹配记录的数据,不提取右表数据
	anti = leftanti 				左外连接:【左差集】:右表只做存在性检查,代替子查询not in
	right = rightouter 				右外连接:【右全集】
	cross 							默认:交叉连接:【笛卡尔积】

潜在的【using】:若join操作采用如下语法【不提供joinType:inner】且【关联字段在两张表中同名】
	join(dataset:DataSet[_],oneCol:String)
	join(dataset:DataSet[_],cols:Seq[String])
	实际的运行结果,两张表中会执行【同名列去重】
【案例】
case class Student(name:String,classId:Int)
  case class Class(classId:Int,className:String)
  def main(args: Array[String]): Unit = {
    val ssb: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-join-01")
    val spark: SparkSession = ssb.spark

    import spark.implicits._

    val frmClass: DataFrame = spark.createDataFrame(
      Seq(
        Class(1, "12212"),
        Class(2, "12309"),
        Class(3, "12401")
      )
    )

    val frmStu : DataFrame = spark.createDataFrame(
      Seq(
        Student("henry",1),
        Student("ariel",2),
        Student("jack",1),
        Student("rose",4),
        Student("jerry",2),
        Student("mary",1),
      )
    )

    // join 默认是笛卡尔积
    frmStu.as("S")
      .join(frmClass.as("C")) // 笛卡尔积
      .join(frmClass.as("C"),$"S.classId"===$"C.classId","inner")
      .join(frmClass.as("C"),Seq("classId"),"inner") // 默认启用 using(同名字段去重)
      .join(frmClass.as("C"),$"S.classId"===$"C.classId","left") // 左外
      .join(frmClass.as("C"),$"S.classId"===$"C.classId","right") // 右外
      .join(frmClass.as("C"),$"S.classId"===$"C.classId","anti") // 左差集
      .join(frmClass.as("C"),$"S.classId"===$"C.classId","semi") // 左交集

      .join(frmClass.as("C"),$"S.classId"===$"C.classId","full") // 全外
      .show(100)

    ssb.stop()
  }

数据提取

DataSet[Row] dataSet = df.limit(n:Int)
Array[Row] rows = df.collect()
Array[Row] rows = df.tail(n:Int)
Array[Row] rows = df.take(n:Int)

数据展示

df.show([n:Int])

【SQL 函数】

常用函数

$"NAME" = col("NAME")								// 取列值

as("ALIAS_NAME")									// 别名

as(alias:Seq[String])								// 多个别名 pos_explode(Array), explode(Map)

when(CONDITION,V1)....otherwise(VN)					// 条件
when($"score"<60,"D")
	.when($"score"<80,"C")
	.when($"score"<90,"B")
	.otherwise("A")
	.as("score_level")
	
lit(VALUE)											//常量列
lit(5).as("new_const_col")
	
withColumn(colName:String, col:Column)				// 扩展列,通常用于使用【窗口函数】扩展表的列
	
cast(DataType)										// 类型转换	
	
explode	和 pos_explode								// 一个 select 子句中只能出现一个 explode	或 pos_explode
【案例】
case class Test(id:Int,hobbies:Array[String],scores:Map[String,Int])
def main(args: Array[String]): Unit = {
	val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-test2")
	val spark: SparkSession = builder.spark

	import spark.implicits._
	import org.apache.spark.sql.functions._

	val frm: DataFrame = spark.createDataFrame(Seq(
		Test(1,Array("money","freedom"),Map("java"->85,"mysql"->67)),
		Test(2,Array("beauty","beauty"),Map("java"->72,"mysql"->90)),
		Test(3,Array("sports","beauty"),Map("java"->76,"html"->52))
	))
	
	frm
		.select($"id",explode($"hobbies").as("hobby"),$"scores")
		.select($"id",$"hobby",explode($"scores").as(Seq("subject","score")))
		.select($"id",$"hobby",$"subject",$"score",
			when($"score"<60,"D")
				.when($"score"<80,"C")
				.when($"score"<90,"B")
				.otherwise("A")
				.as("score_level"),
			lit(5).as("new_const_col")
		).show()

	builder.stop()
}
// 输出结果
+---+-------+-------+-----+-----------+-------------+
| id|  hobby|subject|score|score_level|new_const_col|
+---+-------+-------+-----+-----------+-------------+
|  1|  money|   java|   85|          B|            5|
|  1|  money|  mysql|   67|          C|            5|
|  1|freedom|   java|   85|          B|            5|
|  1|freedom|  mysql|   67|          C|            5|
|  2| beauty|   java|   72|          C|            5|
|  2| beauty|  mysql|   90|          A|            5|
|  2| beauty|   java|   72|          C|            5|
|  2| beauty|  mysql|   90|          A|            5|
|  3| sports|   java|   76|          C|            5|
|  3| sports|   html|   52|          D|            5|
|  3| beauty|   java|   76|          C|            5|
|  3| beauty|   html|   52|          D|            5|
+---+-------+-------+-----+-----------+-------------+

集合函数

array
	size(collectCol:Column)
	array(cols:Column*)							// 一行中的多列转为单列数组类型
	array_sort(arrayCol:Column)
	array_contains(arrayCol:Column,value:Any)
	array_distinct(arrayCol:Column)
	array_join(arrayCol:Column,sep:String,nullReplacement:String)

	array_except(arrayCol:Column)				// 差
	array_intersect(arrayCol:Column)			// 交
	array_union(arrayCol:Column)				// 并
	
map
	map_keys(mapCol:Column)
	map_values(mapCol:Column)
	map_entries(mapCol:Column)
【案例】

val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-test2")
val spark: SparkSession = builder.spark

import spark.implicits._
import org.apache.spark.sql.functions._

val frm: DataFrame = spark.createDataFrame(Seq(
	Test(1,Array("money","freedom"),Map("java"->85,"mysql"->67)),
	Test(2,Array("beauty","beauty"),Map("java"->72,"mysql"->90)),
	Test(3,Array("sports","beauty"),Map("java"->76,"html"->52))
))
frm
	.select(
		$"id",
		map_keys($"scores").as("subjects"),
		map_values($"scores").as("scores"),
		map_entries($"scores").as("subject_scores")
	).show()
	
// 输出结果
+---+-------------+--------+--------------------+
| id|     subjects|  scores|      subject_scores|
+---+-------------+--------+--------------------+
|  1|[java, mysql]|[85, 67]|[{java, 85}, {mys...|
|  2|[java, mysql]|[72, 90]|[{java, 72}, {mys...|
|  3| [java, html]|[76, 52]|[{java, 76}, {htm...|
+---+-------------+--------+--------------------+

字符串函数

提取
提取json
json_tuple(jsonCol:Column, fields:String*)				// fields => field1,field2
get_json_object(jsonCol:Column, path:String)			// path => $.field1[.field2] 
【案例】
val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-json")
val spark: SparkSession = builder.spark

import org.apache.spark.sql.functions._
import spark.implicits._

spark
	.createDataFrame(Seq(
		Json(1,"""{"name":"jack","age":22,"hobbies":["beauty","money","power"],"address":{"province":"jiangsu","city":"nanjing"}}"""),
		Json(2,"""{"name":"ariel","age":12,"hobbies":["beauty","game"],"address":{"province":"jiangsu","city":"wuxi"}}"""),
		Json(3,"""{"name":"tom","age":38,"hobbies":["handsome","money","car"],"address":{"province":"jiangsu","city":"nanjing"}}""")
	))
	.select($"id",
		json_tuple($"json","name","age","hobbies").as(Seq("name","age","hobbies")),
		get_json_object($"json","$.address.province").as("province"),
		get_json_object($"json","$.address.city").as("city")
	)
	.show()
// 输出结果
+---+-----+---+--------------------+--------+-------+
| id| name|age|             hobbies|province|   city|
+---+-----+---+--------------------+--------+-------+
|  1| jack| 22|["beauty","money"...| jiangsu|nanjing|
|  2|ariel| 12|   ["beauty","game"]| jiangsu|   wuxi|
|  3|  tom| 38|["handsome","mone...| jiangsu|nanjing|
+---+-----+---+--------------------+--------+-------+
提取: 正则
regexp_extract(col:Column, pattern:String, groupId:Int)
【案例】
case class Line(line:String)
def main(args: Array[String]): Unit = {
	val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-regex")
	val spark: SparkSession = builder.spark
	import org.apache.spark.sql.functions._
	import spark.implicits._
	import org.apache.spark.sql.types.{IntegerType, LongType, StructField, StringType}


	val regex_line = "(.*?) (INFO|WARN|ERROR) (.*): (.*)"
	val regex_log = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} (INFO|WARN|ERROR) .*"

	spark
		.read
		.text("spark-warehouse/datanode.log")// idea上的相对路径【hadoop上的datanode的一个日志文件】
		.toDF("line")
		.where($"line".rlike(regex_log))
		.select(
			regexp_extract($"line",regex_line,1).as("log_in_time"),
			regexp_extract($"line",regex_line,2).as("log_type"),
			regexp_extract($"line",regex_line,3).as("log_full_pack"),
			regexp_extract($"line",regex_line,4).as("log_detail")
		)
		.where($"log_type".equalTo("ERROR"))
		.show()
}
// 输出结果
+--------------------+--------+--------------------+--------------------+
|         log_in_time|log_type|       log_full_pack|          log_detail|
+--------------------+--------+--------------------+--------------------+
|2023-10-15 17:59:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-21 23:13:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-21 23:30:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-22 00:59:...|   ERROR|org.apache.hadoop...|              SIGHUP|
|2023-10-22 00:59:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-22 11:06:...|   ERROR|org.apache.hadoop...|              SIGHUP|
|2023-10-22 11:06:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-22 11:36:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-22 17:13:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-22 17:45:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-23 19:13:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-24 08:45:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-24 11:25:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-24 18:21:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-24 18:21:...|   ERROR|org.apache.hadoop...|              SIGHUP|
|2023-10-29 01:34:...|   ERROR|org.apache.hadoop...|             SIGTERM|
|2023-10-29 11:51:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-29 14:39:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-29 14:43:...|   ERROR|org.apache.hadoop...|/192.168.83.128:9866|
|2023-10-29 17:50:...|   ERROR|org.apache.hadoop...|             SIGTERM|
+--------------------+--------+--------------------+--------------------+
only showing top 20 rows
分裂和截取
split(col:Column,pattern:String)
substring(col:Column,pos:Int,len:Int)

-- groupId +N 		从左向右前N个
-- groupId -N 		从右向左前N个
-- 第N个  ==》 substring_index(substring_index(COL,SEP,+N),SEP,-1)(SEP为参数之间的分隔符)
substring_index(col:Column,sep:String,groupId:Int)

子字符串在字段中的未知
locate(subStr:String,col:Column)		// 有则>0,否则=0
instr(col:Column,subStr:String)
【案例】
case class Line(line:String)
def main(args: Array[String]): Unit = {
	val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-String")
    val spark: SparkSession = builder.spark
	
    import spark.implicits._
    import org.apache.spark.sql.functions._

    spark
        .createDataFrame(Seq(
          Line("beauty,money,power")
        ))
        .select(
			split($"line",",").as("hobbies"),
			substring_index(substring_index($"line",",",2),",",-1).as("hobbies2"),
			locate("money",$"line").as("pos_money")		-- 输出为8
        ).show()
}
字符串拼接
concat(cols:Column*)
concat_ws(sep:String,cols:Column*)
内容长度
length(col:Column)							// 字符长度
// 字节长度,未提供算子,需要通过 spark.sql(""" select octet_length(...)""") 实现
定长填充
lpad(col:Column,len:Int,pad:String)
rpad(col:Column,len:Int,pad:String)
清除两端空格
ltrim(col:Column)
rtrim(col:Column)
trim(col:Column)
大小写转换
initcap(col:Column)							// 每个单词首字母大写
upper(col:Column)							// 全大写
lower(col:Column)							// 全小写
【案例】
case class Line2(firstName:String, lastName:String)
def main(args: Array[String]): Unit = {
	val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-String")
    val spark: SparkSession = builder.spark
	
    import spark.implicits._
    import org.apache.spark.sql.functions._

	spark
		.createDataFrame(Seq(
		Line2("henry","chen"),
		Line2("pola","li"),
		Line2("jack","chan"),
		))
		.select(
			concat($"firstName", lit(" "), $"lastName") as "full_name_1",
			concat_ws(" ", initcap($"lastName"), initcap($"firstName")) as "full_name_2",
			length($"firstName")+length($"lastName").as("name_size"),
			lpad($"firstName",10,"_").as("name_size_10"),
			trim($"firstName").as("name_clear")
		)
		.show()
		
    builder.stop()
}
// 输出结果
+-----------+-----------+-----------------------------------------------------+------------+----------+
|full_name_1|full_name_2|(length(firstName) + length(lastName) AS `name_size`)|name_size_10|name_clear|
+-----------+-----------+-----------------------------------------------------+------------+----------+
| henry chen| Chen Henry|                                                    9|  _____henry|     henry|
|    pola li|    Li Pola|                                                    6|  ______pola|      pola|
|  jack chan|  Chan Jack|                                                    8|  ______jack|      jack|
+-----------+-----------+-----------------------------------------------------+------------+----------+
转换
hash(col:Column)							// 去哈希值
regexp_replace(col:Column,pattern:String,replace:String)	// 正则替换
translate(col:Column,from:String,to:String)	// 按字母转换
reverse(col:Column)							// 翻转
转码
encode(col:Column, charSet:String)
decode(col:Column, charSet:String)
非对称加密
sha1(col:Column)
md5(col:Column)

spark 自定义函数

-- 建议将自定义函数实现,单独建对象保存
import java.nio.charset.{StandardCharsets}
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec

object SparkUtil {
	private def secretInit(secret:String)={
		val allowNumBits: Array[Int] = Array(16, 24, 32)
		if (allowNumBits.contains(secret.size)) {
			new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8),"AES")
		}else{
			throw new RuntimeException(s"numBits ${secret.size} not in permitted values (${allowNumBits.mkString(",")})")
		}
	}

	def encrypt(src:String,secret:String)={
		val cipher: Cipher = Cipher.getInstance("AES")
		cipher.init(Cipher.ENCRYPT_MODE,secretInit(secret))
		val bytes: Array[Byte] = cipher.doFinal(src.getBytes(StandardCharsets.UTF_8))
		Base64.getEncoder.encodeToString(bytes)
	}

	def decrypt(dest:String,secret:String)={
		val cipher: Cipher = Cipher.getInstance("AES")
		cipher.init(Cipher.DECRYPT_MODE,secretInit(secret))
		val bytes: Array[Byte] = cipher.doFinal(Base64.getDecoder.decode(dest))
		new String(bytes, StandardCharsets.UTF_8)
	}
}

-- 在 Spark 环境下导入对象实现的方法,并在 SparkSession 中注册 UDF 函数
import core.SparkUtil.{encrypt,decrypt}
    spark.udf.register("aes_encrypt",(src:String,secret:String)=>encrypt(src,secret),StringType)
    spark.udf.register("aes_decrypt",(src:String,secret:String)=>decrypt(src,secret),StringType)

-- 在 SparkSql 中调用注册函数
val frm: DataFrame = spark.createDataFrame(Seq(
	Test(1,Array("money","freedom"),Map("java"->85,"mysql"->67)),
  	Test(2,Array("beauty","beauty"),Map("java"->72,"mysql"->90)),
  	Test(3,Array("sports","beauty"),Map("java"->76,"html"->52))
))

val secret = "henryyb2211ariel"

val frmEncrypt: DataFrame = frm
	.select($"id",
		callUDF(
		"aes_encrypt",
		array_join($"hobbies", ","),
		lit(secret)
	).as("encrypted_hobbies")
  )

val frmDecrypt: DataFrame = frmEncrypt
	.select(
		$"id",
		split(callUDF("aes_decrypt",$"encrypted_hobbies",lit(secret)),","
	).as("hobbies")
  ).show()

聚合函数

count(col:Column)									// 计数
countDistinct(col:Column,cols:Column*)				// 去重计数
sum(numCol:Column)									// 求和
sumDistinct(numCol:Column)							// 去重求和
avg(numCol:Column)									// 求均值
max(numCol:Column)									// 求最大值
min(numCol:Column)									// 求最小值
collect_set(col:Column)								// 去重收集
collect_list(col:Column)							// 列表收集

分析函数

// 方差:每个样本值与全体样本值的平均数之差的平方值的平均数,反应数据的偏离程度
var_samp(numCol:Column)						// 样本方差
var_pop(numCol:Column)						// 方差
// 标准差:方差的算术平方根,统计分布程度上的测量依据,也能反应数据的偏离程度
stddev_samp(numCol:Column)					// 标准样本差
stddev_pop(numCol:Column)					// 标准差
// 协方差:两个变量之间的线性关系,两个变量在方向和幅度上的一致性
/*
	如果两个变量的变化趋势一致:
		两者都大于自身的期望值时,协方差就是正值
		一个大于自身期望值,另一个小于自身期望值,协方差就是负值
	如果协方差为正,则表明X和Y同向变化,反之,协方差为负则表明反向变化;
	协方差的绝对值越大,表示同向或反向的程度越深
*/
covar_pop(numCol1:Column,numCol2:Column)	// 协方差
covar_samp(numCol1:Column,numCol2:Column)	// 样本协方差
【案例】
case class Product(name:String,price:Float,date:String,market:String,province:String,city:String)
def main(args: Array[String]): Unit = {
	val path = "D:\\courses\\2_phrase_snd\\6_spark\\products.txt"
	val ssb: SparkSessionBuilder = SparkSessionBuilder("local[*]","spark-sql-product") // 本地
	val sc: SparkContext = ssb.sc
	val rddProduct: RDD[Product] = sc.textFile(path)
		.mapPartitions(_.map(_.toProduct))
	
	val spark: SparkSession = ssb.spark
	
	import spark.implicits._
	import org.apache.spark.sql.functions._
	
	spark
		.createDataFrame(rddProduct)
		.groupBy($"province")
		.agg(
			count($"city").as("city_count"),
			size(collect_set($"city")).as("unique_city_count"),
			stddev($"price").as("dev_price"),
			var_pop($"price").as("pop_price")
		)
		.show()
		
	ssb.stop()
}

窗口函数和OVER从句

// over 从句,配合窗口函数或聚合函数使用
val specFull: WindowSpec = Window			// 全局窗口:unbounded preceding ~ following
  .partitionBy(col:Column)
val specTop2Curr: WindowSpec = specFull		// 排序窗口: unbounded preceding ~ current
  .orderBy(col:Column)
val specThree: WindowSpec = specTop2Curr	// 粒度窗口:start ~ current ~ stop
  .rowsBetween(start:Int, stop:Int)			// rowsBetween 物理偏移 rangeBetween 逻辑偏移

// 窗口函数
first(col:Column).over(specFull:WindowSpec)				// 窗口内首行
lag(col:Column,offset:Int).over(specOrderBy:WindowSpec)	// curr 上 offset 行
lead(col:Column,offset:Int).over(specOrderBy:WindowSpec)// curr 下 offset 行
last(col:Column).over(specFull)							// 窗口内末行
nth_value(col:Column,nth:Int).over(specOrderBy:WindowSpec)	// 窗口第 n 行
dense_rank().over(specOrderBy:WindowSpec)				// 排名(有相同排名时,不进行跳数)
percent_rank().over(specOrderBy:WindowSpec)				// 百分比排名
row_number().over(specOrderBy:WindowSpec)				// 行号
ntile(n:Int).over(specOrderBy:WindowSpec)				// 切片(抽样)
cume_dist().over(specOrderBy:WindowSpec)				// 分布
【案例】
case class Product(name:String,price:Float,date:String,market:String,province:String,city:String)
def main(args: Array[String]): Unit = {
	val path = "D:\\courses\\2_phrase_snd\\6_spark\\products.txt"
	val ssb: SparkSessionBuilder = SparkSessionBuilder("local[*]","spark-sql-product") // 本地
	val sc: SparkContext = ssb.sc
	val rddProduct: RDD[Product] = sc.textFile(path)
		.mapPartitions(_.map(_.toProduct))
	
	val spark: SparkSession = ssb.spark
	
	import spark.implicits._
	import org.apache.spark.sql.functions._
	
	spark.createDataFrame(rddProduct)
		.groupBy($"province",$"name")
		.agg(
			avg($"price").as("avg_price"),
			count("*").as("product_cnt")
		)
		.select(
			$"province",$"name",$"avg_price",$"product_cnt",
			dense_rank().over(
				Window.partitionBy($"province")
					.orderBy($"avg_price")
			).as("rnk_by_product_cnt_desc")
		)
		.show()

	ssb.stop()
}

// 输出结果
+--------+------+------------------+-----------+-----------------------+
|province|  name|         avg_price|product_cnt|rnk_by_product_cnt_desc|
+--------+------+------------------+-----------+-----------------------+
|    广东|白萝卜| 0.824999988079071|          2|                      1|
|    广东|洋白菜| 0.949999988079071|          2|                      2|
|    广东|大白菜|0.9500000178813934|          2|                      3|
|    广东|  冬瓜|              1.25|          2|                      4|
|    广东|  莴笋|1.5999999642372131|          2|                      5|
|    广东|  南瓜| 1.800000011920929|          2|                      6|
|    广东|绿豆芽| 1.899999976158142|          1|                      7|
|    广东|  菜瓜| 2.200000047683716|          1|                      8|
|    广东|  大蒜| 2.200000047683716|          1|                      8|
|    广东|胡萝卜|2.4000000953674316|          1|                      9|
|    广东|  小葱|               2.5|          1|                     10|
|    广东|  生菜|               2.5|          2|                     10|
|    广东|  青笋|2.5999999046325684|          1|                     11|
|    广东|小白菜|2.5999999046325684|          1|                     11|
|    广东|  香蕉|2.5999999046325684|          1|                     11|
|    广东|  土豆|2.6999999284744263|          2|                     12|
|    广东|  菠萝| 2.799999952316284|          1|                     13|
|    广东|  黄瓜| 2.799999952316284|          1|                     13|
|    广东|  菜花|2.8000000715255737|          2|                     14|
|    广东|西洋芹|2.9000000953674316|          1|                     15|
+--------+------+------------------+-----------+-----------------------+
only showing top 20 rows

日期函数

year(dateCol:Column)     			// 年
quarter(dateCol:Column)     		// 季
month(dateCol:Column)     			// 月
dayofweek(dateCol:Column)     		// 星期几 :周日~周六 1~7
weekofyear(dateCol:Column)     		// 年周
dayofmonth(dateCol:Column)     		// 月日
dayofyear(dateCol:Column)     		// 年日
hour(dateCol:Column)     			// 时
minute(dateCol:Column)     			// 分
second(dateCol:Column)     			// 秒

// 适用于将日期的不严格格式,格式化为严格格式
date_format(dateCol:Column,format:String)		// 日期格式化:yyyy-MM-dd HH:mm:ss
last_day(dateCol:Column)						// 日期当月最后一天			

// 【日期计算】
datediff(dateBig:Column,dateSmall:Column)		// 两个日期之间天数差
months_between(dateBig:Column,dateSmall:Column)	// 两个日期之间月数差
date_add(dateCol:Column,days:Int),				// 日期按天计算
add_months(dateCol:Column,months:Int)			// 日期按月计算
// 星期格式,英文单词前三个字母,如:MON TUE ...
next_day(dateCol:Column,weekFormat:String)		// 下一个星期几(未至本周,已至下周)

current_date()						// 当前日期 yyyy-MM-dd
current_timestamp()					// 当前日期 yyyy-MM-dd HH:mm:ss
unix_timestamp()					// 当前时间戳 1715307735 秒

// 如下操作必须进行 SparkSession 的配置
spark.conf.set("spark.sql.legacy.timeParserPolicy", "LEGACY")

// 【日期转时间戳】十位整数
// 获取系统当前时间戳
// 等同于 unix_timestamp(current_timestamp(), yyyy-MM-dd HH:mm:ss)
unix_timestamp()
// 获取指定日期时间戳,默认格式为 yyyy-MM-dd HH:mm:ss 严格格式
// 等同于 unix_timestamp(2013-07-25 00:00:00, yyyy-MM-dd HH:mm:ss
unix_timestamp(dateCol:Column)
// 获取指定日期指定格式时间戳,指定格式亦为严格格式,若不匹配报错
// 等同于 unix_timestamp(2013-07-25 00:00:00, yyyy-MM-dd)
unix_timestamp(dateCol:Column,format:String)

// 【日期转时区】
to_utc_timestamp(lit("2013-07-25 00:00:00"),"GMT+8")

// 【时间戳转日期】
// 将指定时间戳转日期:from_unixtime(lit(1715307735L))
from_unixtime(numCol:Column)
// 将指定时间戳按指定格式转日期:from_unixtime(lit(1715307735L),"yyyy-MM-dd")
from_unixtime(numCol:Column, format:String)

// 进保留日期 yyyy-MM-dd
to_date(dateCol:Column)

/*
	提取日期中指定格式的第一天的日期
    YEAR
    QUARTER
    MONTH
    WEEK
*/
date_trunc(format:String,date:Column)
【案例】
case class Line(date:String)
case class Line2(timestamp:Long)
def main(args: Array[String]): Unit = {
	val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-data-test")
	
    val spark: SparkSession = builder.spark
    import spark.implicits._
    import org.apache.spark.sql.functions._
    import demo01.SparkUtil.weekOfMonth

    spark
      .createDataFrame(Seq(
        Line2(1234786543),
        Line2(125986325),
        Line2(643757987)
      ))
        .select(
          from_unixtime($"timestamp").as("datetime")
        )
        .show()
// 输出结果
+-------------------+
|           datetime|
+-------------------+
|2009-02-16 20:15:43|
|1973-12-29 12:12:05|
|1990-05-27 06:39:47|
+-------------------+
     spark
      .createDataFrame(Seq(
        Line2(1234786543),
        Line2(125986325),
        Line2(643757987)
      ))
        .select(
          from_unixtime($"timestamp").as("datetime")
        ) 
        .select(
          date_trunc("YEAR",$"datetime").as("year"),
          date_trunc("QUARTER",$"datetime").as("quarter"),
          date_trunc("MONTH",$"datetime").as("month"),
          date_trunc("WEEK",$"datetime").as("week"),
          date_trunc("DAY",$"datetime").as("day")
        )
        .show()
// 输出结果
+-------------------+-------------------+-------------------+-------------------+-------------------+
|               year|            quarter|              month|               week|                day|
+-------------------+-------------------+-------------------+-------------------+-------------------+
|2009-01-01 00:00:00|2009-01-01 00:00:00|2009-02-01 00:00:00|2009-02-16 00:00:00|2009-02-16 00:00:00|
|1973-01-01 00:00:00|1973-10-01 00:00:00|1973-12-01 00:00:00|1973-12-24 00:00:00|1973-12-29 00:00:00|
|1990-01-01 00:00:00|1990-04-01 00:00:00|1990-05-01 00:00:00|1990-05-21 00:00:00|1990-05-27 00:00:00|
+-------------------+-------------------+-------------------+-------------------+-------------------+

    spark
      .createDataFrame(Seq(
        Line("2024-5-10"),
        Line("2024-3-5"),
        Line("2024-11-12")
      ))
      // date_format() 函数将日期转化为严格格式
      .select(date_format($"date","yyyy-MM-dd").as("date"))
        .select(
          datediff(current_date(),$"date").as("day_diff"),
          months_between(current_date(),$"date").as("month_diff"),
          date_add($"date",7).as("date_next_week"),
          add_months($"date",1).as("date_next_month"),
          next_day($"date","WED").as("next_wednesday"),
          unix_timestamp($"date","yyyy-MM-dd").as("timestamp")
        ).show()
// 输出结果
+--------+-----------+--------------+---------------+--------------+----------+
|day_diff| month_diff|date_next_week|date_next_month|next_wednesday| timestamp|
+--------+-----------+--------------+---------------+--------------+----------+
|      69| 2.25806452|    2024-05-17|     2024-06-10|    2024-05-15|1715270400|
|     135| 4.41935484|    2024-03-12|     2024-04-05|    2024-03-06|1709568000|
|    -117|-3.80645161|    2024-11-19|     2024-12-12|    2024-11-13|1731340800|
+--------+-----------+--------------+---------------+--------------+----------+
       

    builder.stop()
}

数学函数

abs(col:Column)							// 绝对值
degrees(col:Column)						// 弧度转角度 
radians(col:Column)						// 角度转弧度 
sin(col:Column)							// 正弦
cos(col:Column)							// 余弦
tan(col:Column)							// 正切
asin(col:Column)						// 反正弦
acos(col:Column)						// 反余弦
atan(col:Column)						// 反正切
round(col:Column,n:Int)					// 四舍五入
ceil(col:Column)						// 向上取整
floor(col:Column)						// 向下取整
format_number(col:Column,n:Int)			// 数值格式化
pow(col:Column,n:Int)					// 幂
log(n:Int,col:Column)					// 对数
rand()									// 随机数
greatest(col:Column*)				// 多列最大值
least(col:Column*)					// 多列最小值
【案例】
case class Scores(stu_id:Int,javaScore:Int,mysqlScore:Int,sparkScore:Int)
def main(args: Array[String]): Unit = {
  val builder: SparkSessionBuilder = SparkSessionBuilder("local[*]", "spark-sql-data-test")

  val spark: SparkSession = builder.spark
  import org.apache.spark.sql.functions._
  import spark.implicits._

  spark
	.createDataFrame(Seq(
		Scores(1,89,78,65),
		Scores(2,66,58,54),
		Scores(3,78,69,76)
    ))
    .select(
        $"stu_id",
        greatest($"javaScore",$"mysqlScore",$"sparkScore").as("maxScore"),
        least($"javaScore",$"mysqlScore",$"sparkScore").as("minScore")
     )
    .show()

  builder.stop()
}

SparkSQL 优化

// 查看表结构
df.printSchema()
// 查看执行计划
df.explain(true)

// 数据倾斜
/*
	数据过载:调整并行度(分区数),随机前缀/加盐/Map端聚合
		reduceByKey, combineByKey, aggregateByKey
	维度数据导致:小表+大表,可以采用广播变量
		broadcast
	抽样确定key的倾斜度,key值平均大小,重新分区,让数据均匀分布
	使用 skewness|kurtosis 算子检测数据倾斜
*/
  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值