本文记录了将给定的日期范围用通配符表示的相关内容,欢迎与我沟通联系(zhaoliang19960421@outlook.com)
如果在使用中发现程序输入与结果不符,欢迎与我沟通
当前大数据开发中,数据通常以时间date为分区保存在hdfs中,例如 hdfs://abc/date=20220101
在主流的两大大数据开发框架MapReduce、Spark中对于单个日期数据的输入形式一样,都是直接输入对应日期的hdfs路径即可。
在跨分区读取数据时,MapReduce提供了时间的通配符表示法,例如要读取2022年1月份整月的数据,可以表示成202201*
spark相较于MR提供了类似于sql的时间范围写法
sparkSession.read.parquet(s"hdfs://abc")
.where(s"date between $startDate and $endDate")
但是该方法仍存在不足:在read.parquet(s"hdfs://abc")实际上将该路径下的所有分区都扫描一遍,找到所有满足where条件的分区
这样做虽然并没有实际的将数据读进来,但是仍然需要暴力的扫描整个路径,以便找到满足条件的结果。如果上游数据分区特别多时,采用该方案耗时仍然很高
为了解决该问题,在spark读取路径时可以采用指定输入地址路径列表的方式来避免暴力扫描,也就是将[startDate,endDate]时间范围内的路径直接写出来,不在进行where判断,直接去读对应的时间分区。但是这样实际开发中很不方便,例如要计算2022年1月份的全部数据,需要写31个路径,即使通过写循环语句来解决仍然不够优雅。
在spark中同样支持如MR的通配符表示法,也就是用202201*来表示22年1月份的全部数据,采用这样的方式即避免了暴力扫描的耗时,也避免了写31个路径以及写循环的复杂性。
具体含义以几个case进一步解释
case1 日期范围是:20220101 20221231
解释:需要输入的是2022年一整年的数据,所以采用通配符的表达方式为2022*
case2 日期范围是 : 20210101 20210331
解释:需要输入的2021年,1月、2月、3月的全部日期,采用通配符的表示方式是 '202101*', '202102*', '202103*'
case3 日起范围是:20200104 20200302
解释:需要输入的是,从1月4号到1月底,2月整月、3月1号,3月2号的日期,采用通配符的表示方式是 ['20200104', '20200105', '20200106', '20200107', '20200108', '20200109', '2020011*', '2020012*', '2020013*', '202002*', '20200301', '20200302']
因为1月4号到1月9号,无法继续采用通配符,所以全部枚举;1月10号到1月19号、1月11号到19号、1月21号到1月29号、1月30号到1月31号,均可以用通配符。
将以上问题抽象为数学问题是,给定一个函数,输入的是2个字符串[start,end]的日期范围,输出一个字符串数组,这个字符串数组是给定日期范围的通配符表示式的最简结果
该问题采用递归的方式来解决,具体内容见python、Scala代码
def timeWindow2timeWildcard(start: str, end: str) -> list:
"""
将给定的时间范围 [start,end] 转化成 最简的时间通配符表达式
:param start: 开始时间,左闭
:param end: 结束时间,右闭
:return: list[str] 整个list的结果是时间范围的最简单的时间通配符结果
"""
def _yyyyMM2dd(yyyy, MM):
"""
输出给定年、月的日期
"""
if MM in {
1, 3, 5, 7, 8, 10, 12}:
return 31
if MM != 2:
return 30
if (yyyy % 4 == 0) and (yyyy % 100 != 0) or (yyyy % 400) == 0:
return 29
else:
return 28
def _check(yyyyMMdd):
"""
检查日期是否合理,
"""
date = _yyyyMM2dd(int(yyyyMMdd[:4]), int(yyyyMMdd[4:6]))
if 1 <= int(yyyyMMdd[6:8]) <= date:
return yyyyMMdd
if 1 > int(yyyyMMdd[6:8]):
return "{}01".<