Spark DateType cast 踩坑
前言
在平时的 Spark 处理中常常会有把一个如 2012-12-12
这样的 date 类型转换成一个 long 的 Unix time 然后进行计算的需求.下面是一段示例代码:
val schema = StructType(
Array(
StructField("id", IntegerType, nullable = true),
StructField("birth", DateType, nullable = true),
StructField("time", TimestampType, nullable = true)
)) val data = Seq(
Row(, Date.valueOf("2012-12-12"), Timestamp.valueOf("2016-09-30 03:03:00")),
Row(, Date.valueOf("2016-12-14"), Timestamp.valueOf("2016-12-14 03:03:00"))) val df = spark.createDataFrame(spark.sparkContext.parallelize(data),schema)
问题 & 解决
首先很直观的是直接把DateType cast 成 LongType, 如下:
df.select(df.col("birth").cast(LongType))
但是这样出来都是 null, 这是为什么? 答案就在 org.apache.spark.sql.catalyst.expressions.Cast
中, 先看 canCast 方法, 可以看到 DateType 其实是可以转成 NumericType 的, 然后再看下面castToLong的方法, 可以看到case DateType => buildCast[Int](_, d => null)
居然直接是个 null, 看提交记录其实这边有过反复, 然后为了和 hive 统一, 所以返回最后还是返回 null 了.
虽然 DateType 不能直接 castToLong, 但是TimestampType可以, 所以这里的解决方案就是先把 DateType cast 成 TimestampType. 但是这里又会有一个非常坑爹的问题: 时区问题.
首先明确一个问题, 就是这个放到了 spark 中的 2012-12-12 到底 UTC 还是我们当前时区? 答案是如果没有经过特殊配置, 这个2012-12-12代表的是 当前时区的 2012-12-12 00:00:00., 对应 UTC 其实是: 2012-12-11 16:00:00, 少了8小时. 这里还顺便说明了Spark 入库 Date 数据的时候是带着时区的.
然后再看DateType cast toTimestampType 的代码, 可以看到buildCast[Int](_, d => DateTimeUtils.daysToMillis(d, timeZone) * 1000)
, 这里是带着时区的, 但是 Spark SQL 默认会用当前机器的时区. 但是大家一般底层数据比如这个2016-09-30, 都是代表的 UTC 时间, 在用 Spark 处理数据的时候, 这个时间还是 UTC 时间, 只有通过 JDBC 出去的时间才会变成带目标时区的结果. 经过摸索, 这里有两种解决方案:
- 配置 Spark 的默认时区
config("spark.sql.session.timeZone", "UTC")
, 最直观. 这样直接写df.select(df.col("birth").cast(TimestampType).cast(LongType))
就可以了. - 不配置 conf, 正面刚:
df.select(from_utc_timestamp(to_utc_timestamp(df.col("birth"), TimeZone.getTimeZone("UTC").getID), TimeZone.getDefault.getID).cast(LongType))
, 可以看到各种 cast, 这是区别:
- 没有配置 UTC:
from_utc_timestamp(to_utc_timestamp(lit("2012-12-11 16:00:00"), TimeZone.getTimeZone("UTC").getID), TimeZone.getDefault.getID)
- 配置了 UTC: 多了8小时
from_utc_timestamp(to_utc_timestamp(lit("2012-12-12 00:00:00"), TimeZone.getTimeZone("UTC").getID), TimeZone.getDefault.getID)
/**
* Returns true iff we can cast `from` type to `to` type.
*/
def canCast(from: DataType, to: DataType): Boolean = (from, to) match {
case (fromType, toType) if fromType == toType => true case (NullType, _) => true case (_, StringType) => true case (StringType, BinaryType) => true case (StringType, BooleanType) => true
case (DateType, BooleanType) => true
case (TimestampType, BooleanType) => true
case (_: NumericType, BooleanType) => true case (StringType, TimestampType) => true
case (BooleanType, TimestampType) => true
case (DateType, TimestampType) => true
case (_: NumericType, TimestampType) => true case (StringType, DateType) => true
case (TimestampType, DateType) => true case (StringType, CalendarIntervalType) => true case (StringType, _: NumericType) => true
case (BooleanType, _: NumericType) => true
case (DateType, _: NumericType) => true
case (TimestampType, _: NumericType) => true
case (_: NumericType, _: NumericType) => true
...
} private[this] def castToLong(from: DataType): Any => Any = from match {
case StringType =>
val result = new LongWrapper()
buildCast[UTF8String](_, s => if (s.toLong(result)) result.value else null)
case BooleanType =>
buildCast[Boolean](_, b => if (b) 1L else 0L)
case DateType =>
buildCast[Int](_, d => null)
case TimestampType =>
buildCast[Long](_, t => timestampToLong(t))
case x: NumericType =>
b => x.numeric.asInstanceOf[Numeric[Any]].toLong(b)
}
// TimestampConverter
private[this] def castToTimestamp(from: DataType): Any => Any = from match {
...
case DateType =>
buildCast[Int](_, d => DateTimeUtils.daysToMillis(d, timeZone) * )
// TimestampWritable.decimalToTimestamp
...
}
/**
* Given a timestamp, which corresponds to a certain time of day in the given timezone, returns
* another timestamp that corresponds to the same time of day in UTC.
* @group datetime_funcs
* @since 1.5.0
*/
def to_utc_timestamp(ts: Column, tz: String): Column = withExpr {
ToUTCTimestamp(ts.expr, Literal(tz))
} /**
* Given a timestamp, which corresponds to a certain time of day in UTC, returns another timestamp
* that corresponds to the same time of day in the given timezone.
* @group datetime_funcs
* @since 1.5.0
*/
def from_utc_timestamp(ts: Column, tz: String): Column = withExpr {
FromUTCTimestamp(ts.expr, Literal(tz))
}
Deep dive
配置源码解读:
val SESSION_LOCAL_TIMEZONE = buildConf("spark.sql.session.timeZone").stringConf.createWithDefaultFunction(() => TimeZone.getDefault.getID)
def sessionLocalTimeZone: String = getConf(SQLConf.SESSION_LOCAL_TIMEZONE)
/**
* Replace [[TimeZoneAwareExpression]] without timezone id by its copy with session local
* time zone.
*/
case class ResolveTimeZone(conf: SQLConf) extends Rule[LogicalPlan] {
private val transformTimeZoneExprs: PartialFunction[Expression, Expression] = {
case e: TimeZoneAwareExpression if e.timeZoneId.isEmpty =>
e.withTimeZone(conf.sessionLocalTimeZone)
// Casts could be added in the subquery plan through the rule TypeCoercion while coercing
// the types between the value expression and list query expression of IN expression.
// We need to subject the subquery plan through ResolveTimeZone again to setup timezone
// information for time zone aware expressions.
case e: ListQuery => e.withNewPlan(apply(e.plan))
} override def apply(plan: LogicalPlan): LogicalPlan =
plan.transformAllExpressions(transformTimeZoneExprs) def resolveTimeZones(e: Expression): Expression = e.transform(transformTimeZoneExprs)
} /**
* Mix-in trait for constructing valid [[Cast]] expressions.
*/
trait CastSupport {
/**
* Configuration used to create a valid cast expression.
*/
def conf: SQLConf /**
* Create a Cast expression with the session local time zone.
*/
def cast(child: Expression, dataType: DataType): Cast = {
Cast(child, dataType, Option(conf.sessionLocalTimeZone))
}
}
org.apache.spark.sql.catalyst.analysis.Analyzer#batches 可以看到有ResolveTimeZone
lazy val batches: Seq[Batch] = Seq( Batch("Resolution", fixedPoint,
ResolveTableValuedFunctions ::
ResolveRelations ::
ResolveReferences ::
...
ResolveTimeZone(conf) ::
ResolvedUuidExpressions ::
TypeCoercion.typeCoercionRules(conf) ++
extendedResolutionRules : _*),
Batch("Post-Hoc Resolution", Once, postHocResolutionRules: _*),
Batch("View", Once,
AliasViewChild(conf)),
Batch("Nondeterministic", Once,
PullOutNondeterministic),
Batch("UDF", Once,
HandleNullInputsForUDF),
Batch("FixNullability", Once,
FixNullability),
Batch("Subquery", Once,
UpdateOuterReferences),
Batch("Cleanup", fixedPoint,
CleanupAliases)
)
Test Example
对于时区理解
在不同的时区下 sql.Timestamp 对象的表现:
这里是 GMT+8:
Timestamp "2014-06-24 07:22:15.0"
- fastTime =
- "2014-06-24T07:22:15.000+0700"
如果是 GMT+7, 会显示如下,可以看到是同一个毫秒数
Timestamp "2014-06-24 06:22:15.0"
- fastTime =
- "2014-06-24T06:22:15.000+0700"
test("ColumnBatch") {
val schema = StructType(
Array(
StructField("id", IntegerType, nullable = true),
StructField("birth", DateType, nullable = true),
StructField("time", TimestampType, nullable = true)
)) val columnarBatch = ColumnarBatch.allocate(schema, MemoryMode.ON_HEAP, )
val c0 = columnarBatch.column()
val c1 = columnarBatch.column()
val c2 = columnarBatch.column() c0.putInt(, )
// 1355241600, /3600/24 s to days
c1.putInt(, / / )
// microsecond
c2.putLong(, 1355285532000000L) val internal0 = columnarBatch.getRow() //a way converting internal row to unsafe row.
//val convert = UnsafeProjection.create(schema)
//val internal = convert.apply(internal0) val enc = RowEncoder.apply(schema).resolveAndBind()
val row = enc.fromRow(internal0)
val df = spark.createDataFrame(Lists.newArrayList(row), schema) TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
val tsStr0 = df.select(col("time")).head().getTimestamp().toString
val ts0 = df.select(col("time").cast(LongType)).head().getLong() TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"))
val tsStr1 = df.select(col("time")).head().getTimestamp().toString
val ts1 = df.select(col("time").cast(LongType)).head().getLong() assert(true, "2012-12-12 04:12:12.0".equals(tsStr0))
assert(true, "2012-12-12 12:12:12.0".equals(tsStr1))
// to long 之后毫秒数都是一样的
assert(true, ts0 == ts1)
}
番外 : ImplicitCastInputTypes
我们自己定义了一个Expr, 要求接受两个 input 为 DateType 的参数.
case class MockExpr(d0: Expression, d1: Expression)
extends BinaryExpression with ImplicitCastInputTypes { override def left: Expression = d0 override def right: Expression = d1 override def inputTypes: Seq[AbstractDataType] = Seq(DateType, DateType) override def dataType: DataType = IntegerType override def nullSafeEval(date0: Any, date1: Any): Any = {
...
}
}
假设我们有如下调用, 请问这个调用符合预期吗? 结论是符合的, 因为有ImplicitCastInputTypes
.
lit("2012-11-12 12:12:12.0").cast(TimestampType)
lit("2012-12-12 12:12:12.0").cast(TimestampType)
Column(MockExpr(tsc1.expr, tsc2.expr))
org.apache.spark.sql.catalyst.analysis.TypeCoercion.ImplicitTypeCasts
case e: ImplicitCastInputTypes if e.inputTypes.nonEmpty =>
val children: Seq[Expression] = e.children.zip(e.inputTypes).map { case (in, expected) =>
// If we cannot do the implicit cast, just use the original input.
implicitCast(in, expected).getOrElse(in)
}
e.withNewChildren(children) def implicitCast(e: Expression, expectedType: AbstractDataType): Option[Expression] = {
implicitCast(e.dataType, expectedType).map { dt =>
if (dt == e.dataType) e else Cast(e, dt)
}
}
org.apache.spark.sql.catalyst.expressions.Cast#castToDate#DateConverter
private[this] def castToDate(from: DataType): Any => Any = from match {
case StringType =>
buildCast[UTF8String](_, s => DateTimeUtils.stringToDate(s).orNull)
case TimestampType =>
// throw valid precision more than seconds, according to Hive.
// Timestamp.nanos is in 0 to 999,999,999, no more than a second.
buildCast[Long](_, t => DateTimeUtils.millisToDays(t / 1000L, timeZone))
}
Spark DateType cast 踩坑的更多相关文章
- Spark踩坑记——从RDD看集群调度
[TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...
- ALS部署Spark集群入坑记
[Stage 236:> (0 + 0) / 400]17/12/04 09:45:55 ERROR yarn.ApplicationMaster: User class threw excep ...
- Spark 1.6升级2.x防踩坑指南
原创文章,谢绝转载 Spark 2.x自2.0.0发布到目前的2.2.0已经有一年多的时间了,2.x宣称有诸多的性能改进,相信不少使用Spark的同学还停留在1.6.x或者更低的版本上,没有升级到2. ...
- Spark踩坑记——Spark Streaming+Kafka
[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...
- Spark踩坑记——数据库(Hbase+Mysql)
[TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...
- Spark踩坑记——共享变量
[TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...
- [转]Spark 踩坑记:数据库(Hbase+Mysql)
https://cloud.tencent.com/developer/article/1004820 Spark 踩坑记:数据库(Hbase+Mysql) 前言 在使用Spark Streaming ...
- Spark踩坑记:共享变量
收录待用,修改转载已取得腾讯云授权 前言 前面总结的几篇spark踩坑博文中,我总结了自己在使用spark过程当中踩过的一些坑和经验.我们知道Spark是多机器集群部署的,分为Driver/Maste ...
- Spark踩坑记——数据库(Hbase+Mysql)转
转自:http://www.cnblogs.com/xlturing/p/spark.html 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库 ...
随机推荐
- mybatis 查询单个对象,结果集类型一定要明确
简单介绍:用ssm框架已经有很长时间了,但是似乎从来都没有对于查询单个对象,存在问题的,好像也就是那回事,写完sql就查出来了,也从来都没有认真的想过,为什么会这样,为什么要设置结果集类型 代码: / ...
- SQL 性能优化 总结
SQL 性能优化 总结 (1)选择最有效率的表名顺序(只在基于规则的优化器中有效): ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving ...
- Codeforces 715B. Complete The Graph 最短路,Dijkstra,构造
原文链接https://www.cnblogs.com/zhouzhendong/p/CF715B.html 题解 接下来说的“边”都指代“边权未知的边”. 将所有边都设为 L+1,如果dis(S,T ...
- win10下如何解决U盘连接上电脑但不显示的问题
问题:U盘插上电脑之后,任务栏上有U盘连接上的显示,但是在磁盘符和U盘管理器上没有它的显示. 方法: 1.在任务栏上点击win图标,再点击“设置”(或直接使用快捷键win+i)进入到win10下的“设 ...
- Imcash平台测评报告
ImCash是由全球知名量子基金(QuantumFund)与美国好事达保险公司 (ALL ) 联合投资美国区块链金融资本(BFC)打造全球首款量子基金数字资产服务平台 . ImCash作为全球首款量子 ...
- Facebook授权登录
1.注册开发者账号 登陆facebook开发者平台 (https://developers.facebook.com/), 注册facebook开发者账号. 2.Facebook登录Key Hash配 ...
- 2018-2019-1 20189201 《LInux内核原理与分析》第七周作业
我的愿望是 好好学习Linux 一.书本第六章知识总结[进程的描述和进程的创建] 基础知识1 操作系统内核实现操作系统的三大管理功能,即进程管理功能,内存管理和文件系统.对应的三个抽象的概念是进程,虚 ...
- 2017-11-4—模拟PID电路(参考ADN8834datasheet)
先贴几张datasheet原图: 这部分都很想了解,最想了解的是这四个zero point.pole point.pole point.zero point是怎么求出来的? 现在S域求出传函?(自动化 ...
- 07-MYSQL多表查询
今日任务 完成对MYSQL数据库的多表查询及建表的操作 教学目标 掌握MYSQL中多表的创建及多表的查询 掌握MYSQL中的表关系分析并能正确建表 昨天内容回顾: 数据库的创建 : crea ...
- 小甲鱼Python第十九讲课后习题
笔记: 1.内嵌函数:函数内部新创建另一个函数 2.闭包:函数式编程的重要语法,如果在一个内部函数里,对外部作用域(但不是在全局作用域的变量)进行引用,那么内部函数就会被认为是闭包. 3.nonloc ...