Spark SQL

3.0--3.2

3. DataFrame编程详解

3.1 创建DataFrame

在Spark SQL中SparkSession是创建DataFrames和执行SQL的入口

创建DataFrames有三种方式:

  1. 从一个已存在的RDD进行转换
  1. JSON/Parquet/CSV/ORC等结构化文件源创建
  1. Hive/JDBC各种外部结构化数据源(服务)创建

核心要义:创建DataFrame,需要创建 “RDD  +  元信息schema定义” + 执行计划

rdd来自于数据

schema则可以由开发人员定义,或者由框架从数据中推断

3.1.1 使用RDD创建DataFrame

  1. 将RDD关联case class创建DataFrame

Scala
object DataFrameDemo1 {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf()
      .setAppName(this.getClass.getSimpleName)
      .setMaster("local[*]")
    //1.SparkSession,是对SparkContext的增强
    val session: SparkSession = SparkSession.builder()
      .config(conf)
      .getOrCreate()

    //2.创建DataFrame
    //2.1先创建RDD
    val lines: RDD[String] = session.sparkContext.textFile("data/user.txt")
    //2.2对数据进行整理并关联Schema
    val tfBoy: RDD[Boy] = lines.map(line => {
      val fields = line.split(",")
      val name = fields(0)
      val age = fields(1).toInt
      val fv = fields(2).toDouble
      Boy(name, age, fv) //字段名称,字段的类型
    })

    //2.3将RDD关联schema,将RDD转成DataFrame
    //导入隐式转换
    import session.implicits._
    val df: DataFrame = tfBoy.toDF
    //打印DataFrame的Schema信息
    df.printSchema()
    //3.将DataFrame注册成视图(虚拟的表)
    df.createTempView("v_users")
    //4.写sql(Transformation)
    val df2: DataFrame = session.sql("select * from v_users order by fv desc, age asc")
    //5.触发Action
    df2.show()
    //6.释放资源
    session.stop()
  }
}

case class Boy(name: String, age: Int, fv: Double)

  1. 将RDD关联普通class创建DataFrame

Scala
object C02_DataFrameDemo2 {

  def main(args: Array[String]): Unit = {

    //1.创建SparkSession
    val spark = SparkSession.builder()
      .appName("DataFrameDemo2")
      .master("local[*]")
      .getOrCreate()

    //2.创建RDD
    val lines: RDD[String] = spark.sparkContext.textFile("data/user.txt")
    //2将数据封装到普通的class中
    val boyRDD: RDD[Boy2] = lines.map(line => {
      val fields = line.split(",")
      val name = fields(0)
      val age = fields(1).toInt
      val fv = fields(2).toDouble
      new Boy2(name, age, fv) //字段名称,字段的类型
    })
    //3.将RDD和Schema进行关联
    val df = spark.createDataFrame(boyRDD, classOf[Boy2])
    //df.printSchema()
    //4.使用DSL风格的API
    import spark.implicits._
    df.show()
    spark.stop()
  }
}

//参数前面必须有var或val
//必须添加给字段添加对应的getter方法,在scala中,可以@BeanProperty注解
class Boy2(
            @BeanProperty
            val name: String,
            @BeanProperty
            val age: Int,
            @BeanProperty
            val fv: Double) {

}

普通的scala class 必须在成员变量加上@BeanProperty属性,因为sparksql需要通过反射调用getter获取schema信息

  1. 将RDD关联java class创建DataFrame

Scala
object SQLDemo4 {

  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .appName(this.getClass.getSimpleName)
      .master("local[*]")
      .getOrCreate()

    val lines = spark.sparkContext.textFile("data/boy.txt")
    //将RDD关联的数据封装到Java的class中,但是依然是RDD
    val jboyRDD: RDD[JBoy] = lines.map(line => {
      val fields = line.split(",")
      new JBoy(fields(0), fields(1).toInt, fields(2).toDouble)
    })
    //强制将关联了schema信息的RDD转成DataFrame
    val df: DataFrame = spark.createDataFrame(jboyRDD, classOf[JBoy])
    //注册视图
    df.createTempView("v_boy")
    //写sql
    val df2: DataFrame = spark.sql("select name, age, fv from v_boy order by fv desc, age asc")
    df2.show()
    spark.stop()
  }
}

Java
public class JBoy {

    private String name;

    private Integer age;

    private Double fv;

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

    public Double getFv() {
        return fv;
    }

    public JBoy(String name, Integer age, Double fv) {
        this.name = name;
        this.age = age;
        this.fv = fv;
    }
}

  1. 将RDD关联Schema创建DataFrame

Scala
object SQLDemo4 {

  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .appName(this.getClass.getSimpleName)
      .master("local[*]")
      .getOrCreate()

    val lines = spark.sparkContext.textFile("data/boy.txt")
    //将RDD关联了Schema,但是依然是RDD
    val rowRDD: RDD[Row] = lines.map(line => {
      val fields = line.split(",")
      Row(fields(0), fields(1).toInt, fields(2).toDouble)
    })
    
    val schema = StructType.apply(
      List(
        StructField("name", StringType),
        StructField("age", IntegerType),
        StructField("fv", DoubleType),
      )
    )
    val df: DataFrame = spark.createDataFrame(rowRDD, schema)
    //打印schema信息
    //df.printSchema()
    //注册视图
    df.createTempView("v_boy")
    //写sql
    val df2: DataFrame = spark.sql("select name, age, fv from v_boy order by fv desc, age asc")
    df2.show()
    spark.stop()
  }
}

3.1.2 从结构化文件创建DataFrame

(1)从csv文件(不带header)进行创建

csv文件内容:

Plain Text
1,张飞,21,北京,80.0
2,关羽,23,北京,82.0
3,赵云,20,上海,88.6
4,刘备,26,上海,83.0
5,曹操,30,深圳,90.0

代码示例:

Scala
val df = spark.read.csv("data_ware/demodata/stu.csv")
df.printSchema()
df.show()

结果如下:

Spark SQL_第1张图片

 

Spark SQL_第2张图片

 

可以看出,框架对读取进来的csv数据,自动生成的schema中,

字段名为:_c0,_c1,.....

字段类型全为String

不一定是符合我们需求的

(2)从csv文件(不带header)自定义Schema进行创建

// 创建DataFrame时,传入自定义的schema

// schema在api中用StructType这个类来描述,字段用StructField来描述

Scala
val schema = new StructType()
  .add("id", DataTypes.IntegerType)
  .add("name", DataTypes.StringType)
  .add("age", DataTypes.IntegerType)
  .add("city", DataTypes.StringType)
  .add("score", DataTypes.DoubleType)

val df = spark.read.schema(schema).csv("data_ware/demodata/stu.csv")
df.printSchema()
df.show()

Schema信息:

Plain Text
root
 |-- id: integer (nullable = true)
 |-- name: string (nullable = true)
 |-- age: integer (nullable = true)
 |-- city: string (nullable = true)
 |-- score: double (nullable = true)

输出数据信息:

SQL
+---+------+---+------+-----+
| id|name  |age|city  |score|
+---+------+---+------+-----+
|  1|  张飞| 21|  北京| 80.0|
|  2|  关羽| 23|  北京| 82.0|
|  3|  赵云| 20|  上海| 88.6|
|  4|  刘备| 26|  上海| 83.0|
|  5|  曹操| 30|  深圳| 90.0|
+---+------+---+------+-----+

(3)从csv文件(带header)进行创建

csv文件内容:

Plain Text
id,name,age,city,score
1,张飞,21,北京,80.0
2,关羽,23,北京,82.0
3,赵云,20,上海,88.6
4,刘备,26,上海,83.0
5,曹操,30,深圳,90.0

注意:此文件的第一行是字段描述信息,需要特别处理,否则会被当做rdd中的一行数据

代码示例:关键点设置一个header=true的参数

Scala
val df = spark.read
  .option("header",true) //读取表头信息
  .csv("data_ware/demodata/stu.csv")
df.printSchema()
df.show()

结果如下:

root

Plain Text
 |-- id: string (nullable = true)
 |-- name: string (nullable = true)
 |-- age: string (nullable = true)
 |-- city: string (nullable = true)
 |-- score: string (nullable = true)

+---+----+---+----+-----+
| id|name|age|city|score|
+---+----+---+----+-----+
|  1|  张飞| 21|  北京| 80.0|
|  2|  关羽| 23|  北京| 82.0|
|  3|  赵云| 20|  上海| 88.6|
|  4|  刘备| 26|  上海| 83.0|
|  5|  曹操| 30|  深圳| 90.0|
+---+----+---+----+-----+

问题:虽然字段名正确指定,但是字段类型还是无法确定,默认情况下全视作String对待,当然,可以开启一个参数  inferSchema=true 来让框架对csv中的数据字段进行合理的类型推断

Scala
val df = spark.read
  .option("header",true)
  .option("inferSchema",true) //推断字段类型
  .csv("data_ware/demodata/stu.csv")
df.printSchema()
df.show()

如果推断的结果不如人意,当然可以指定自定义schema

让框架自动推断schema,效率低不建议!

(4)从JSON文件进行创建

准备json数据文件

JSON
{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}

代码示例

JSON
val df = spark.read.json("data_ware/demodata/people.json")
df.printSchema()
df.show()

(5)从Parquet文件进行创建

Parquet文件是一种列式存储文件格式,文件自带schema描述信息

准备测试数据

任意拿一个dataframe,调用write.parquet()方法即可将df保存为一个parquet文件

代码示例:

Scala
val df = spark.read.parquet("data/parquet/")

(6)从orc文件进行创建

代码示例

Scala
val df = spark.read.orc("data/orcfiles/")

3.2.3外部存储服务创建DF

(1)从JDBC连接数据库服务器进行创建

实验准备

在一个mysql服务器中,创建一个数据库demo,创建一个表student,如下:

Spark SQL_第3张图片

 

注:要使用jdbc连接读取数据库的数据,需要引入jdbc的驱动jar包依赖

XML

    mysql
    mysql-connector-java
    8.0.30

代码示例

Scala
val props = new Properties()
props.setProperty("user","root")
props.setProperty("password","root")
val df = spark.read.jdbc("jdbc:mysql://localhost:3306/demo","student",props)
df.show()

结果如下:

Plain Text
+---+------+---+------+-----+
| id|name  |age|city  |score|
+---+------+---+------+-----+
|  1|  张飞| 21|  北京| 80.0|
|  2|  关羽| 23|  北京| 82.0|
|  3|  赵云| 20|  上海| 88.6|
|  4|  刘备| 26|  上海| 83.0|
|  5|  曹操| 30|  深圳| 90.0|
+---+------+---+------+-----+

SparkSql添加了spark-hive的依赖,并在sparkSession构造时开启enableHiveSupport后,就整合了hive的功能(通俗说,就是sparksql具备了hive的功能);

Spark SQL_第4张图片

 

既然具备了hive的功能,那么就可以执行一切hive中能执行的动作:

  • 建表
  • show 表
  • 建库
  • show 库
  • alter表
  • ……

只不过,此时看见的表是spark中集成的hive的本地元数据库中的表!

如果想让spark中集成的hive,看见你外部集群中的hive的表,只要修改配置:把spark端的hive的元数据服务地址,指向外部集群中hive的元数据服务地址;

有两种指定办法:

  • 在spark端加入hive-site.xml ,里面配置 目标元数据库 mysql的连接信息

这会使得spark中集成的hive直接访问mysql元数据库

  • 在spark端加入hive-site.xml ,里面配置 目标hive的元数据服务器地址

这会使得spark中集成的hive通过外部独立的hive元数据服务来访问元数据库

Spark SQL_第5张图片

 

(2)从Hive创建DataFrame

Sparksql通过spark-hive整合包,来集成hive的功能

Sparksql加载“外部独立hive”的数据,本质上是不需要“外部独立hive”参与的,因为“外部独立hive”的表数据就在hdfs中,元数据信息在mysql中

不管数据还是元数据,sparksql都可以直接去获取!

步骤:

  1. 要在工程中添加spark-hive的依赖jar以及mysql的jdbc驱动jar

XML

    mysql
    mysql-connector-java
    8.0.30




    org.apache.spark
    spark-hive_2.12
    3.2.3

  1. 要在工程中添加hive-site.xml/core-site.xml配置文件

Spark SQL_第6张图片

 

  1. 创建sparksession时需要调用.enableHiveSupport( )方法

Scala
val spark = SparkSession
  .builder()
  .appName(this.getClass.getSimpleName)
  .master("local[*]")
  // 启用hive支持,需要调用enableHiveSupport,还需要添加一个依赖 spark-hive
  // 默认sparksql内置了自己的hive
  // 如果程序能从classpath中加载到hive-site配置文件,那么它访问的hive元数据库就不是本地内置的了,而是配置中所指定的元数据库了
  // 如果程序能从classpath中加载到core-site配置文件,那么它访问的文件系统也不再是本地文件系统了,而是配置中所指定的hdfs文件系统了
  .enableHiveSupport()
  .getOrCreate()

  1. 加载hive中的表

Scala
val df = spark.sql("select * from t1")

注意点:如果自己也用dataframe注册了一个同名的视图,那么这个视图名会替换掉hive的表

(3)从Hbase创建DataFrame

其实,sparksql可以连接任意外部数据源(只要有对应的“连接器”即可)

Sparksql对hbase是有第三方连接器(华为)的,但是久不维护!

建议用hive作为连接器(hive可以访问hbase,而sparksql可以集成hive)

在hbase中建表

Shell
create 'doitedu_stu','f'

插入数据到hbase表

Shell
put 'doitedu_stu','001','f:name','zhangsan'
put 'doitedu_stu','001','f:age','26'
put 'doitedu_stu','001','f:gender','m'
put 'doitedu_stu','001','f:salary','28000'
put 'doitedu_stu','002','f:name','lisi'
put 'doitedu_stu','002','f:age','22'
put 'doitedu_stu','002','f:gender','m'
put 'doitedu_stu','002','f:salary','26000'
put 'doitedu_stu','003','f:name','wangwu'
put 'doitedu_stu','003','f:age','21'
put 'doitedu_stu','003','f:gender','f'
put 'doitedu_stu','003','f:salary','24000'
put 'doitedu_stu','004','f:name','zhaoliu'
put 'doitedu_stu','004','f:age','22'
put 'doitedu_stu','004','f:gender','f'
put 'doitedu_stu','004','f:salary','25000'

创建hive外部表映射hbase中的表

SQL
CREATE EXTERNAL TABLE doitedu_stu
( 
  id        string ,
  name      string ,
  age       int    ,
  gender    string ,
  salary    double  
)  
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' 
WITH SERDEPROPERTIES ( 'hbase.columns.mapping'=':key,f:name,f:age,f:gender,f:salary') 
TBLPROPERTIES ( 'hbase.table.name'='default:doitedu_stu')
;

工程中放置hbase-site.xml配置文件

XML

    
        hbase.rootdir
        hdfs://doit01:8020/hbase
    

    
        hbase.cluster.distributed
        true
    

    
        hbase.zookeeper.quorum
        doit01:2181,doit02:2181,doit03:2181
    

    
        hbase.unsafe.stream.capability.enforce
        false
    

工程中添加hive-hbase-handler连接器依赖

XML

    org.apache.hive
    hive-hbase-handler
    2.3.7
    
        
            org.apache.hadoop
            hadoop-common
        
    

以读取hive表的方式直接读取即可

Scala
spark.sql("select * from doitedu_stu")

    

3.2 输出DF的各种方式

3.2.1 展现在控制台

Scala
df.show()
df.show(10)        //输出10行  
df.show(10,false)  // 不要截断列

3.2.2 保存为文件

Scala
object Demo17_SaveDF {

  def main(args: Array[String]): Unit = {

    val spark = SparkUtil.getSpark()

    val df = spark.read.option("header",true).csv("data/stu2.csv")

    val res = df.where("id>3").select("id","name")

    // 展示结果
    res.show(10,false)
}

 保存结果为文件: parquet,json,csv,orc,textfile

 文本文件是自由格式,框架无法判断该输出什么样的形式

Scala
res.write.parquet("out/parquetfile/")

res.write.csv("out/csvfile")

res.write.orc("out/orcfile")

res.write.json("out/jsonfile")

 要将df输出为普通文本文件,则需要将df变成一个列

Scala
res.selectExpr("concat_ws('\001',id,name)")
  .write.text("out/textfile")

3.2.3 保存到RDBMS

将dataframe写入mysql的表

Scala
// 将dataframe通过jdbc写入mysql
val props = new Properties()
props.setProperty("user","root")
props.setProperty("password","root")

// 可以通过SaveMode来控制写入模式:SaveMode.Append/Ignore/Overwrite/ErrorIfExists(默认)
res.write.mode(SaveMode.Append).jdbc("jdbc:mysql://localhost:3306/demo?characterEncoding=utf8","res",props)

3.2.4 写入hive

开启spark的hive支持

Scala
val spark = SparkSession
  .builder()
  .appName("")
  .master("local[*]")
  .enableHiveSupport()
  .getOrCreate()

放入配置文件

Spark SQL_第7张图片

 

写代码:

Scala
// 将dataframe写入hive,saveAsTable就是保存为hive的表
// 前提,spark要开启hiveSupport支持,spark-hive的依赖,hive的配置文件
res.write.saveAsTable("res")

3.2.5 DF输出时的分区操作

Hive中对表数据的存储,可以将数据分为多个子目录!

比如:

Shell
create table tx(id int,name string) partitioned by (city string);
load data inpath ‘/data/1.datinto table tx partition(city=”beijing”)
load data inpath ‘/data/2.datinto table tx partition(city=”shanghai”)

Hive的表tx的目录结构如下:

Plain Text
/user/hive/warehouse/tx/
                    city=beijing/1.dat
                    city=shanghai/2.dat

查询的时候,分区标识字段,可以看做表的一个字段来用

SQL
Select * from  tx   where city=’shanghai’

那么,sparksql既然是跟hive兼容的,必然也有对分区存储支持的机制!

  1. 能识别解析分区

有如下数据结构形式:

Spark SQL_第8张图片

 

Scala
/**
  * sparksql对分区机制的支持
  * 识别已存在分区结构  /aaa/city=a;  /aaa/city=b;
  * 会将所有子目录都理解为数据内容,会将子目录中的city理解为一个字段
  */
spark.read.csv("/aaa").show()

  1. 能将数据按分区机制输出

Scala
/**
  * sparksql对分区机制的支持
  * 将dataframe存储为分区结构
  */
val dfp = spark.read.option("header",true).csv("data/stu2.csv")
dfp.write.partitionBy("city").csv("/bbb")


/**
  * 将数据分区写入hive
  * 注意:写入hive的默认文件格式是parquet
  */
dfp.write.partitionBy("sex").saveAsTable("res_p")

输入结构如下:

Spark SQL_第9张图片

 

你可能感兴趣的:(spark,sql,大数据)