sparkSQL應用

一、Spark SQL多數據源交互

Spark SQL可以與多種數據源交互,如普通文本、json、parquet、csv、MySQL等

1.寫入不同數據源

2.讀取不同數據源

寫數據

import java.util.Properties
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}

object WriterDataSourceDemo {

case class Person(id:Int,name:String,age:Int)

  def main(args: Array[String]): Unit = {
    //1.創建SparkSession
    val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL")
.getOrCreate()
    val sc: SparkContext = spark.sparkContext
    sc.setLogLevel("WARN")
    //2.讀取文件
    val fileRDD: RDD[String] = sc.textFile("D:\\data\\person.txt")
    val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" "))
    val rowRDD: RDD[Person] = linesRDD.map(line =>Person(line(0).toInt,line(1),line(2).toInt))
    //3.RDD轉成DF
    //注意:RDD中原本沒有toDF方法,新版本中要給它增加一個方法,可以使用隱式轉換
    import spark.implicits._
    //注意:上面的rowRDD的泛型是Person,裏面包含了Schema信息
    //所以SparkSQL可以通過反射自動獲取到並添加給DF
    val personDF: DataFrame = rowRDD.toDF
    //==================DF寫入到不同數據源===================
    //Text data source supports only a single column, and you have 3 columns.;
    //personDF.write.text("D:\\data\\output\\text")
    personDF.write.json("D:\\data\\output\\json")
    personDF.write.csv("D:\\data\\output\\csv")
    personDF.write.parquet("D:\\data\\output\\parquet")
    val prop = new Properties()
    prop.setProperty("user","root")
    prop.setProperty("password","root")
    personDF.write.mode(SaveMode.Overwrite).jdbc(
"jdbc:mysql://localhost:3306/bigdata?characterEncoding=UTF-8","person",prop)
    println("寫入成功")
    sc.stop()
    spark.stop()
  }
 }

 讀數據

import java.util.Properties
import org.apache.spark.SparkContext
import org.apache.spark.sql.SparkSession

object ReadDataSourceDemo {
  def main(args: Array[String]): Unit = {
    //1.創建SparkSession
    val spark: SparkSession = SparkSession.builder().master("local[*]").appName("SparkSQL")
.getOrCreate()
    val sc: SparkContext = spark.sparkContext
    sc.setLogLevel("WARN")
    //2.讀取文件
    spark.read.json("D:\\data\\output\\json").show()
    spark.read.csv("D:\\data\\output\\csv").toDF("id","name","age").show()
    spark.read.parquet("D:\\data\\output\\parquet").show()
    val prop = new Properties()
    prop.setProperty("user","root")
    prop.setProperty("password","root")
    spark.read.jdbc(
"jdbc:mysql://localhost:3306/bigdata?characterEncoding=UTF-8","person",prop).show()
    sc.stop()
    spark.stop()
  }
}

 總結

1.SparkSQL寫數據:

DataFrame/DataSet.write.json/csv/jdbc

2.SparkSQL讀數據:

SparkSession.read.json/csv/text/jdbc/format

二、Spark SQL自定義函數

自定義函數分類

類似於hive當中的自定義函數, spark同樣可以使用自定義函數來實現新的功能。

spark中的自定義函數有如下3類

1.UDF(User-Defined-Function)

輸入一行,輸出一行

2.UDAF(User-Defined Aggregation Funcation)

輸入多行,輸出一行

3.UDTF(User-Defined Table-Generating Functions)

輸入一行,輸出多行

自定義UDF

有udf.txt數據格式如下:

Hello
abc
study
small

通過自定義UDF函數將每一行數據轉換成大寫

select value,smallToBig(value) from t_wor

import org.apache.spark.SparkContext
import org.apache.spark.sql.{Dataset, SparkSession}

  object UDFDemo {

  def main(args: Array[String]): Unit = {
    //1.創建SparkSession
    val spark: SparkSession = SparkSession
.builder()
.master("local[*]")
.appName("SparkSQL")
.getOrCreate()
    val sc: SparkContext = spark.sparkContext
    sc.setLogLevel("WARN")
    //2.讀取文件
    val fileDS: Dataset[String] = spark.read.textFile("D:\\data\\udf.txt")
    fileDS.show()
    /*
    +----------+
    |     value|
    +----------+
    |helloworld|
    |       abc|
    |     study|
    | smallWORD|
    +----------+
     */
   /*
    將每一行數據轉換成大寫
    select value,smallToBig(value) from t_word
    */
    //註冊一個函數名稱爲smallToBig,功能是傳入一個String,返回一個大寫的String
    spark.udf.register("smallToBig",(str:String) => str.toUpperCase())
    fileDS.createOrReplaceTempView("t_word")
    //使用我們自己定義的函數
    spark.sql("select value,smallToBig(value) from t_word").show()
    /*
    +----------+---------------------+
    |     value|UDF:smallToBig(value)|
    +----------+---------------------+
    |helloworld|           HELLOWORLD|
    |       abc|                  ABC|
    |     study|                STUDY|
    | smallWORD|            SMALLWORD|
    +----------+---------------------+
     */
    sc.stop()
    spark.stop()
  }
}

自定義UDAF[瞭解]

有udaf.json數據內容如下

{"name":"Michael","salary":3000}
{"name":"Andy","salary":4500}
{"name":"Justin","salary":3500}
{"name":"Berta","salary":4000}

求取平均工資

●繼承UserDefinedAggregateFunction方法重寫說明

inputSchema:輸入數據的類型

bufferSchema:產生中間結果的數據類型

dataType:最終返回的結果類型

deterministic:確保一致性,一般用true

initialize:指定初始值

update:每有一條數據參與運算就更新一下中間結果(update相當於在每一個分區中的運算)

merge:全局聚合(將每個分區的結果進行聚合)

evaluate:計算最終的結果

import org.apache.spark.SparkContext
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types._
import org.apache.spark.sql.{DataFrame, Row, SparkSession}


object UDAFDemo {
 
def main(args: Array[String]): Unit = {
   
//1.獲取sparkSession
   
val spark: SparkSession = SparkSession.builder().appName("SparkSQL").master("local[*]").getOrCreate()
    val sc: SparkContext = spark.sparkContext
   
sc.setLogLevel("WARN")
   
//2.讀取文件
    val employeeDF: DataFrame = spark.read.json("D:\\data\\udaf.json")
   
//3.創建臨時表
   
employeeDF.createOrReplaceTempView("t_employee")
   
//4.註冊UDAF函數
   
spark.udf.register("myavg",new MyUDAF)
    //5.使用自定義UDAF函數
   
spark.sql("select myavg(salary) from t_employee").show()
   
//6.使用內置的avg函數
   
spark.sql("select avg(salary) from t_employee").show()
  }
}

class MyUDAF extends UserDefinedAggregateFunction{
 
//輸入的數據類型的schema
 
override def inputSchema: StructType = {
    
StructType(StructField("input",LongType)::Nil)
  }
 
//緩衝區數據類型schema,就是轉換之後的數據的schema
 
override def bufferSchema: StructType = {
   
StructType(StructField("sum",LongType)::StructField("total",LongType)::Nil)
  }
 
//返回值的數據類型
 
override def dataType: DataType = {
   
DoubleType
 
}
 
//確定是否相同的輸入會有相同的輸出
 
override def deterministic: Boolean = {
   
true
 
}
 
//初始化內部數據結構
 
override def initialize(buffer: MutableAggregationBuffer): Unit = {
   
buffer(0) = 0L
   
buffer(1) = 0L
 
}
 
//更新數據內部結構,區內計算
 
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
   
//所有的金額相加
   
buffer(0) = buffer.getLong(0) + input.getLong(0)
   
//一共有多少條數據
   
buffer(1) = buffer.getLong(1) + 1
 
}
 
//來自不同分區的數據進行合併,全局合併
 
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
   
buffer1(0) =buffer1.getLong(0) + buffer2.getLong(0)
   
buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
  }
 
//計算輸出數據值
 
override def evaluate(buffer: Row): Any = {
   
buffer.getLong(0).toDouble / buffer.getLong(1)
  }
}

三、開窗函數

 

概述

https://www.cnblogs.com/qiuting/p/7880500.html

 

開窗函數的引入是爲了既顯示聚集前的數據又顯示聚集後的數據。即在每一行的最後一列添加聚合函數的結果。

開窗用於爲行定義一個窗口(這裏的窗口是指運算將要操作的行的集合),它對一組值進行操作,不需要使用 GROUP BY 子句對數據進行分組,能夠在同一行中同時返回基礎行的列和聚合列。

聚合函數和開窗函數

聚合函數是將多行變成一行,count,avg....

開窗函數是將一行變成多行

聚合函數如果要顯示其他的列必須將列加入到group by中

開窗函數可以不使用group by,直接將所有信息顯示出來

開窗函數分類

1.聚合開窗函數

聚合函數() OVER(選項),這裏的選項可以是PARTITION BY 子句,但不可以是 ORDER BY 子句。

2.排序開窗函數

排序函數() OVER(選項),這裏的選項可以是ORDER BY 子句,也可以是 OVER(PARTITION BY 子句 ORDER BY 子句),但不可以是 PARTITION BY 子句

準備工作

/export/servers/spark/bin/spark-shell --master spark://node01:7077,node02:7077

case class Score(name: String, clazz: Int, score: Int)
val scoreDF = spark.sparkContext.makeRDD(Array(
Score("a1", 1, 80),
Score("a2", 1, 78),
Score("a3", 1, 95),
Score("a4", 2, 74),
Score("a5", 2, 92),
Score("a6", 3, 99),
Score("a7", 3, 99),
Score("a8", 3, 45),
Score("a9", 3, 55),
Score("a10", 3, 78),
Score("a11", 3, 100))
).toDF("name", "class", "score")
scoreDF.createOrReplaceTempView("scores")
scoreDF.show()
+----+-----+-----+
|name|class|score|
+----+-----+-----+
|  a1|    1|   80|
|  a2|    1|   78|
|  a3|    1|   95|
|  a4|    2|   74|
|  a5|    2|   92|
|  a6|    3|   99|
|  a7|    3|   99|
|  a8|    3|   45|
|  a9|    3|   55|
| a10|    3|   78|
| a11|    3|  100|
+----+-----+-----+

 

聚合開窗函數

示例1

OVER 關鍵字表示把聚合函數當成聚合開窗函數而不是聚合函數。

SQL標準允許將所有聚合函數用做聚合開窗函數。

spark.sql("select  count(name)  from scores").show
spark.sql("select name, class, score, count(name) over() name_count from scores").show

查詢結果如下所示:

+----+-----+-----+----------+                                                  
|name|class|score|name_count|
+----+-----+-----+----------+
|  a1|    1|   80|        11|
|  a2|    1|   78|        11|
|  a3|    1|   95|        11|
|  a4|    2|   74|        11|
|  a5|    2|   92|        11|
|  a6|    3|   99|        11|
|  a7|    3|   99|        11|
|  a8|    3|   45|        11|
|  a9|    3|   55|        11|
| a10|    3|   78|        11|
| a11|    3|  100|        11|
+----+-----+-----+----------+

 示例2

OVER 關鍵字後的括號中還可以添加選項用以改變進行聚合運算的窗口範圍。

如果 OVER 關鍵字後的括號中的選項爲空,則開窗函數會對結果集中的所有行進行聚合運算。

開窗函數的 OVER 關鍵字後括號中的可以使用 PARTITION BY 子句來定義行的分區來供進行聚合計算。與 GROUP BY 子句不同,PARTITION BY 子句創建的分區是獨立於結果集的,創建的分區只是供進行聚合計算的,而且不同的開窗函數所創建的分區也不互相影響

 

下面的 SQL 語句用於顯示按照班級分組後每組的人數

OVER(PARTITION BY class)表示對結果集按照 class 進行分區,並且計算當前行所屬的組的聚合計算結果。

spark.sql("select name, class, score, count(name) over(partition by class) name_count from scores").show

查詢結果如下所示:

+----+-----+-----+----------+                                                   
|name|class|score|name_count|
+----+-----+-----+----------+
|  a1|    1|   80|         3|
|  a2|    1|   78|         3|
|  a3|    1|   95|         3|
|  a6|    3|   99|         6|
|  a7|    3|   99|         6|
|  a8|    3|   45|         6|
|  a9|    3|   55|         6|
| a10|    3|   78|         6|
| a11|    3|  100|         6|
|  a4|    2|   74|         2|
|  a5|    2|   92|         2|
+----+-----+-----+----------+

排序開窗函數

1、ROW_NUMBER順序排序

row_number() over(order by score) as rownum 表示按score 升序的方式來排序,並得出排序結果的序號

注意:

在排序開窗函數中使用 PARTITION  BY 子句需要放置在ORDER  BY 子句之前。

spark.sql("select name, class, score, row_number() over(order by score) rank from scores").show()
+----+-----+-----+----+
|name|class|score|rank|
+----+-----+-----+----+
|  a8|    3|   45|   1|
|  a9|    3|   55|   2|
|  a4|    2|   74|   3|
|  a2|    1|   78|   4|
| a10|    3|   78|   5|
|  a1|    1|   80|   6|
|  a5|    2|   92|   7|
|  a3|    1|   95|   8|
|  a6|    3|   99|   9|
|  a7|    3|   99|  10|
| a11|    3|  100|  11|
+----+-----+-----+----+

spark.sql("select name, class, score, row_number() over(partition by class order by score) rank from scores").show()
+----+-----+-----+----+                                                         
|name|class|score|rank|
+----+-----+-----+----+
|  a2|    1|   78|   1|
|  a1|    1|   80|   2|
|  a3|    1|   95|   3|
|  a8|    3|   45|   1|
|  a9|    3|   55|   2|
| a10|    3|   78|   3|
|  a6|    3|   99|   4|
|  a7|    3|   99|   5|
| a11|    3|  100|   6|
|  a4|    2|   74|   1|
|  a5|    2|   92|   2|
+----+-----+-----+----+

2、RANK跳躍排序

rank() over(order by score) as rank表示按 score升序的方式來排序,並得出排序結果的排名號。

這個函數求出來的排名結果可以並列(並列第一/並列第二)並列排名之後的排名將是並列的排名加上並列數

簡單說每個人只有一種排名,然後出現兩個並列第一名的情況,這時候排在兩個第一名後面的人將是第三名,也就是沒有了第二名,但是有兩個第一名

spark.sql("select name, class, score, rank() over(order by score) rank from scores").show()                                                    
+----+-----+-----+----+
|name|class|score|rank|
+----+-----+-----+----+
|  a8|    3|   45|   1|
|  a9|    3|   55|   2|
|  a4|    2|   74|   3|
| a10|    3|   78|   4|
|  a2|    1|   78|   4|
|  a1|    1|   80|   6|
|  a5|    2|   92|   7|
|  a3|    1|   95|   8|
|  a6|    3|   99|   9|
|  a7|    3|   99|   9|
| a11|    3|  100|  11|
+----+-----+-----+----+

spark.sql("select name, class, score, rank() over(partition by class order by score) rank from scores").show()
+----+-----+-----+----+                                                        
|name|class|score|rank|
+----+-----+-----+----+
|  a2|    1|   78|   1|
|  a1|    1|   80|   2|
|  a3|    1|   95|   3|
|  a8|    3|   45|   1|
|  a9|    3|   55|   2|
| a10|    3|   78|   3|
|  a6|    3|   99|   4|
|  a7|    3|   99|   4|
| a11|    3|  100|   6|
|  a4|    2|   74|   1|
|  a5|    2|   92|   2|
+----+-----+-----+----+

3、DENSE_RANK連續排序

dense_rank() over(order by  score) as  dense_rank 表示按score 升序的方式來排序,並得出排序結果的排名號。

這個函數並列排名之後的排名是並列排名加1

簡單說每個人只有一種排名,然後出現兩個並列第一名的情況這時候排在兩個第一名後面的人將是第二名,也就是兩個第一名,一個第二名

spark.sql("select name, class, score, dense_rank() over(order by score) rank from scores").show()
+----+-----+-----+----+
|name|class|score|rank|
+----+-----+-----+----+
|  a8|    3|   45|   1|
|  a9|    3|   55|   2|
|  a4|    2|   74|   3|
|  a2|    1|   78|   4|
| a10|    3|   78|   4|
|  a1|    1|   80|   5|
|  a5|    2|   92|   6|
|  a3|    1|   95|   7|
|  a6|    3|   99|   8|
|  a7|    3|   99|   8|
| a11|    3|  100|   9|
+----+-----+-----+----+

spark.sql("select name, class, score, dense_rank() over(partition by class order by score) rank from scores").show()
+----+-----+-----+----+                                                        
|name|class|score|rank|
+----+-----+-----+----+
|  a2|    1|   78|   1|
|  a1|    1|   80|   2|
|  a3|    1|   95|   3|
|  a8|    3|   45|   1|
|  a9|    3|   55|   2|
| a10|    3|   78|   3|
|  a6|    3|   99|   4|
|  a7|    3|   99|   4|
| a11|    3|  100|   5|
|  a4|    2|   74|   1|
|  a5|    2|   92|   2|
+----+-----+-----+----+

4、NTILE分組排名[瞭解]

ntile(6) over(order by score)as ntile表示按 score 升序的方式來排序,然後 6 等分成 6 個組,並顯示所在組的序號。

spark.sql("select name, class, score, ntile(6) over(order by score) rank from scores").show()
+----+-----+-----+----+
|name|class|score|rank|
+----+-----+-----+----+
|  a8|    3|   45|   1|
|  a9|    3|   55|   1|
|  a4|    2|   74|   2|
|  a2|    1|   78|   2|
| a10|    3|   78|   3|
|  a1|    1|   80|   3|
|  a5|    2|   92|   4|
|  a3|    1|   95|   4|
|  a6|    3|   99|   5|
|  a7|    3|   99|   5|
| a11|    3|  100|   6|
+----+-----+-----+----+

spark.sql("select name, class, score, ntile(6) over(partition by class order by score) rank from scores").show()
+----+-----+-----+----+                                                        
|name|class|score|rank|
+----+-----+-----+----+
|  a2|    1|   78|   1|
|  a1|    1|   80|   2|
|  a3|    1|   95|   3|
|  a8|    3|   45|   1|
|  a9|    3|   55|   2|
| a10|    3|   78|   3|
|  a6|    3|   99|   4|
|  a7|    3|   99|   5|
| a11|    3|  100|   6|
|  a4|    2|   74|   1|
|  a5|    2|   92|   2|
+----+-----+-----+----+

四、Spark-On-Hive

官網:http://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html

Configuration of Hive is done by placing your hive-site.xml, core-site.xml (for security configuration), and hdfs-site.xml (for HDFS configuration) file in conf/.

Hive查詢流程及原理

執行HQL時,先到MySQL元數據庫中查找描述信息,然後解析HQL並根據描述信息生成MR任務

Hive將SQL轉成MapReduce執行速度慢

 

使用SparkSQL整合Hive其實就是SparkSQL去加載Hive 的元數據庫,然後通過SparkSQL執行引擎去操作Hive表內的數據

所以首先需要開啓Hive的元數據庫服務,讓SparkSQL能夠加載元數據

1、Hive開啓MetaStore服務

1: 修改 hive/conf/hive-site.xml 新增如下配置

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <property>
      <name>hive.metastore.warehouse.dir</name>
      <value>/user/hive/warehouse</value>
    </property>
    <property>
      <name>hive.metastore.local</name>
      <value>false</value>
    </property>
    <property>
      <name>hive.metastore.uris</name>
      <value>thrift://node01:9083</value>
    </property>
 </configuration>

 2: 後臺啓動 Hive MetaStore服務

nohup /export/servers/hive/bin/hive --service metastore 2>&1 >> /var/log.log &

2、SparkSQL整合Hive MetaStore

Spark 有一個內置的 MateStore,使用 Derby 嵌入式數據庫保存數據,但是這種方式不適合生產環境,因爲這種模式同一時間只能有一個 SparkSession 使用,所以生產環境更推薦使用 Hive 的 MetaStore

SparkSQL 整合 Hive 的 MetaStore 主要思路就是要通過配置能夠訪問它, 並且能夠使用 HDFS 保存 WareHouse,所以可以直接拷貝 Hadoop 和 Hive 的配置文件到 Spark 的配置目錄

hive-site.xml 元數據倉庫的位置等信息

core-site.xml 安全相關的配置

hdfs-site.xml HDFS 相關的配置

使用IDEA本地測試直接把以上配置文件放在resources目錄即可

3、使用SparkSQL操作Hive表

import org.apache.spark.sql.SparkSession

object HiveSupport {

  def main(args: Array[String]): Unit = {
    //創建sparkSession
    val spark = SparkSession
      .builder()
      .appName("HiveSupport")
      .master("local[*]")
      //.config("spark.sql.warehouse.dir", "hdfs://node01:8020/user/hive/warehouse")
      //.config("hive.metastore.uris", "thrift://node01:9083")
      .enableHiveSupport()//開啓hive語法的支持
      .getOrCreate()
    spark.sparkContext.setLogLevel("WARN")

    //查看有哪些表
    spark.sql("show tables").show()
  
    //創建表
    spark.sql("CREATE TABLE person (id int, name string, age int) row format delimited fields terminated by ' '")
  
    //加載數據,數據爲當前SparkDemo項目目錄下的person.txt(src平級)
    spark.sql("LOAD DATA LOCAL INPATH 'SparkDemo/person.txt' INTO TABLE person")

    //查詢數據
    spark.sql("select * from person ").show()

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