Presto环境下Spark SQL timestamp转string调用get_spark_df报错
问题排查思路
- 先查日期合法性:公历6月是小月,满算只有30天,你代码里写的
end_date = '2019-06-31'、给出的时间样例2019-06-31 19:00:00本身就是非法日期值。从报错栈可以看到SQL是下推到Presto引擎执行的,Presto解析到非法日期会直接抛出执行异常,这是最高概率的触发原因。 - 再查SQL语法兼容性:
get_spark_df通过JDBC连接拉数时,传入的子查询是直接在底层数据源(这里是Presto)执行,不是Spark原生执行。你写的date_format(xxx, '%Y%m%d')是Spark/MySQL的格式化语法,Presto的date_format遵循Java DateTimeFormatter规范,格式符要用yyyyMMdd,格式符不匹配会直接导致执行失败。 - 最后查过滤逻辑合理性:你在WHERE条件里对timestamp字段套函数转成INT再比较,一方面会让底层引擎无法命中时间字段的索引,全表扫描性能极差;另一方面如果字段里存在非法时间脏数据,转换过程中碰到坏值就会直接中断任务,只要表中有一条脏数据整个任务就会失败。
可行解决方案
优先选适配下推逻辑的写法,性能最好:
# 第一步先修正非法日期值 start_date = '2019-06-15' end_date = '2019-06-30' records = QaasHook.get_spark_df(""" (SELECT name, date_format(startdt, 'yyyyMMdd') AS stdt, cast(amnt AS DOUBLE) AS amount, date_format(enddt, 'yyyyMMdd') AS endt FROM table_1 -- 不要对字段套函数做比较,直接用时间范围过滤,支持索引命中,也避免转换脏数据报错 WHERE startdt >= CAST('{st_dt}' AS TIMESTAMP) AND enddt < CAST('{end_dt}' AS TIMESTAMP) + INTERVAL '1' DAY ) """.format( st_dt = start_date, end_dt = end_date ))
如果表中确实存在类似6月31日的脏时间数据,可以把cast换成try_cast,转换失败的脏数据会返回null,再加一层is not null过滤即可。
如果不想反复适配不同引擎的SQL语法差异,可以把原始时间字段拉到Spark层再做格式化,完全规避下推语法兼容问题:
start_date = '2019-06-15' end_date = '2019-06-30' # 下推的SQL只做原始数据拉取和范围过滤,不做格式化 records = QaasHook.get_spark_df(""" (SELECT name, startdt, amnt, enddt FROM table_1 WHERE startdt >= CAST('{st_dt}' AS TIMESTAMP) AND enddt < CAST('{end_dt}' AS TIMESTAMP) + INTERVAL '1' DAY ) """.format(st_dt = start_date, end_dt = end_date)) # 所有格式化、类型转换逻辑在Spark层执行,用Spark原生语法,不存在兼容问题 from pyspark.sql import functions as F result = records.select( F.col('name'), F.date_format(F.col('startdt'), 'yyyyMMdd').alias('stdt'), F.col('amnt').cast('DOUBLE').alias('amount'), F.date_format(F.col('enddt'), 'yyyyMMdd').alias('endt') )
内容的提问来源于stack exchange,提问作者Tahir Zamaan
相关产品推荐
相关产品推荐

