关于sparksql中设置自定义自增列的相关要点(工作共踩过的坑-1)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

关于sparksql中设置⾃定义⾃增列的相关要点(⼯作共踩过的坑-1)
⼩⽩终于进⼊了职场,从事⼤数据⽅⾯的⼯作!
分到项⽬组了,搬砖的时候遇到了⼀个这样的问题。

要求:⽤spark实现oracle的存储过程中计算部分。

坑:由于报表中包含了⼀个ID字段,其要求是不同的区域拥有不同的区域ID,且ID在数据库表中的属性为主键。

Oracle的存储过程中采⽤的是⾃定义序列,采⽤发号的形式实现ID唯⼀且符合区域特性。

填坑过程:
⽅法⼀:sql.functions 中monotonically_increasing_id。

采⽤import org.apache.spark.sql.functions.中的
monotonically_increasing_id函数。

使⽤demo如下:
//从数据库中加载表TEST_EMP进⼊内存,并且取ENAME和EMPNO两列
val dfEmp=sqlContext.read.options(conUtil.con("TEST_EMP"))
.format("jdbc").load()
.select("ENAME","EMPNO")
val test =dfEmp
.withColumn("TEST_NO",monotonically_increasing_id)
//向oracle中写数据,这个函数的使⽤前提是需要确定表"EMP_TMP"存在。

且向这张表写⼊数据的时候最好字段进⾏对应,如果列多余数据库中的列数则会出现参数过多的错误。

JdbcUtils.saveTable(test, url, "EMP_TMP", properties)
//代码结果如下所⽰,在数据库中⽣成了⼀个从0开始⾃增的列
ENAME EMPNO TEST_NO
SMITH 73690
ALLEN74991
WARD75212
JONES75663
这个⽅法有⼀个缺点:序列是从0开始的,monotonically_increasing_id函数⽆法接受参数,所以我们⽆法⽤其根据我们的业务进⾏指定序列。

所以,有⼀个想法于是去看了⼀下该⽅法的源码,发下如下特点:
⾸先看到函数的定义def monotonically_increasing_id(): Column = withExpr { MonotonicallyIncreasingID() }
深⼊查看MonotonicallyIncreasingID() ,具体源码如下:
private[sql] case class MonotonicallyIncreasingID() extends LeafExpression with Nondeterministic {
/**
* Record ID within each partition. By being transient, count's value is reset to 0 every time
* we serialize and deserialize and initialize it.
*/
@transient private[this] var count: Long = _
@transient private[this] var partitionMask: Long = _
override protected def initInternal(): Unit = {
count = 0L
partitionMask = TaskContext.getPartitionId().toLong << 33
}
override def nullable: Boolean = false
override def dataType: DataType = LongType
override protected def evalInternal(input: InternalRow): Long = {
val currentCount = count
count += 1
partitionMask + currentCount
}
override def genCode(ctx: CodeGenContext, ev: GeneratedExpressionCode): String = {
val countTerm = ctx.freshName("count")
val partitionMaskTerm = ctx.freshName("partitionMask")
ctx.addMutableState(ctx.JAVA_LONG, countTerm, s"$countTerm = 0L;")
ctx.addMutableState(ctx.JAVA_LONG, partitionMaskTerm,
s"$partitionMaskTerm = ((long) org.apache.spark.TaskContext.getPartitionId()) << 33;")
ev.isNull = "false"
s"""
final ${ctx.javaType(dataType)} ${ev.value} = $partitionMaskTerm + $countTerm;
$countTerm++;
"""
}
}
我们可以发现这个类中重写了⽗类的initInternal()⽅法,指定了初始值count=0L,enmm这样⼦的话我们可不可以通过复写该类中的初始值来满⾜我们的业务需求
override protected def initInternal(): Unit = {
count = 0L
partitionMask = TaskContext.getPartitionId().toLong << 33
}
(别想太多,⼀个业务涉及那么多序列,总不能⽤⼀次改⼀次吧,当然如果技术过硬,⾃⼰写⼀套⽅法以及类,⽤来接收参数1:序列起始值,参数2:序列终⽌值。

当前技术不够且加班导致这个想法凉凉)⽅法⼆:rdd算⼦中的zipWithIndex()⽅法
代码demo如下:
val dfEmp=sqlContext.read.options(conUtil.con("TEST_EMP"))
.format("jdbc").load()
.select("ENAME","EMPNO")
//对读取的dfEmp进⾏schema加列操作,增加⼀列且指定列数据类型
val schma=dfEmp.schema.add(StructField("TEST_NO",LongType))
val temp=dfEmp.rdd.zipWithIndex()
//可以在row中指定我们⾃⼰业务需求的序列初始值
val changed= temp.map(t => Row.merge(t._1, Row(t._2+340000000)))
val in=sqlContext.createDataFrame(changed,schma)
JdbcUtils.saveTable(in, url, "EMP_TMP", properties)
结果如下所⽰:
ENAME EMPNO TEST_NO
SMITH 7369 300000000
ALLEN 7499 300000001
WARD 7521 300000002
到此,⼊职的第⼀个坑填好了!貌似⽅法⼆还能够⽤zipWithUniqueId()⽅法进⾏实现,由于时间不够就没有⼀⼀的尝试了,如果各位⼩伙伴们有空可以尝试⼀下! 同时,如果⼩伙伴们有更加好的⽅法,求分享!求指导!感谢!!!!!
欢迎留⾔!!!!。

相关文档
最新文档