Spark SQL

一.SparkSql

  • SparkSQL 可以简化 RDD 的开发,提高开发效率.提供了 2 个编程抽象,类似 Spark Core 中的 RDD
    ➢ DataFrame
    ➢ DataSet

1.SparkSQL 特点

➢ 易整合
无缝的整合了 SQL 查询和 Spark 编程
➢ 统一的数据访问
使用相同的方式连接不同的数据源
➢ 兼容 Hive
在已有的仓库上直接运行 SQL 或者 HiveQL
➢ 标准数据连接
通过 JDBC 或者 ODBC 来连接

DataFrame和DataSet

DataFrame
    DataFrame也是一种基于RDD的分布式数据集, 与RDD的区别在于DataFrame中有数据的原信息
    DataFrame可以理解为传统数据库中的一张二维表格,每一列都有列名和类型
    
 ➢ DataSet
    DataSet也是分布式数据集,DataFrame的一个扩展,相当于传统JDBC中的ResultSet

2.SparkSQL 核心编程

新的起点

SparkCore中需要创建上下文环境SparkContextSparkSqlSparkCore的封装, 不仅仅是功能上的封装,上下文件环境也封装了
        老版本中称为 SQLContext 用于Spark自己的查询 和 HiveContext 用于Hive连接的查询
        新版本中称为 SparkSessionSQLContextHiveContext的组成 , 所以他们的API是通用的
        同时 SparkSession也可以直接获取到SparkContext对象

在这里插入图片描述

3.DataFrame的创建和使用

    三个概念:
        数据:  RDD中只关心数据  比如: (1,"jack",20)  并不关心每个字段的汉字
        结构:  DataFrame关心 数据+结构  比如: {"id":1,"name":"jack","age":20} 关心每个字段数据的类型
        类型: DataSet关系 数据+结构+类型 比如: DataSet[Person] Person是我们定义好的类, 既有类型+字段+数据
3.1 创建DataFrame
➢ 从数据源中创建
    scala> var df = spark.read.json("data/info.json")
            df: org.apache.spark.sql.DataFrame = [ age: bigint , id: bigint ]
➢ 从RDD中转换(后续章节补充)
➢ 从Hive Table查询返回(后续章节补充)

在这里插入图片描述

3.2 使用DataFrame
使用DataFrame有两个方式,分别是 SQL语法和DSL语法

➢ SQL语法
    1. 通过 "临时视图" 来使用,所以先创建视图
    2. 通过 sparkSession对象执行sql进行数据查询
    scala> df.createOrReplaceTempView("user")  //创建临时视图
    scala> var viewdf = spark.sql("select id,name,age from user") //通过spark执行sql
    viewdf: org.apache.spark.sql.DataFrame = [id: bigint, name: string] //执行sql返回的还是DF
    scala> viewdf.show  //展示DF中的数据
    scala> spark.sql("select id,name,age from user").show  //也可以直接查询sql并展示
    +---+-----+---+
    | id| name|age|
    +---+-----+---+
    |  1|jack1| 18|
    |  2|jack2| 28|
    |  3|jack3| 38|
    +---+-----+---+
    
    注意: 
        df.createOrReplaceTempView 只能创建当前会话有效的临时视图
        df.createOrReplaceGlobalTempView 能创建所有会话都有效的临时视图
        使用时 需要在视图名前面加上  global_temp.视图名
        
➢ DSL语法
    DSL称为 Domain-Specific Language 特定领域语言
    这是 DataFrame中管理结构化数据的API ,通过DataFrame就可以调用这些API

    scala> df.printSchema
            root
             |-- age: long (nullable = true)
             |-- id: long (nullable = true)
             |-- name: string (nullable = true)
    scala> df.select("name")
            res20: org.apache.spark.sql.DataFrame = [name: string]
    //基本查询
    scala> df.select("name").show
            +-----+
            | name|
            +-----+
            |jack1|
            |jack2|
            |jack3|
            +-----+
   //列运算         
    scala> df.select($"age" + 1).show
    scala> df.select('age + 1).show
            +---------+
            |(age + 1)|
            +---------+
             |       19|
             |       29|
             |       39|
            +---------+
            
    //取别名
    scala> df.select('name,'age + 1 as "aa").show
            +-----+---+
            | name| aa|
            +-----+---+
            |jack1| 19|
            |jack2| 29|
            |jack3| 39|
            +-----+---+    
    //统计函数
    scala> df.select(avg("age") as "平均年龄").show
            +--------+
            |平均年龄|
            +--------+
            |    48.0|
            +--------+
    //条件过滤
    scala> df.filter('age > 25).show
            +---+---+-----+
            |age| id| name|
            +---+---+-----+
            | 28|  2|jack2|
            | 38|  3|jack3|
            +---+---+-----+
    //组合+聚合函数
    scala> df.groupBy("id").count.show
            +---+-----+
            | id|count|
            +---+-----+
            |  1|    1|
            |  3|    1|
            |  2|    1|
            +---+-----+
    
3.3 DataFrame转换
➢ RDD 与 DF 转换需要导入 隐式函数
    import spark.implicits._
    这里的spark是 SparkSession的对象名,因此需要创建好SparkSession对象之后导入,并且该对象必须是val常量
    
    1. RDD ==> DF ,缺少结构,即字段名
        scala> var rdd = spark.sparkContext.makeRDD(List(1,2,3))
                rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[129] at makeRDD at <console>:23
        scala> var df = rdd.toDF("id")
                df: org.apache.spark.sql.DataFrame = [id: int]
        scala> df.show
                +---+
                | id|
                +---+
                |  1|
                |  2|
                |  3|
                +---+
    2. DF ===> RDD DF内部封装了RDD 直接获取即可
            删除结构后,DF中每一行 就会变成一个Row对象
            通过Row对象的get(index) 或者 getAs[Type](index)方法获取Row对象中的数据
            
    scala> var df = spark.read.json("data/info.json");
            df: org.apache.spark.sql.DataFrame = [age: bigint, id: bigint]
    scala> var rdd = df.rdd
            rdd: org.apache.spark.rdd.RDD[org.apache.spark.sql.Row] 
    scala> var arr = rdd.collect
            arr: Array[org.apache.spark.sql.Row] = Array([18,1,jack1], [28,2,jack2], [38,3,jack3])
    scala> arr(0)
            res62: org.apache.spark.sql.Row = [18,1,jack1]
    scala> arr(0).get(0)
            res63: Any = 18
    scala> arr(0).getAs[String](2)
            res67: String = jack1         
3.4 DataSet
  • DataSet 即有数据,又有结构,也有类型, DataFrame其实一个特殊的DataSet,其类型是DataSet[Row]
➢ 通过Seq或者List 可以把集合直接转成DS
    1. 通过基本类型的集合
     scala> var ds = List(1,2,3).toDS
            ds: org.apache.spark.sql.Dataset[Int] = [value: int]

    scala> var ds = List(1.1,2.2).toDS
            ds: org.apache.spark.sql.Dataset[Double] = [value: double]
    
    2.通过已定义类型的集合
    scala> case class User(age:Int,name:String)
            defined class User

    scala> var ds = List(User(10,"jack"),User(20,"rose")).toDS
            ds: org.apache.spark.sql.Dataset[User] = [age: int, name: string]
3.5 DataSet的转换
    在实际使用的时候,很少用到把序列转换成DataSet,更多的是通过RDD来得到DataSet
    
    1. RDD ==> DS  缺少结构和类型
        a. RDD 转DS 我们一般可以映射成 具体具体类型的RDD之后再转DS 
    scala> case class User(name:String, age:Int)
            defined class User
    scala> sc.makeRDD(List(("zhangsan",30), ("lisi",49))).map(t=>User(t._1,t._2)).toDS
            res11: org.apache.spark.sql.Dataset[User] = [name: string, age: int]
        b. 也可以直接ToDS
    scala> sc.makeRDD(List(("zhangsan",30), ("lisi",49))).toDS
            res11: org.apache.spark.sql.Dataset[(String,Int)] = [_1: String, _2: Int]
    
    2.DS ==> RDD  DS内部封装了RDD 直接获取即可,且获取出来的RDD也是带有类型的
    scala> var ds = List(User("aa",11),User("bb",22)).toDS
            ds: org.apache.spark.sql.Dataset[User] = [name: string, age: int]
    scala> var rdd = ds.rdd
            rdd: org.apache.spark.rdd.RDD[User]
3.5 DataFrame和DataSet的转换
DataFrame ==> DataSet  需要一个类型    
    scala> case class User(name:String, age:Int)
            defined class User
    scala> val df = sc.makeRDD(List(("zhangsan",30),("lisi",49))).toDF("name","age")
            df: org.apache.spark.sql.DataFrame = [name: string, age: int]
    scala> val ds = df.as[User]
            ds: org.apache.spark.sql.Dataset[User] = [name: string, age: int]DataSet ==> DataFrame  删除类型 即变成 DataSet[Row]
    scala> val df = ds.toDF
            df: org.apache.spark.sql.DataFrame = [name: string, age: int]
3.6 RDD、DataFrame、DataSet
    DF ==rdd==> RDD [ ROW ] 
    DS ==rdd==> RDD [ Type ]
    转rdd 如果是DF 那么泛型是ROW 如果是DS泛型就是DS的泛型
    
    RDD ==toDF==> DF [ ROW ]
    DS   ==toDF==> DF [ ROW ]
    转DF 无论如何DF没有类型 所以都是ROW

    DF   ==as[Type]==> DS[ Type ]
    RDD ====> DS [ RDD的泛型 ]
    转DS 如果是DF 那么泛型是Type 如果是RDD泛型就是RDD的泛型

4. IDEA 开发 SparkSQL

object Spark_SQL_Start {
	def main(args: Array[String]): Unit = {
			//1.创建SparkSession
		val spark: SparkSession = SparkSession.builder()
			.config(new SparkConf().setMaster("local[*]")
				.setAppName("start01")).getOrCreate()
		import spark.implicits._
		//2.DF的创建和使用
		val df: DataFrame = spark.read.json("datas/info.json").cache()
		//SQL
		df.createOrReplaceTempView("User")
		spark.sql("select * from User").show()
		//DSL
		df.select("name").show()
		df.groupBy("id").count().show()
		println("-----------------------")
		//3.DF ==> DS  DF ==> RDD
		val ds: Dataset[User] = df.as[User]
		ds.show()
		val rdd: RDD[Row] = df.rdd
		rdd.collect().foreach(println)
		println("-----------------------")
		//4.RDD ==> DF  RDD ==> DS
		val rdd1: RDD[(Int, String)] = spark.sparkContext.makeRDD(Seq((10,"tom"),(20,"jack")))
		var df1: DataFrame = rdd1.toDF("id","name")
		df1.show()
		val ds1: Dataset[User] = rdd1.map(t=>User(t._1,t._2)).toDS()
		ds1.show()
		println("-----------------------")
		//5.DS==>RDD  DS==>DF
		val rdd2: RDD[User] = ds1.rdd
		rdd2.collect().foreach(println)
		val df2: DataFrame = ds1.toDF()
		df2.show()
		//6.关闭
		spark.close()
	}
	case class User(id:Long,name:String)
}

5. UDF

  • 执行spark.sql时 可以使用用户自定义函数,实现自己想要的功能
  • 通过spark.udf.register注册函数即可使用
object Spark_Sql_UDF {
	def main(args: Array[String]): Unit = {
		val spark: SparkSession = SparkSession.builder()
        .config(new SparkConf().setAppName("UDF").setMaster("local[*]")).getOrCreate()
		import spark.implicits._
		//注册用户自定义函数
		spark.udf.register("getWithName",(x)=>{"Name:"+x})
		//创建DF
		val df: DataFrame = spark.read.format("json").load("datas/info.json")
		//创建临时表 
		df.createOrReplaceTempView("user")
		//使用sql自定义函数
		spark.sql("select getWithName(name),id from user").show()
		spark.close()
	}
}
➢ info.json:
    {"id": 10,"name": "jack"}
    {"id": 20,"name": "rose"}
    {"id": 30,"name": "tom"}
➢ console:
    |getWithName(name)| id|
    +-----------------+---+
    |        Name:jack| 10|
    |        Name:rose| 20|
    |         Name:tom| 30|
    +-----------------+---+

5. UDAF

  • 用户自定义聚合函数
  • 通过
    • 继承 UserDefinedAggregateFunction 弱类型的聚合函数 ( Spark3.0之前 )
    • 继承 Aggregator 强类型的聚合函数 ( Spark3.0 )
    需求: 自定义求平均值函数 avgAge
    
➢ 通过RDD实现
        val rdd: RDD[(String, Int)] = spark.sparkContext.makeRDD(Seq(("jack", 10), ("rose", 20), ("tom", 30)))
        val ageOneRdd: RDD[(Int, Int)] = rdd.map { case (_, age) => (age, 1) }
        val ageCount: (Int, Int) = ageOneRdd.reduce((t1, t2) => (t1._1 + t2._1, t1._2 + t2._2))
        println(ageCount._1 / ageCount._2)
➢ 通过累加器实现
        val ageAcc = new AgeAccumulator
        spark.sparkContext.register(ageAcc)
        val rdd: RDD[(String, Int)] = spark.sparkContext.makeRDD(Seq(("jack", 10), ("rose", 20), ("tom", 30)))
        rdd.foreach { case (_, age) => ageAcc.add(age) }
        val ageCount: (Int, Int) = ageAcc.value
        println(ageCount._1 / ageCount._2)
        
class AgeAccumulator extends AccumulatorV2[Int, (Int, Int)] {
		private var ageSum: Int = 0
		private var ageCnt: Int = 0
		override def isZero: Boolean = ageSum == 0 && ageCnt == 0
		override def copy(): AccumulatorV2[Int, (Int, Int)] = new AgeAccumulator
		override def reset(): Unit = {
			ageSum = 0
			ageCnt = 0
		}
		override def add(age: Int): Unit = {
			ageSum += age
			ageCnt += 1
		}
		override def merge(other: AccumulatorV2[Int, (Int, Int)]): Unit = {
			ageSum += other.value._1
			ageCnt += other.value._2
		}
		override def value: (Int, Int) = (ageSum, ageCnt)
	}

➢ 通过 继承 UDAF (Spark3.0之前) 抽象类实现自定义聚合函数
		//创建DF
		val rdd: RDD[(String, Int)] = spark.sparkContext.makeRDD(Seq(("jack", 10), ("rose", 20), ("tom", 30)))
		val df: DataFrame = rdd.toDF("name", "age")
		//创建自定义集合函数对象
		val ageUDAF = new AgeUDAF
		//注册UDAF
		spark.udf.register("ageAVG", ageUDAF)
		//创建临时表
		df.createOrReplaceTempView("user")
		//执行sql
		spark.sql("select ageAVG(age) from user").show()
        
	class AgeUDAF extends UserDefinedAggregateFunction {
		//聚合函数输入的数据类型
		override def inputSchema: StructType = {
			StructType(
				Array(
					StructField("age", IntegerType)
				)
			)
		}
		//计算过程的缓冲区
		override def bufferSchema: StructType = {
			StructType(
				Array(
					StructField("ageSum", LongType),
					StructField("ageCnt", LongType)
				)
			)
		}
		//聚合函数返回值类型
		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.getInt(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): Double = buffer.getLong(0).toDouble / buffer.getLong(1)
	}        

➢ 通过继承 Aggregate (Spark3.0) 自定义强类型聚合函数
		//创建强类型UDAF对象
		val ageAggr = new AgeAggregator
		//注册强类型的UDAF,需要使用functions.udaf进行函数转换
		spark.udf.register("ageAggr", functions.udaf(ageAggr))
		//创建rdd
		val rdd: RDD[(String, Int)] = spark.sparkContext.makeRDD(Seq(("jack", 10), ("rose", 20), ("tom", 30)))
		//转成DF
		val df: DataFrame = rdd.toDF("name", "age")
		//创建临时表
		df.createOrReplaceTempView("user")
		//执行sql 直接使用聚合函数
		spark.sql("select ageAggr(age) from user").show()
        
	class AgeAggregator extends Aggregator[Int, (Long, Long), Double] {
		//缓冲区 初始值
		override def zero: (Long, Long) = (0, 0)
		//输入age到缓冲区计算
		override def reduce(buff: (Long, Long), age: Int): (Long, Long) = {
			(buff._1 + age, buff._2 + 1)
		}
		//合并多个缓冲区
		override def merge(buff1: (Long, Long), buff2: (Long, Long)): (Long, Long) = {
			(buff1._1 + buff2._1, buff1._2 + buff2._2)
		}
		//计算结果
		override def finish(buff: (Long, Long)): Double = {
			buff._1.toDouble / buff._2
		}
		//输入编码,自定义对象Encoders.product  其他Encoders.scalaXxx
		override def bufferEncoder: Encoder[(Long, Long)] = Encoders.tuple(Encoders.scalaLong, Encoders.scalaLong)
		//输出编码,自定义对象Encoders.product  其他Encoders.scalaXxx
		override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
	}

6. 数据的加载和保存

通用的加载和保存方式

SparkSQL 提供了通用的保存数据和加载数据的方式. 默认的保存和加载数据的格式都是parquet
    scala> spark.read.load( "parquet path" ) 和 df.write.save( "parquet path" )

我们可以通过设置不同的参数,来指定不同的数据源格式,读取和保存数据均可
    scala>  spark.read.format( "json" ) [ .option( "key","value" ) ] .load( "filepath" )
    
➢ format( "..." )  指定读取数据源的格式, 包括 "csv""jdbc""json""orc""parquet""textFile"。

➢ option( "key","value" )  如果format是jdbc, 那么使用多个option传递JDBC参数

➢ 也有一些简化的方法,用于特定的文件读取,从而省略format调用
    spark.read.json("json file path") spark.read.cvs("cvs file path")
    scala> spark.read.
                csv      jdbc   load     options   parquet   table   textFile   
                format   json   option   orc       schema    text 
    
保存操作:
    df.write.mode("SaveMode String").save("parquet file path")
    除了和读取操作一样的参数之外, 另有一个模式,表示保存时的状态
    SaveMode.ErrorIfExists(default) ==> "error"(默认的) 如果文件已经存在则抛出异常
    SaveMode.Append  ==> "append" 如果文件已经存在则追加(会有多个文件生成)
    SaveMode.Overwrite  ==> "overwrite" 如果文件已经存在则覆盖(把以前的文件删除)
    SaveMode.Ignore  ==> "ignore" 如果文件已经存在则忽略(不生成新文件,保留原来的文件 )
    
    scala> df.write.mode("append").json("/output")

7. MySQL数据读取和写入

➢ spark-shell 命令行连接

    1. 添加mysql驱动到spark的jars目录下
    2. scala> val jdbcDF = spark.read.format("jdbc").options(Map("url" -> "jdbc:mysql://localhost:3306/mysql", "driver" -> "com.mysql.jdbc.Driver", "dbtable" -> "plugin", "user" -> "root", "password" -> "1234")).load()

➢ scala代码连接

    1.导入mysql的驱动依赖
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.27</version>
    </dependency>
    2.读取mysql数据
    spark.read.format("jdbc").options(Map(
        "url" -> "jdbc:mysql://192.168.189.90:3306/mysql",
        "driver" -> "com.mysql.cj.jdbc.Driver",
        "user" -> "root",
        "password" -> "1234",
        "dbtable" -> "help_topic"
    )).load().show()

    spark.read.format("jdbc")
        .option("url", "jdbc:mysql://192.168.189.90:3306/mysql")
        .option("driver", "com.mysql.cj.jdbc.Driver")
        .option("user", "root")
        .option("password", "1234")
        .option("dbtable", "plugin")
        .load().show

    val props: Properties = new Properties()
    props.setProperty("user", "root")
    props.setProperty("password", "1234")
    spark.read.jdbc("jdbc:mysql://node0:3306/mysql",
        "plugin", props).show()
    
    3.写入数据到mysql
    val df: DataFrame = spark.sparkContext.makeRDD(List(1,2,3)).toDF("id")
    df.write.format("jdbc").options(Map(
            "url" -> "jdbc:mysql://192.168.189.90:3306/test",
            "driver" -> "com.mysql.cj.jdbc.Driver",
            "user" -> "root",
            "password" -> "1234",
            "dbtable" -> "ids"
        )).mode(SaveMode.Append)  //默认的模式是 表存在则报错,这里指定追加 表存在不会保存
        .save()

8.Spark连接Hive

  • Apache Hive 可以理解为一个Hadoop上的SQL引擎数据库
  • SparkSQL在编译时可以加入Hive的支持也可以不加入(我们下载的Hive二进制包是包含Hive支持的)
    • 那么就可以 访问Hive表、UDF (用户自定义函数) 以及 Hive 查询语言(HiveQL/HQL)等
    • 由于Spark包含了Hive 所以连接Hive有两种方式,
      • 一是连接自带的内嵌Hive(不需要任何配置)
      • 二是连接外部的Hive 需要简单的配置
1.连接内嵌的Hive
    连接内嵌Hive什么都不需要做, 默认使用derby作为元数据库,使用本地文件系统作为数据仓库
    执行两个命令:
         //执行查看数据库sql, 会自动在 $spark_home下生成metastore_db元数据库信息
        scala> spark.sql("show tables").show
        //执行创建表操作,或者插入数据操作,会自动生成并在$spark_home/spark-warehouse 存储数据
        scala> spark.sql("create table test(id int)")
        spark.sql("insert into test values(1),(2)")
2.连接外部的Hive
  • Spark 使用hive 数据仓库,
    • 将hive-site.xml文件 拷贝到spark的conf目录下
    • 添加mysql驱动包到spark的jars目录下
    • 如果spark访问不到hdfs, 需要将core-site.xml和hdfs-site.xml也拷贝到spark的conf目录下
      [经测试是不需要拷贝这两个文件的,也不知道spark是如何找到hadoop的,hive是通过$HADOOP_HOME找到的]
    • 如果hive-site.xml配置了元数据服务器,需要启动元数据服务器
    • 重写启动spark-shell即可自动切换到hive数据仓库
scala> spark.sql("show tables").show
        +--------+--------------------+-----------+
        |database|           tableName|isTemporary|
        +--------+--------------------+-----------+
        | default|                  aa|      false|
        | default|         live_events|      false|
        | default|        login_events|      false|
        | default|order_amount_by_p...|      false|
        | default|        order_detail|      false|
        | default|    page_view_events|      false|
        | default|      payment_detail|      false|
        | default|        product_info|      false|
        | default|      promotion_info|      false|
        | default|       province_info|      false|
        +--------+--------------------+-----------+
2.使用Spark SQL CLI
    确保Spark可以连接外部的hive之后,.就可以是用spark-sql直接连接hive进行操作
    [zhyp@node0 spark-local]$ bin/spark-sql
    spark-sql> show tables;
                    default aa      false
                    default live_events     false
                    default login_events    false
                    default order_amount_by_province        false
                    default order_detail    false
                    default page_view_events        false
                    default payment_detail  false
                    default product_info    false
                    default promotion_info  false
                    default province_info   false
                    Time taken: 2.433 seconds, Fetched 10 row(s)
2.使用Spark Beeline
  • Spark Thrift Server 是 基于HiveServer2的另外一个实现 , 完全兼容HiveServer2 并且都是一样的协议和端口
    我们使用Spark Thrift Server 取代HiveServer2 和 Hive的metastore服务交互获取元数据
  • Spark Beeline 连接 Spark thrift server的步骤
    • 将hive-site.xml文件 拷贝到spark的conf目录下
    • 如果hive-site.xml配置了元数据服务器,需要启动元数据服务器
      • [如果不是用元数据服务器,需要在jars中加入mysql驱动和配置文件中添加连接数据库四大要素]
    • 启动Spark thrift server 使用beeline连接即可
    [zhyp@node0 spark-local]$ sbin/start-thriftserver.sh
    [zhyp@node0 spark-local]$ bin/beeline -u jdbc:hive2://linux1:10000 -n zhyp
    或者
    [zhyp@node0 spark-local]$ bin/beeline
    beeline> !connect jdbc:hive2://node0:10000
3.代码连接Hive
    def main(args: Array[String]): Unit = {
		//创建 SparkSession
		System.setProperty("HADOOP_USER_NAME", "zhyp")
		val spark: SparkSession = SparkSession
			.builder()
			.enableHiveSupport()  //添加Hive支持,默认是不支持连接Hive,开启后会读取classpath下的hive-site.xml
          .config("spark.sql.warehouse.dir", "hdfs://node0:8020/user/hive/warehouse") //通过spark创建数据库需要写这个地址,因为新建的数据库默认是在本地路径中找/user/hive/warehouse
			.master("local[*]")
			.appName("sql")
			.getOrCreate()
		import spark.implicits._

		spark.sql("create table abc(id int)").show()
		spark.sql("insert into abc values(1),(111)").show()
		spark.sql("show tables").show()
		spark.sql("select * from abc").show()

		spark.close()
	}
代码练习
package com.zhyp.spark.sql.start
import org.apache.spark.SparkConf
import org.apache.spark.sql.{Dataset, SparkSession}
object Spark_SQL_Test01{
	def main(args: Array[String]): Unit = {
		System.setProperty("HADOOP_USER_NAME", "zhyp")
		//1.创建SparkSession
		val spark: SparkSession = SparkSession.builder().enableHiveSupport()
			.config("spark.sql.warehouse.dir","hdfs://node0:8020/user/hive/warehouse")
			.config(new SparkConf().setMaster("local[*]")
				.setAppName("start01")).getOrCreate()
		import spark.implicits._
		spark.sql("create database test")
		spark.sql("use test")
		spark.sql(
			"""
				|CREATE TABLE `user_visit_action`(
				| `date` string,
				| `user_id` bigint,
				| `session_id` string,
				| `page_id` bigint,
				| `action_time` string,
				| `search_keyword` string,
				| `click_category_id` bigint,
				| `click_product_id` bigint,
				| `order_category_ids` string,
				| `order_product_ids` string,
				| `pay_category_ids` string,
				| `pay_product_ids` string,
				| `city_id` bigint)
				|row format delimited fields terminated by '\t';
			""".stripMargin)
		spark.sql(
			"""
				|load data local inpath 'datas/user_visit_action.txt' into table
				|user_visit_action
			""".stripMargin)
		spark.sql(
			"""
				|CREATE TABLE `product_info`(
				| `product_id` bigint,
				| `product_name` string,
				| `extend_info` string)
				|row format delimited fields terminated by '\t'
			""".stripMargin)
		spark.sql(
			"""
				|load data local inpath 'datas/product_info.txt' into table product_info
			""".stripMargin)
		spark.sql(
			"""
				|CREATE TABLE `city_info`(
				| `city_id` bigint,
				| `city_name` string,
				| `area` string)
				|row format delimited fields terminated by '\t'
			""".stripMargin)
		spark.sql(
			"""
				|load data local inpath 'datas/city_info.txt' into table city_info
			""".stripMargin)

		spark.close()
	}
}


package com.zhyp.spark.sql.start
import org.apache.spark.SparkConf
import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.{Encoder, Encoders, SparkSession, functions}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
object Spark_SQL_Test03 {
	def main(args: Array[String]): Unit = {
		System.setProperty("HADOOP_USER_NAME", "zhyp")
		//1.创建SparkSession
		val spark: SparkSession = SparkSession.builder().enableHiveSupport()
			.config("spark.sql.warehouse.dir", "hdfs://node0:8020/user/hive/warehouse")
			.config(new SparkConf().setMaster("local[*]")
				.setAppName("start01")).getOrCreate()
		//2.执行
		spark.udf.register("cityRemark", functions.udaf(new CityRemarkUDAF))
		spark.sql("use test")
		//1.关联三张表 过滤非点击数据
		spark.sql(
			"""
				|select
				|c.area,c.city_name,p.product_name
				|from
				|user_visit_action a
				|join
				|city_info c
				|on a.city_id = c.city_id
				|join
				|product_info p
				|on a.click_product_id = p.product_id
				|where a.click_product_id > -1
			""".stripMargin).createOrReplaceTempView("t1")

		//2.分组 按照地区和商品分组
		spark.sql(
			"""
				|select
				|area,product_name,
				|cityRemark(city_name) city_remark,
				|count(*) clickCnt
				|from t1
				|group by area,product_name
			""".stripMargin).createOrReplaceTempView("t2")

		//3.对 同一个地区的各种商品点击量排名
		spark.sql(
			"""
				|select
				|*,
				|rank() over(partition by area order by clickCnt desc) rank
				|from t2
			""".stripMargin).createOrReplaceTempView("t3")

		//4.取各区域的前三名
		spark.sql(
			"""
				|select
				|*
				|from t3
				|where rank <= 3
			""".stripMargin).show()
	}
	case class CityAndCntBuff(map:mutable.Map[String,Long])
	class CityRemarkUDAF extends Aggregator[String, CityAndCntBuff, String] {
		override def zero: CityAndCntBuff = CityAndCntBuff(mutable.Map())
		override def reduce(buff: CityAndCntBuff, city: String): CityAndCntBuff = {
			val map: mutable.Map[String, Long] = buff.map
			map.update(city, map.getOrElse(city, 0L) + 1L)
			buff
		}
		override def merge(buff1: CityAndCntBuff, buff2: CityAndCntBuff): CityAndCntBuff = {
			var map1 = buff1.map;
			var map2 = buff2.map
			map2.foreach({
				case (city, cnt) => {
					map1.update(city, map1.getOrElse(city, 0L) + cnt)
				}
			})
			buff1
		}
		override def finish(resultBuff: CityAndCntBuff): String = {
			val resultMap: mutable.Map[String, Long] = resultBuff.map
			val totalCnt: Long = resultMap.values.reduce(_ + _)
			val top2: List[(String, Long)] = resultMap.toList.sortBy(_._2)(Ordering.Long.reverse).take(2)
			val cityBuffer = new ListBuffer[String]
			var percentSum = 0L
			top2.foreach({
				case (city, cnt) => {
					val percent: Long = cnt * 100 / totalCnt
					cityBuffer.append(s"${city} ${percent}%")
					percentSum += percent
				}
			})
			if (resultMap.size > 2) {
				cityBuffer.append(s"其他 ${1 - percentSum}%")
			}
			cityBuffer.mkString(", ")
		}
		override def bufferEncoder: Encoder[CityAndCntBuff] = Encoders.product
		override def outputEncoder: Encoder[String] = Encoders.STRING
	}
}

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值