Spark SQL 相关

Spark SQL的简介

1. 简介

Spark SQL是用于处理结构化数据的模块。与Spark RDD不同的是,Spark SQL提供数据的结构信息(源数据)和性能更好,可以通过SQL和DataSet API与Spark SQL进行交互。

2. 特点

2.1 Spark程序和SQL可以无缝对接

2.2 统一数据访问:使用相同方式链接到数据

2.3 集成hive:在hive数据仓库中可以执行HQL,也可以执行SQL

2.4 标准连接:通过JDBC或者ODBC连接到数据

Spark SQL应用场景

1.执行SQL:从hive中读取数据

Hive Table

  • Hive拥有很多依赖,但是不再Spark中,如果能从classpath中读取这些依赖,Spark会自动加载这些以来,如果是集群,在每个节点上都要有这些依赖,以便访问到Hive中的数据。
  • 需要将Hive的配置文件hive-site.xml(用来访问源数据),拷贝到Spark的conf目录下。
  • 在读取Hive的数据时,要实例化一个SparkSession对象,用户不需要部署hive。如果没有配置hive-site.xml,Spark会自动在spark.sql.warehouse.dir(如果没有指定,在当前目录)指定的文件下创建metastore_db文件,在Spark2.0.0之后,spark.sql.warehouse.dir取代hive.metastore.warehouse.dir指定源数据的存放位置。
  • 如果使用Hive默认的数据库(derby),只能使用一个连接,因为derby是单session的。
  • 需要将相应的连接数据库的jar包,放到Hive和Spark的目录下,如果不这样做的话,可以在spark配置文件中指定的驱动程序的位置,在spark-defaults.conf文件,也可以使用--jars来引入驱动程序。
spark.driver.extraClassPath  ../jars/mysql-connector-java-bin-5.1.27.jar # jar包的位置
spark.executor.extraClassPath  ../jars/mysql-connector-java-bin-5.1.27.jar # jar包的位置
  • 配置驱动程序的方式:a)直接将驱动程序放入hive和spark的目录中;b)使用参数--jars;c)配置到spark-defaults.conf的文件中

Spark SQL执行过程

  • 从Hive MetaStore中查找指定的数据库中是否有查找的数据表,如果有,执行下面的操作,如果没有返回错误。
  • Hive中的数据是存储在HDFS上,通过SparkContext来启动Job。
  • DAGScheduler 将DAG切分成为stage(多个task,taskSet),并且提交stage
  • TaskScheduler 在cluster manager启动task,如果task失败进行重试
  • Worker 运行task
  • WebUI 查看结果

2.DataSet

  • DataSet是一个分布式数据集合,从Spark1.6开始添加的,可以从JVM或者通过transform操作获取DataSet。Python不支持DataSet API,Java和Scala支持。
  • DataFrame是对DataSet数据进行组织,类似一个二维表,具有字段名称和数据。可以通过结构化数据文件、Hive的数据表、外部数据源和已经存在的RDD中获取。Python、Java和Scala都支持DataFrame API,DataFrame借鉴自Python。
  • DataFrame=DataSet[Row],SchemaRDD(Spark1.2)==>DataFrame(Spark1.6)==>DataSet(Spark2.0.0)

Spark SQL操作

简介

  1. 创建SparkSession对象:在创建的过程中会设置一些属性
var sparkSession=SparkSession.builder()
      .appName("SparkSessionApp")
      .master("local[2]")
      .getOrCreate()

2.获取DataFrame

2.1外部数据源

//Spark Sql支持的文件格式为:json, parquet, jdbc, orc, libsvm, csv, text
//如果是text的读取方式,会返回一个字符串列名字为value,其他的返回dataframe。
sparkSession.read.format("json").option("path","/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").load().show()
sparkSession.read.json("/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").show()
//如果读取的是csv文件,需要添加头信息
sparkSession.read.format("csv").option("sep", ";").option("header",true).option("inferSchema", "true").load("/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.csv")

2.2从HiveTable中获取DataFrame

sparkSession.table("people").show()

需要将hive-site.xml文件拷贝到项目resources文件下,然后在pom文件中添加:


  mysql
  mysql-connector-java
  5.1.27


  org.apache.spark
  spark-hive_2.11
  ${spark.version}

在创建SparkSession对象时,需要添加参数,用来支持Hive:

var sparkSession=SparkSession.builder()
      .appName("SparkSessionApp")
      .master("local[2]")
      .enableHiveSupport() //使用hive一定要开启这个
      .getOrCreate()

2.3从现有的RDD转换为DataFrame

import sparkSession.implicits._
sparkSession.sparkContext.parallelize(Array(("1", "xiaoyao"), ("2", "yisheng"),( "3", "bruce"))).toDF("id","name").show()

2.4spark读取的默认格式为parquet

println(sparkSession.conf.get("spark.sql.sources.default")) //输出为parquet,默认的数据源格式为parquet。

2.5输出schema信息

val df=sparkSession.read.format("json").option("path","/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").load()
df.printSchema()
//printSchema()==>println(schema.treeString)

Spark1.5之后,Spark自己来管理内存而不是使用JVM,这样可以避免JVM GC带来的性能损失。内存中的Java对象被存储成Spark自己的二进制格式,计算直接繁盛在二进制格式上,省去了序列化和发序列化时间。同时这种格式也更加紧凑,节省内存空间,而且能更好地估计数据量大小和内存使用情况。默认情况下为开启状态:spark.sql.tungsten.enabled=true。

3.获取指定的数据

3.1 获取一列数据

val df=sparkSession.read.format("json").option("path","/Users/Download/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").load()
df.select("name").show()
//下面两种方式需要将sparkSession定义为val类型,需要导入隐士转换:import sparkSession.implicits._
df.select($"name").show() 
df.select('name).show() //需要进行隐士转换,在spark-shell中可以直接操作
df.select(col("domain")).show() //需要导入以来,import org.apache.spark.sql.functions.col

3.2对某些列进行操作

df.select('name,'age+1).show() //需要进行隐士转换,在spark-shell中可以直接操作

3.3对数据进行过滤

df.filter("age>21").show() //age>20
df.filter('age>21).show()  //age>20
df.filter($"age">21).show() //age>20
df.filter('age===19).show() //age==19
df.filter($"age"===19).show() //age==19
df.filter("age=19").show() //age==19
df.filter("age==19").show() //age==19

在上面的参数中show(),默认展示20条数据,可以指定设置展示的个,第二个参数为是否对字符串进行裁剪(如果字符串的长度超过20个,就需要这个参数truncate)。
3.4 聚合函数

df.groupBy("domain").agg(sum("responseSize").as("rs"))
      .select("domain","rs").show() //后面的agg表示执行聚合操作

4.创建数据的视图
由于数据的视图是与SparkSession的同生命周期(SparkSession结束,视图失效),可以创建一个全局的视图,可以在全局使用,然后就可以使用sql来操作数据

df.createOrReplaceGlobalTempView("people")
    sparkSession.sql("SELECT * FROM global_temp.people").show() #不要同时在Spark-shell与idea同时运行

5.与RDD进行交互
有两种方式将RDD转换为DataSet:

a)使用反射来推断出来对象的类型,这种方式可以减少代码量,如果知道数据类型之后,可以提高性能;

def inferReflection(sparkSession: SparkSession): Unit ={
    //get the origin data
    val info=sparkSession.sparkContext.textFile("/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.txt")
    //split the data and convert to an object
    import sparkSession.implicits._
    //简单写法
    //val df=info.map(_.split(",")).map(x=>People(x(0),x(1).toLong)).toDF()
    //复杂写法,在使用这种方式时,需要表明每一列的名称
    val df=info.map(x=>{
      val temp=x.split(",")
      (temp(0),temp(1).toLong)
    }).toDF("name","age")

    //Before Spark2.0,we can operate directly.Since Spark2.0,we need to convert the dataframe to rdd.
    //df.map(x=>x(0)).collect().foreach(println) //before spark2.0
    //df.rdd.map(x=>x(0)).collect().foreach(println) //since spark2.0
    //df.rdd.map(x=>x.get(0)).collect().foreach(println)
    //df.rdd.map(x=>x.getAs[String](0)).collect().foreach(println)
    //df.rdd.map(x=>x.getString(0)).collect().foreach(println)
    //df.rdd.map(x=>x.getAs[String]("name")).collect().foreach(println)
    //df.select(col("name")).show()
    /**
      * Compute the average for all numeric columns grouped by department.
      * ds.groupBy("department").avg()
      * // Compute the max age and average salary, grouped by department and gender.
      * ds.groupBy($"department", $"gender").agg(Map(
      * "salary" -> "avg",
      * "age" -> "max"
      * ))
      */
    df.groupBy("name").agg(sum("age").as("age"))
     .select("name","age").show()
  }
  //scala2.10之前,字段个数有限制:最大值为22个,在scala2.10之后,就没有这个限制,如果超过这个限制,修改设计或者使用nested case classes。
case class People(name:String,age:Long) 

b)通过编程方式,构建schema结构,然后作用与现有的RDD,这种方式在运行时才会知道字段的类型。

def programmatically(sparkSession: SparkSession): Unit ={
    /**
      * 1.Create an RDD of Rows from the original RDD;
      * 2.Create the schema represented by a StructType matching the structure of Rows in the RDD created in Step 1.
      * 3.Apply the schema to the RDD of Rows via createDataFrame method provided by SparkSession.
      */
    //1.Create an RDD of Rows from the original RDD;
    val info=sparkSession.sparkContext.textFile("/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.txt")
    val rdd=info.map(_.split(",")).map(x=>Row(x (0),x(1).toLong))
    //2.Create the schema represented by a StructType matching the structure of Rows in the RDD created in Step 1.
    val struct= StructType(
      StructField("name", StringType, false) ::
        StructField("age", LongType, false) :: Nil)
    //第二中写法
    /*val struct= StructType(
                Array(StructField("name", StringType, false),
                  StructField("age", LongType, false)))*/
    //Apply the schema to the RDD of Rows via createDataFrame method provided by SparkSession.
    val df=sparkSession.createDataFrame(rdd,struct)
    //df.show()
    df.groupBy("name").agg(sum("age").as("age"))
      .select("name","age").show()
  }

c)聚合函数

df.groupBy("name").agg(Map("age"->"max")).show()

d)自定义函数

自定义函数的步骤:1.defined the udf;2.register the udf;3.use the udf
1.defined the udf
def getStringLength(name: String)={
    name.length()
}
2.register the udf
sparkSession.udf.register("strlen",(name:String)=>{
      getStringLength(name)
})
3.use the udf
//Registers this Dataset as a temporary table using the given name,
// registerTempTable is deprecated since 2.0.0,we can use createOrReplaceTempView instead.
df.registerTempTable("people")
df.createOrReplaceTempView("people")
sparkSession.sql("select name,strlen(name) from people").show()

e)增加字段

//使用sql的方式
sparkSession.udf.register("strlen",(name:String,age:Long)=>{
  getStringLength(name,age)
})
df.registerTempTable("people")
df.createOrReplaceTempView("people")
sparkSession.sql("select name,strlen(name) as length from people").show()
//udf第二种方式
val code=(args0:String)=>{
  (args0.length())
}
val addCol=udf(code)
df.withColumn("other",addCol(df("name"))).show(false)
//udf 第三种写法
sparkSession.udf.register("strlen",(name:String,age:Long)=>{
  getStringLength(name,age)
})
import sparkSession.implicits._
df.withColumn("length",callUDF("strlen",$"name",$"age")).show()

f)创建DataSet

DataSet使用具体的编码器而不是用java和kryo的序列化方式。
编码器和序列化都是将对象变成字节,编码器是动态产生并且可以执行过滤、排序和hash等操作而不用反序列化为对象。
import sparkSession.implicits._
//读取数据,直接对应到类
//val peolpleDs = sparkSession.read.json("/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").as[People]
//peolpleDs.show()
//将数据直接序列化
val ds = Seq(People("Andy", 32)).toDS()
ds.show()

6.将数据写入到文件中

//在写文件时,只能指定文件的路径,不能指定文件的名称
val df=sparkSession.read.format("json").option("path","/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").load()
df.select("name").write.format("json").mode(SaveMode.Overwrite).save("/Users/Downloads/test/")
//也可以指定不同的格式    
df.select("name").write.format("text").mode(SaveMode.Overwrite).save("/Users/Downloads/test/")
//写入orc文件:
df.write.format("orc").option("orc.bloom.filter.columns", "favorite_color").option("orc.dictionary.key.threshold", "1.0").save("users_with_options.orc")
//保存为HiveTable,也可以指定路径
val df=sparkSession.read.format("json").option("path","/Users//Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").load()
df.select("name").write.saveAsTable("people")
//sparkSession.sql("select * from people").show()
sparkSession.table("people").show()

6.1 SaveMode

Scala/Java Any Language Meaning
SaveMode.ErrorIfExists(默认) "error" or "errorifexists" 如果文件存在,就报错
SaveMode.Append "append" 如果文件存在,直接追加,如果不存在,创建
SaveMode.Overwrite "overwrite" 如果存在直接重写,如果不存在,创建
SaveMode.Ignore "ignore" 如果存在,不写

6.2 Saving to Persistent Tables

DatFrame使用saveAsTable将数据存储到HiveMetaStore中(不需要安装Hive)。如果Spark应用程序重启之后可以连接到之前HiveMetaStore,数据依然存在。

使用df.write.option("path", "/some/path").saveAsTable("t"),如果自定义路径之后,删除表之后,路径和数据依然存在。如果没有自定路径,删除表之后,表和数据都没有拉。

从Spark2.1之后,可以存储分区表,有以下好处:

  • 只返回查询的分区,不用扫描所有的分区
  • 在DataSource 中,可以直接使用Hive DDLs
    在默认情况,创建外部表的时候,不会存储分区信息,需要使用命令MSCK REPAIR TABLE来将这些信息加上。

6.3 Bucketing, Sorting and Partitioning

Bucketing和分区只能在持久化表的时候,才能使用

peopleDF.write.bucketBy(42, "name").sortBy("age").saveAsTable("people_bucketed")

在使用DataSet API时,可以使用save和saveAsTable。

usersDF.write.partitionBy("favorite_color").format("parquet").save("namesPartByColor.parquet")

在操作单表的时候,可以使用partitioning和bucketing

usersDF
  .write
  .partitionBy("favorite_color")
  .bucketBy(42, "name")
  .saveAsTable("users_partitioned_bucketed")

7.直接运行SQL

sparkSession.sql("select * from parquet.`/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/users.parquet`").show()

数据源操作

1.parquet
1.1读取数据保存为parquet格式

val df=sparkSession.read.json("/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json")
    df.write.parquet("people.parquet")
    val peopleDF=sparkSession.read.parquet("people.parquet")
    peopleDF.createOrReplaceTempView("parquetFile")
    val namesDF=sparkSession.sql("select name from parquetFile where age between 13 and 19")
    namesDF.show()

1.2发现分区

内置文件数据源Text/CSV/JSON/ORC/Parquet可以自动发现分区和获取分区信息。
SparkSql可以从路径中自动发现分区信息和分区字段的类型,目前支持类型为:numeric data types、date、timestamp、string。
如果不需要字段的类型,设置参数spark.sql.sources.partitionColumnTypeInference.enabled=false。
Spark1.6开始支持发现分区,只查找指定路径下的分区。如果需要更深层次的分区,需要指定basePath。

1.3Schema Merging

从SparkSQL1.5开始该功能默认关闭,因为非常耗内存。如果需要的话,可以开启,设置spark.sql.parquet.mergeSchema=true。
Parquet数据可以自动对数据的schema信息进行合并。

1.4Hive metastore Parquet table conversion

Hive与Parquet在处理表schema信息的区别:
a)Hive不区分大小写,Parquet区分大小写;
b)Hive需要考虑列是否为空,Parquet不需要考虑;
在将Hive的Parquet数据表转换为SparkSQL的Parquet表时,需要将Hive中的源数据与Parquet的源数据进行转换。
转换规则为:仅出现在Parquet中出现的源数据需要考虑,仅出现在Hive源数据中,可以添加为null值列。

1.5刷新MetaData
为了提高性能,需要对Parquet数据进行缓存,Hive metastore Parquet table conversion时,会造成一些源数据发生变化,需要刷新代码中缓存的源数据,使用

sparkSession.catalog.refreshTable("my_table")

1.6配置

常用的配置有:

参数 解释
spark.sql.parquet.binaryAsString 为了兼容之前的版本和其他的源数据,将二进制变成字符串,默认为不开启
spark.sql.parquet.int96AsTimestamp 将其他系统中的INT96转换为SparkSQL的timestamp类型,默认为转换
spark.sql.parquet.compression.codec 对Parquet数据进行压缩
spark.sql.parquet.filterPushdown 用于过滤优化

1.7 Parquet文件的优点

  • 高效,Parquet采取列示存储避免读入不需要的数据,具有极好的性能和GC。
  • 方便的压缩和解压缩,并具有极好的压缩比例。
  • 可以直接固化为parquet文件,可以直接读取parquet文件,具有比磁盘更好的缓存效果。

2.ORC文件
从Spark2.3支持矢量化读取ORC文件,需要开启一下配置:

参数 解释
spark.sql.orc.impl native和Hive二选一,native是基于ORC1.4,Hive是基于Hive的ORC1.2.1
spark.sql.orc.enableVectorizedReader 默认为true,在本地可以矢量化读取,否则,不可以,如果读取hive数据,忽略这个配置

3.JSON

在读取JSON文件时,分单行和多行读取,如果使用多行读取,需要将multiline配置设置为true。

4.HiveTable
在从Hive总读取数据时,需要指定spark.sql.warehouse.dir的地址。
可以创建Hive表、加载数据、分区

//指定warehouse的路径,需要导入import java.io.File
val warehouseLocation = new File("spark-warehouse").getAbsolutePath
val sparkSession = SparkSession
  .builder()
  .master("local[2]")
  .appName("Spark Hive Example")
  .config("spark.sql.warehouse.dir", warehouseLocation)
  .enableHiveSupport() //连接到Hive必须要有的配置
  .getOrCreate()
import sparkSession.implicits._
import sparkSession.sql
sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) USING hive") //创建数据表
sql("LOAD DATA LOCAL INPATH '/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/kv1.txt' INTO TABLE src")
//查询数据
sql("SELECT * FROM src").show()
//聚合操作
sql("SELECT COUNT(*) FROM src").show()
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")
//遍历每一行数据
val stringsDS = sqlDF.map {
  case Row(key: Int, value: String) => s"Key: $key, Value: $value"
}
stringsDS.show()
//创建表并设置存储格式
sql("CREATE TABLE hive_records(key int, value string) STORED AS PARQUET")
val df = sparkSession.table("src")
df.write.mode(SaveMode.Overwrite).saveAsTable("hive_records")
//指定路径
val dataDir = "/tmp/parquet_data"
sparkSession.range(10).write.parquet(dataDir)
//指定外部表
sql(s"CREATE EXTERNAL TABLE hive_ints(key int) STORED AS PARQUET LOCATION '$dataDir'")
//开启动态分区
sparkSession.sqlContext.setConf("hive.exec.dynamic.partition", "true")
sparkSession.sqlContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")
//创建分区表
df.write.partitionBy("key").format("hive").saveAsTable("hive_part_tbl")
sparkSession.sql("SELECT * FROM hive_part_tbl").show()
sparkSession.stop()
case class Record(key: Int, value: String)

4.1设置文件的存储格式
默认情况下,读取Hive Table都是以文本文件读取。Hive storage handler不支持创建表(spark sql中),在hive中创建表,使用spark sql读取。

参数 解释
fileFormat 存储文件格式:sequencefile、rcfile、orc、parquet、textfile、avro。
inputFormat, outputFormat 设置输入格式和输出格式
serde 进行序列化的方式
fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim 指定分隔符

4.2与Hive MetaStore进行交互
从SparkSQL1.4可以访问各种Hive MetaStore。

参数 解释
spark.sql.hive.metastore.version 默认Hive版本为1.2.1,支持0.12.0到2.3.3
spark.sql.hive.metastore.jars 实例化HiveMetastoreClient的三种方式:内置(必须要定义spark.sql.hive.metastore.version)、maven(不推荐使用)、classpath(必须要包含所有以来)
spark.sql.hive.metastore.sharedPrefixes 连接数据库驱动程序
spark.sql.hive.metastore.barrierPrefixes 连接不同版本的Hive

5.JDBC

参数 解释
url 连接字符串
dbtable 连接的表
query 查询语句,dbtable不能与query同时使用,query不能与partitionColumn同时使用
driver 连接驱动类
numPartitions 同时读取分区的数量,同时最大连接数,如果数量超过限制,会对数据进行重新分配
partitionColumn, lowerBound, upperBound 查询条件限制,按照那个字段进行分区,扫描数据的范围
fetchsize 返回数据的大小
batchsize 一次获取数据的大小
createTableOptions 创建表时指定分区等一系列参数
pushDownPredicate 是否使用谓词下压,默认为true

设置连接相关参数的方式:1.使用option来指定;2.通过Properties来指定。

val sparkSession=SparkSession
  .builder()
  .master("local[2]")
  .appName("Spark Jdbc Example")
  .getOrCreate()
//读取数据
val jdbcDF = sparkSession.read
  .format("jdbc")
  .option("url", "jdbc:mysql://localhost:3306/xiaoyao")
  .option("dbtable", "select * from xiaoyao.TBLS")
  .option("user", "root")
  .option("password", "123456")
  .option("driver","com.mysql.jdbc.Driver")
  .load()
jdbcDF.show()
val connectProperties=new Properties()
connectProperties.put("user","root")
connectProperties.put("password","123456")
connectProperties.put("driver","com.mysql.jdbc.Driver")
val jdbcDF2=sparkSession.read.jdbc("jdbc:mysql://localhost:3306/xiaoyao","people",connectProperties)
jdbcDF2.show()
// 写入数据
val schema= StructType(StructField("namge", StringType, false) :: StructField("age", LongType, false) :: Nil)
val info=sparkSession.sparkContext.textFile("/Users/Documents/test/spark-2.2.2-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.txt")
val rdd=info.map(_.split(",")).map(x=>Row(x (0),x(1).toLong))
val peopleDF=sparkSession.createDataFrame(rdd,schema)
peopleDF.write.mode(SaveMode.Append).jdbc("jdbc:mysql://localhost:3306/xiaoyao","people",connectProperties)
jdbcDF.show()

6.Troubleshooting

  • 原始class loader必须要对JDBC Driver可见,因为Java有安全检查;修改classpath.sh,使得classpath.sh包含driver的jar。
  • 一些数据库会将名字变成大写。在Spark SQL需要将名字变成大写。
  • 用户可以通过指定具体的JDBC来连接到相应的数据库。

自定义外部数据源

名词解释

1.BaseRelation

Schema是一个KV的数据组合,继承BaseRelation的类,必须要设置自己的Schema(StructType形式),实现Scan类的相关方法。主要功能是定义Schema信息。

2.TableScan

将元祖数据变成Rows对象中的数据。

3.PrunedScan

将不需要的列过滤掉。

4.PrunedFilteredScan

先进行数据过滤然后将不需要的列过滤掉。

5.InsertableRelation

将数据写回去。

有三种假设:a)插入字段与原先的字段一致;b)Schema细腻不能变化;c)插入的数据可以为空。

6.RelationProvider

指定数据源,如果没有找到指定的数据源,就找DefaultSource附加到路径中,每次调用DDL都会创建实例对象。

7.DataSourceRegister

注册自定义的外部数据源

关于自定义数据源请参考后续文章。

你可能感兴趣的:(Spark SQL 相关)