SparkSql笔记分享

1、SparkSql概述
    1、什么是SparkSql?
        SparkSql是一个处理结构化数据的spark组件。
    2、Sparksql的应用场景
        sparksql用于离线处理
    3、什么是DataFrame
        DataFrame就是类似一个mysql的表,有列名列类型
        DataFrame只关注列,不关注行[不管每一行保存的是什么类型,显示的都是Row类型]
    4、什么是DataSet
        DataSet就是类似一个mysql的表
        DataSet既关注列[有列名列类型],也关注行[每一行要求必须是固定类型]
        DataSet是强类型[比如DataSet[Car]要求每一行必须是Car类型],DataFrame是弱类型[不管每一行保存的是什么类型,显示的都是Row类型]
    5、RDD、DataFrame、DataSet关系
        RDD、DataFrame、DataSet:都是弹性分布式数据集
        RDD、DataFrame、DataSet: 都有分区
        RDD、DataFrame、DataSet: 都是中间结果优先放入内存,内存不足放入磁盘
        RDD、DataFrame、DataSet: 都是惰性,需要使用类似action算子触发计算
2、SparkSql编程
    show方法默认只显示20行,可以自定义设置    .show[(numRows)]
    1、sparksession的创建:
        1、val spark = new SparkSession.Builder().appName(..).master(..).getOrCreate()
        2、val spark = SparkSession.builder().appName(..).master(..).getOrCreate()
    2、DataFrame的创建
        1、通过toDF方法创建
            使用toDF方法必须导入隐式转换: import spark.implicits._
                集合.toDF()/集合.toDF(列名,...)
                rdd.toDF/rdd.toDF(列名,...)
                集合/rdd中元素如果是样例类,那么转成dataFrame的时候列名就是样例类的属性名
                集合/rdd中元素如果是元组,那么转成dataFrame的时候列名就是_N,此时可以使用有参的toDF方法重定义列名。
        2、通过读取文件创建
            spark.read.json/csv/parquet/orc/...
        3、通过其他DataFrame衍生
            val df:DataFrame = ...
            val df2:DataFrame = df.filter/distinct/where/select/selectExpr/...("...")
        4、通过createDataFrame方法创建
            val rdd:RDD[Row] = ...
            val fields = Array( StructFiled(列名,列类型),.... )
            val schema:StructType = StructType( fields )
            spark.createDataFrame(rdd,schema)
    3、DataSet的创建
        1、通过toDS方法创建
            使用toDS方法必须导入隐式转换: import spark.implicits._
                集合.toDS()
                rdd.toDS
                集合/rdd中元素如果是样例类,那么转成dataSet的时候列名就是样例类的属性名
                集合/rdd中元素如果是元组,那么转成dataSet的时候列名就是_N,此时可以使用有参的toDF方法重定义列名。然后再toDS。
        2、通过读取文件创建[只能读textfile]
            val ds:DataSet[String] = spark.read.textFile
        3、通过其他DataSet衍生
            val ds:DataSet = ...
            val ds2:DataSet = ds.map/flatMap/filter/...
        4、通过createDataSet方法创建
            val ds:DataSet[rdd元素类型] = spark.createDataSet(rdd)
    4、sparksql编程方式
        1、命令式[使用一些方法算子操作数据]
            1、过滤: filter("sql过滤条件")/where("sql过滤条件")
            2、列裁剪: selectExpr("列名","函数(列名) 别名",...)
            3、去重
                1、所有列都相同才会去重: distinct
                2、指定列相同就会去重: dropDumplicates("列名",...)    [保留第一个列]
        2、声明式[使用sql语句操作数据]
            1、将df/ds的数据集注册成表: 
                df/ds.createOrReplaceTempView("表名"): 此种方法注册的表只能在生成当前df/ds的sparksession中使用
                df/ds.createOrReplaceGlobalTempView("表名") : 此种方法注册的表可以在多个sparksession中使用,使用的时候必须通过 global_temp.表名 使用
            2、写sql操作表数据: spark.sql("sql语句")
    5、RDD、DataFrame、DataSet相互转换
        RDD转DataFrame: rdd.toDF()/rdd.toDF(列名,...)
        RDD转DataSet: rdd.toDS()
        DataFrame转RDD: val rdd:RDD[Row] = df.rdd
            row类型的取值:  row.getAs[列类型]("列名")
        DataSet转RDD: val rdd:RDD[DataSet行类型] = ds.rdd
        DataSet转DataFrame: ds.toDF()/ds.toDF(列名,...)
        DataFrame转DataSet: val ds:DataSet[行类型] = df.as[行类型]
            as中的行类型的泛型如果是样例类,那么样例类的属性名可以不写,如果写属性,要求属性名必须和列名一致,并且属性的个数不能大于列的个数。
                如:
                    case calss Man(name:String,age:Int)
                    case calss Man1(name1:String,age1:Int)
                    case calss Man2()
                    df:DF(name:String,age:Int,salary:Double)        
                    df.as(man)    //得到    name:String,age:Int,salary:Double
                    df.as(man1)    //报错
                    df.as(man2)    //得到    name:String,age:Int,salary:Double
            as中的行类型的泛型如果是元组,要求元组的元素个数必须和列的个数一致,并且元组第N个元素的类型必须和第N个列的类型一致。
                如:
                    df:DF(name:String,age:Int,salary:Double)        
                    df.as[(String,Int,Double)]    //得到    name:String,age:Int,salary:Double
            [推荐写元组]
    6、自定义函数
        1、自定义UDF函数
            1、定义一个函数
            2、注册成udf函数: spark.udf.register("函数名",自定义函数对象)
            3、使用
        2、自定义UDAF函数
            1、自定义弱类型UDAF函数
                1、弱类型UDAF定义
                    1、创建class继承UserDefinedAggregateFunction
                    2、重写抽象方法
                        1、指定自定义UDAF函数参数的类型
                        2、指定中间变量的数据类型
                        3、UDAF函数最终结果类型
                        4、一致性
                        5、初始化中间变量
                        6、combiner计算
                        7、reducer计算
                        8、返回最终结果
                2、弱类型UDAF的使用
                    1、创建弱类型UDAF对象
                    2、对象注册: spark.udf.register("函数名",弱类型UDAF对象)
                    3、使用spark.sql中写sql使用
                3、代码
                class UserDefinedWeakUDAF extends UserDefinedAggregateFunction{
                  /**
                    * 指定自定义UDAF函数参数的类型
                    * @return
                    */
                  override def inputSchema: StructType = {

                    //StructType( Array( StructField("age",IntegerType) ) )
                    new StructType()
                      .add("age1",IntegerType)
                  }

                  /**
                    *指定中间变量的数据类型[ sum, count]
                    * @return
                    */
                  override def bufferSchema: StructType = {
                    //StructType( Array( StructField("sum",IntegerType) , StructField("count",IntegerType) )  )
                    new StructType()
                      .add("sum",IntegerType)
                      .add("count",IntegerType)
                  }

                  /**
                    * UDAF函数最终结果类型
                    * @return
                    */
                  override def dataType: DataType = DoubleType

                  /**
                    * 一致性
                    * @return
                    */
                  override def deterministic: Boolean = true

                  /**
                    * 初始化中间变量
                    * @param buffer
                    */
                  override def initialize(buffer: MutableAggregationBuffer): Unit = {
                    //初始化sum
                    buffer(0) = 0

                    //初始化count
                    buffer(1) = 0
                  }

                  /**
                    * combiner计算
                    * @param buffer
                    * @param input
                    */
                  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
                    println(s"update ${Thread.currentThread().getName} buffer=${buffer}  input=${input}")
                    //更新中间结果变量sum  = sum + age
                    buffer(0) = buffer.getAs[Int](0) + input.getAs[Int](0)

                    //更新中间结果变量 count = count + 1
                    buffer(1) = buffer.getAs[Int](1) + 1
                  }

                  /**
                    * reducer计算
                    * @param buffer1
                    * @param buffer2
                    */
                  override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
                    println(s"merge ${Thread.currentThread().getName} buffer1=${buffer1}  buffer2=${buffer2}")
                    // sum = sum + 分区的sum
                    buffer1(0) = buffer1.getAs[Int](0) + buffer2.getAs[Int](0)
                    // count = count + 分区的count
                    buffer1(1) = buffer1.getAs[Int](1) + buffer2.getAs[Int](1)
                  }

                  /**
                    * 返回最终结果
                    * @param buffer
                    * @return
                    */
                  override def evaluate(buffer: Row): Any = {
                    buffer.getAs[Int](0).toDouble / buffer.getAs[Int](1)
                  }
                }
            2、自定义强类型UDAF函数
                1、定义
                    1、定义一个class继承Aggregator[IN,BUFF,OUT]
                        IN: UDAF函数参数类型
                        BUFF: UDAF函数中间结果类型
                        OUT: UDAF函数结果类型
                    2、重写抽象方法
                        1、中间变量赋予初始值
                        2、combiner计算
                        3、reducer计算
                        4、计算最终结果
                        5、指定中间变量的编码格式
                        6、最终结果的编码格式
                2、使用
                    1、创建一个自定义UDAF对象
                    2、udaf函数转换: 
                        import org.apache.spark.sql.function._
                        udaf(自定义udaf对象)
                    3、注册: spark.udf.register(函数名,udaf函数转换结果)
                    4、使用spark.sql中写sql使用
                3、代码
                case class AvgBuff(sum:Int,count:Int)

                class UserDefinedStrongUDAF extends Aggregator[Int,AvgBuff,Double]{
                  /**
                    * 中间变量赋予初始值
                    * @return
                    */
                  override def zero: AvgBuff = {
                    AvgBuff(sum=0,count=0)
                  }

                  /**
                    * combiner计算
                    * @param b
                    * @param a
                    * @return
                    */
                  override def reduce(buff: AvgBuff, age: Int): AvgBuff = {
                    AvgBuff( buff.sum + age, buff.count+1 )
                  }

                  /**
                    * reducer计算
                    * @param b1
                    * @param b2
                    * @return
                    */
                  override def merge(buff1: AvgBuff, buff2: AvgBuff): AvgBuff = {
                    AvgBuff( buff1.sum+buff2.sum , buff1.count+buff2.count )
                  }

                  /**
                    * 计算最终结果
                    * @param reduction
                    * @return
                    */
                  override def finish(reduction: AvgBuff): Double = reduction.sum.toDouble / reduction.count

                  /**
                    * 指定中间变量的编码格式
                    * @return
                    */
                  override def bufferEncoder: Encoder[AvgBuff] = Encoders.product[AvgBuff]

                  /**
                    * 最终结果的编码格式
                    * @return
                    */
                  override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
                }
3、sparksql数据加载和保存
    1、读取数据
        1、读取文本文件: spark.read.textFile(路径)
        2、读取json文件: spark.read.json(路径)
        3、读取csv文件:    
            spark.read
            [.option("sep","指定字段之间的分隔符")]
            [.option("header","指定是否以第一行作为列名")]
            [.option("inferSchema","指定是否对列进行类型推断")]
            .csv(路径)
        4、读取parquet: spark.read.parquet(路径)
        5、读取mysql
            1、第一种方式: [此种方式读取mysql生成的DataFrame只有一个分区,只适合小数据量场景 10W line]
                spark.read.jdbc(url,tableName,prop)
                //连接参数
                val prop = new Properties()
                prop.setProperty("user","root")
                prop.setProperty("password","root123")
                val url = "jdbc:mysql://hadoop102:3306/test"
                val tableName = "user_info" //表示查询整表
                或
                val tableName = "(select id,name,age from user_info where id>100) user" //表示查询指定行和列
            2、第二种方式: [此种方式读取mysql生成的DataFrame的分区数 = condition数组中元素个数] [一般不用]
                spark.read.jdbc(url,tableName,condition,prop)
                //连接参数
                val condition = Array("id<100","id>=100 and id<200","id>=200") //condition中元素就是每个分区拉取的数据的where条件
            3、第三种方式: [此种方式读取mysql生成的DataFrame的分区数 = min(upperBound-lowerBound,numPartitions),一般用于大数据量场景 ]
                spark.read.jdbc(url,tableName,columnName,lowerBound,upperBound,numPartitions,props)
                //连接参数
                columnName必须是数字、日期、时间戳类型的mysql的列名[用于作为分区列]
                lowerBound是columnName列的下限,用于决定每个分区取数的跨距
                upperBound是columnName列的上限,用于决定每个分区取数的跨距
                numPartitions是分区数
                    案例1:如col=id,分区数=3,lowerBound=1,upperBound=30
                        这时生成3个condition    id<11,id>=11 and id <21,id>=21 
                    案例2:如col=id,分区数=3,lowerBound=101,upperBound=130
                        这时生成3个condition    id<111,id>=111 and id <121,id>=121 
                    工作中lowerBound,upperBound一般设置成动态获取的表中分区列的最小值和最大值
                        val df4 = spark.read.jdbc(url,"(select min(id) min_id, max(id) max_id from user_info) user",prop)
                        val row = df4.collect().head
                        val min_id = row.getAs[Long]("min_id")
                        val max_id = row.getAs[Long]("max_id")
    2、保存数据       
        1、保存数据为文本: df/ds[.toJSON].write.mode(SaveMode.Overwrite).text(路径)
        2、保存数据为json: df/ds.write.mode(SaveMode.Overwrite).json(路径)
        3、保存数据为csv: df/ds.write.mode(SaveMode.Overwrite)
                            [.option("sep","指定保存字段之间的分隔符").option("header","指定是否将列名作为文件第一行保存")].csv(路径)
        4、保存数据到parquet: df/ds.write.mode(SaveMode.Overwrite).parquet(路径)
        5、保存数据到mysql: df/ds.write.mode(SaveMode.Append).jdbc(url,tableName,prop)
        常用的写入模式:
            1、SaveMode.Append: 代表如果写入数据的目录/表已经存在则追加数据
                [一般用于数据写入mysql并且mysql表没有主键或者唯一索引]
                    如果写入的表有主键,使用Append可能出现主键冲突的问题,
                    此时建议使用foreachPartition结合:
                        INSERT INTO 表名 VALUES(....) ON DUPLICATE KEY UPDATE 字段名=值,.... 解决
            2、SaveMode.Overwrite: 代表如果写入数据的目录/表已经存在则覆盖数据
                [一般用于数据写入HDFS (删掉文件/表重写)]
            3、ErrorIfExists:如果写入数据的目录/表已经存在则报错
            4、Ignore:如果写入数据的目录/表已经存在则不保存
    3、整合hive
            注:derby数据库只能单线程操作
        1、idea中如何操作hive
            1、引入依赖: spark-hive
            2、将hive的配置文件放入resource目录
            3、在创建sparksession的时候通过enableHiveSupport开启hive的支持
            4、操作hive数据
            5、[再idea本地执行时放在最前面声明,sparksession创建时会要加载环境配置,环境配置在加载后就不会改了]    
                    System.setProperty("HADOOP_USER_NAME","username")    /    (也可以在IDEA右上角 Edit VM options: -DHADOOP_USER_NAME=username)
        代码
            def main(args: Array[String]): Unit = {
            
                System.setProperty("HADOOP_USER_NAME","atguigu")
                
                import org.apache.spark.sql.SparkSession
                val spark = SparkSession.builder().master("local[4]").appName("test").enableHiveSupport().getOrCreate()
                //导入隐式转换
                import spark.implicits._

                //读取hive数据
                spark.sql("select * from student").show

                //保存数据到hive
                spark.sql("insert into student values(2,'wangwu',30)")
            }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值