目前的工作需要在公司平台上通过hive导出数据,定时任务的权限没有开放给我,所以只能每天手动导,手动导数时又要天天手工改日期,很麻烦,所以想要where子句能够每天自动有对应的日期。以上是前提,有太多槽点,请忽略。
首先,要思考的是日期字段的数据类型,以及hive是否也有隐式转换
参考这篇文章 HIVE 数据类型转换
| bl | tinyint | si | int | bigint | float | double | dm | string | vc | ts | date | ba |
boolean | true | false | false | false | false | false | false | false | false | false | false | false | false |
tinyint | false | true | true | true | true | true | true | true | true | true | false | false | false |
smallint | false | false | true | true | true | true | true | true | true | true | false | false | false |
int | false | false | false | true | true | true | true | true | true | true | false | false | false |
bigint | false | false | false | false | true | true | true | true | true | true | false | false | false |
float | false | false | false | false | false | true | true | true | true | true | false | false | false |
double | false | false | false | false | false | false | true | true | true | true | false | false | false |
decimal | false | false | false | false | false | false | false | true | true | true | false | false | false |
string | false | false | false | false | false | false | true | true | true | true | false | false | false |
varchar | false | false | false | false | false | false | true | true | true | true | false | false | false |
ts | false | false | false | false | false | false | false | false | true | true | true | false | false |
date | false | false | false | false | false | false | false | false | true | true | false | true | false |
binary | false | false | false | false | false | false | false | false | false | false | false | false | true |
对应表中的日期字段day是以string的类型保存的,类似 '20190618',同时这个字段还是分区字段。。
1、datediff函数的问题
当执行时间为周一时,我希望日期限制的是上周五到周日,其他执行时间则仅昨日。那么加入个if判断以下执行时间是否为周一吧。
用pmod(datediff(current_date, '2012-01-01'), 7)判断,0~6对应周日至周一,没什么问题吧?
然后就出问题了,出来的星期不对。经过排查,锁定以下问题
select datediff(current_date, '2019-06-18')
, datediff('2019-06-19', '2019-06-18')
, current_date, current_date = '2019-06-19'
结果如下
0 | 1 | 2019-06-19 | true |
我百思不得其解,只能百度。
--------------------> Hive SQL中的datediff、current_date使用问题 <---------------------------
[ https://issues.apache.org/jira/browse/HIVE-18304?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16297824#comment-16297824
]
Hengyu Dai commented on HIVE-18304:
-----------------------------------
SimpleDateFormat.parse(String source) method will convert String type(UTC) to java.util.Date
type(use current JVM timezone), this may lead deviations in time when JVM timezone is not
UTC, my environment is GMT+8, 8 hours is added comparing to the UTC time.
while for a date type argument, the default JVM timezone is used.
The patch uploaded treats String type and Date type at the same way to remove the deviations.
> datediff() UDF returns a wrong result when dealing with a (date, string) input
> ------------------------------------------------------------------------------
>
> Key: HIVE-18304
> URL: https://issues.apache.org/jira/browse/HIVE-18304
> Project: Hive
> Issue Type: Bug
> Components: UDF
> Reporter: Hengyu Dai
> Assignee: Hengyu Dai
> Priority: Minor
> Attachments: 0001.patch
>
>
> for date type argument, datediff() use DateConverter to convert input to a java Date
object,
> for example, a '2017-12-18' will get 2017-12-18T00:00:00.000+0800
> for string type argument, datediff() use TextConverter to convert a string to date,
> for '2012-01-01' we will get 2012-01-01T08:00:00.000+0800
> now, datediff() will return a number less than the real date diff
> we should use TextConverter to deal with date input too.
> reproduce:
> {code:java}
> select datediff(cast('2017-12-18' as date), '2012-01-01'); --2177
> select datediff('2017-12-18', '2012-01-01'); --2178
> {code}
大体意思就是对,date类型和string类型的两个参数,hive调用了不同的函数处理,处理出来的带时间的日期数据相减。
而要想解决的话很简单,统一两个参数的数据类型就行了。
select datediff(current_date, cast('2019-06-18' as date))
, datediff(to_date(current_date), '2019-06-18')
2、使用hiveconf变量
把这么长的式子写在where后面实在太难看,所以我决定使用变量传参的方式
找到了以下文章
hive中的参数
set end_day = '20190618';
select * where day = ${hiveconf:end_day};
另,当某些时候用参数传递给分区字段报错时,加入下面这段
SET hive.exec.dynamic.partition.mode = nonstrict;
3、避免科学计数法
开始时,我使用的是这种方法,
select from_unixtime(unix_timestamp(), 'yyyyMMdd') - 1
结果得到的是 2.0190618E7
用这个作为where分区限制时,报错
Error while compiling statement: FAILED: SemanticException [Error 10041]: No partition predicate found
我猜测又是类型的问题
所以我使用cast显式转换格式成string,但科学计数法表示的数字转换成字符,那画面实在太美。
所以,为了解决这种计算结果改变数据类型的情况,
cast(bigint(from_unixtime(unix_timestamp(), 'yyyyMMdd') - 1 )as string)
我套了一个bigint
好了,没问题了,但是仍然报错。
ps: string形式形如’20190618‘格式的分区字段,用
cast(bigint(from_unixtime(unix_timestamp(), 'yyyyMMdd') - 1 )as string) 报错
用 cast(bigint('20190619'- 1 )as string) 没事。可能还是数据类型的问题,但我已放弃治疗
3、最后的解决方案
SET start_day = if(pmod(datediff(to_date(current_date), '2012-01-01'), 7) = 1, regexp_replace(date_sub(current_date, 3), '-', ''), regexp_replace(date_sub(current_date, 1), '-', ''));
SET end_day = regexp_replace(date_sub(current_date, 1), '-', '');
hive的数据类型实在是个坑