须知
1. toplink
2. saveAsTable是DataFrameWriter的方法,DFW会有mode和option,mode统一有4种,但saveAsTable没有option,可以在上面的官文中查看某方法有哪些option
3. saveAsTable执行后,原来hive的表的元数据会变,TBLPROPERTIES
会增加很多spark相关的属性。但分区字段会变成普通字段,需要使用DataFrameWriter的partitionBy方法重新指定下分区。
4. 注意:
sparksql中的INSERT OVERWRITE 后面必须加TABLE
,否则报错
5. 注意:
调整DF中的字段顺序、筛选字段,可以使用select方法
方案
1. saveAsTable
在此方法中,如果数据源表存在于Spark-catalog中,则使用save方法参数中的模式来确定如何插入。如果表不在Spark-catalog中,我们总是会覆盖它(比如JDBC数据源中的一个表),因为spark可以整合hive,所以hive中的表在Spark-catalog中,但比如spark创建的临时表,就不在Spark-catalog中。如果表在Spark-catalog中,则追加。
如果df根据一个没分区的 hive表创建,并且可以转换为hive内置的序列化器(比如ORC和Parquet),那么会以hive兼容的格式持久化到hdfs。否则,会以sparksql指定的格式持久化。
问题:
对象是分区表时,执行完分区表的元数据会变,分区字段变成普通字段,需要partitionBy方法指定分区字段
Overwrite模式时,会把其他分区覆盖,暂时没有好的方法
实验
创建一个分区表t1,然后在t1基础上添加一个字段,创建表t2
使用spark创建这个表的df1,然后添加字段转为df2,然后df2使用saveAsTable插入到t2的新分区。
环境
create table IF NOT EXISTS test1.parTable1(id string,name string)
partitioned by (etl_date string) stored as parquet;
create table IF NOT EXISTS test1.parTable2(id string,name string,age string)
partitioned by (etl_date string) stored as parquet;
1. saveAsTable
发现overwrite状态下会将所有分区覆盖,并且使用show create table会发现分区字段变成普通字段
df3.write.mode(SaveMode.Overwrite).format("parquet").saveAsTable("test1.parTable2")
添加.partitionBy(“etl_date”)方法后,分区字段还在,但还是会把其他所有分区删除
df3.write.partitionBy("etl_date").mode(SaveMode.Overwrite).format("parquet").saveAsTable("test1.parTable2")
网上没有找到解决方法,原因可能是因为表不是通过spark建的,所以会有问题,但也不能每次都通过spark建一次吧。网上说spark2.3已经解决这个问题。
2. insert into
不管有没有.partitionBy(“etl_date”),只要是Overwrite模式,都会覆盖所有分区。不加Overwrite默认append模式,不会删除其他分区但指定分区也不能覆盖
df4.write
.partitionBy("etl_date")
.mode(SaveMode.Overwrite)
.insertInto(tableName="test1.parTable2")
3. 注册为spark临时表然后直接调用hql,能解决分区的覆盖问题,但不能指定插入的字段。只能是拼接INSERT INTO后面的SELECT列表
val code :(String => String) = (arg: String) => {"1"}
val addCol = udf(code)
val df3 = df2.withColumn("age",addCol(col("id")))
val df4 = df3.select("id","name","age")
df4.show()
/*
目的就是为了把df4插入到指定表中
*/
df4.createOrReplaceTempView("temp_df4")
sparkSession.sql(
"""
| INSERT OVERWRITE TABLE test1.parTable2 partition(ETL_DATE='20200916')
| SELECT * FROM temp_df4
""".stripMargin)
拼接INSERT INTO后的SELECT 列表,没有的列设为null,因为select 后面如果不是列名而是值,则直接输出值。
val mxjgDF = sparkSession.table(s"ADS.${tableNamePre}_${modelName}_MXJG")
val fieldNames = mxjgDF.schema.fieldNames.dropRight(1) // 所有字段的按序数组
.map(_.toLowerCase)
Logl.error("CustomLog-MX-fieldNames:)
for (i <- fieldNames)
logl.error(i)
}
Logl.error(s"CustomLog-MX-outputRule:outputRule")
val outputcols = outputRule.split(",")
.map(s => if (s.startsWith("`") s.drop(1).dropRight(1) else s)
.map(_.toLowerCase)
var colsStr = ""
for (i <- fieldNames){
if (outputcois.contains(i)) colsStr += i + ","
else colsstrHnuii)
colsstr = colsStr.dropRight(1)
Logl.error(s"CustomLog-MX-outputcols:${outputcols.foneach(printLn(_))}")
logl.error(s"CustomLog-MX-colsStr:$sStr")
val temp_outputDF:DataFrame = transfomer.transform(wideDF)
log1.error(s"预测结果DF:")
temp_outputDF.show(2)