带有当前日期时间表达式条件的查询效率提升方法

简介: 针对含有日期表达式的计算字段参与到表达式查询从而导致系统效率低下的情况,通过代数转换来充分利用数据库索引达到提升效率的目的

一、问题背景

在实际的业务需求中,经常会遇到根据业务操作的当前时间对数据进行查询的场景,例如库存管理中对保质期的查询,以便了解即将会有多少商品即将超过保质期限,类似下图所示:

image.png

在这样的报表中,往往会存在增加一个计算字段的诉求,这个字段(临期天数)是在页面加载的时候通过系统计算自动得到的,因而在数据库中不会存储。

在这样的报表开发过程中,增加一个含有日期表达式的计算字段是一个常见的诉求,比如上图中的临期天数(expired_days),就是由“保质日期”(expiration_date)和“当前日期”(current_date())计算得到的,其计算公式为:

expired_days = expiration_date - current_date()

在报表展示的时候,该字段由计算引擎(前端js或者serverjava)实时算出expired_days的值之后展示在界面上,因此在后台数据库中也不会存储。

这时一个问题就浮出水面了:如何支持针对字段expired_days的高效过滤?这对高时效要求的业务来说是迫切需要解决的问题。

 

二、思考分析

思路一:条件直接拼接

直接把expired_days的公式提交给DB,假设要查询“3天内到期的数据”,过滤条件是:“expired_days ”<  3;直接把expiration_date  - current_date() 拼接到sqlwhere条件里提交给DB

这样会存在一个问题:由于对expiration_date字段引入了表达式,查询时没办法走索引,会导致查询效率很低下,在数据量很大的情况下,查询接口一定会超时。


思路二:条件转换

那如果在过滤的时候,由应用程序先把current_date()的值计算好之后,再提交给DB呢?比如:

expiration_date – ‘2021-8-17’ < 3

经测试,这种条件在mysqlpostgresql中,查询时同样不能使用expiration_date字段的索引,因而也不解决问题。


思路三:表达式索引

我们知道,在postgresqlmysql的高版本中已经支持了表达式索引,可不可以把expiration_date - current_date() 建立一个表达式索引呢?

这样做的结果也不行,因为这里的current_date()取值就是建立索引那一刻的值(即创建索引的DDL执行的时刻),而我们业务的实际需求是current_date()应该取查询过滤那一刻的值,不满足业务含义。


思路四:代数转换

我们的目标是在查询时能够使用到expiration_date字段的索引,而我们的过滤表达式为:

expiration_date – ‘2021-8-17’ < 3

通过前面的分析我们了解到,DB之所以不走expiration_date的索引的核心原因是:过滤表达式的左侧只能是expiration_date这个字段名,而不能是包含expiration_date的任何表达式,那么这样方案就出来了:我们可以由应用程序把上述表达式做一个简单的变化,变成以下形式:

expiration_date  <  3 + ‘2021-8-17’

然后再把这个表达式提交给DB,这样就能使用索引,实现高效查询了。

 

三、解决方案

最终我们选择思路四来提升查询的效率,其中关键的一步是做表达式的转换,这在数学上称为“代数转换”。幸运的是,Java方面已经有开源的 代数转换 的类库,我们只要集成到我们的服务里来就可以了(参考:https://github.com/axkr/symja_android_library)。另外,在Python中也有类似的类库,我们可以根据自己的语言需要选择使用。

 

四、效果对比

下面将对“代数转换”前后的查询性能进行一个对比测试验证。

验证测试条件

数据库

PostgreSQL

数据表

 inventory_store

数据量

2373w数据,其中expiration_date是时间戳字段,有索引

“代数转换”前

SQL脚本:

select  * from inventory_store

where expiration_date - 1630425600000 > 748800000

limit 10

执行结果:

image.png


耗时:15.354

结果分析:

   由于使用了全表扫描,未能使用索引,导致查询效率较低。


 “代数转换”后

SQL脚本:

select  * from inventory_store

where expiration_date > 1630425600000 + 748800000

limit 10

执行结果:

image.png



耗时:0.14 毫秒

结果分析:

   由于使用expiration_date索引,查询效率明显提升。

             结论:通过“代数转换”方式,在本测试案例中提升查询效率99%以上,能够有效解决带有当前日期时间表达式条件查询效率问题。

相关文章
|
5月前
解决全网90%以上的日期格式转换、日期序列等骚操作问题
解决全网90%以上的日期格式转换、日期序列等骚操作问题
解决全网90%以上的日期格式转换、日期序列等骚操作问题
|
3月前
时间函数举例2
时间函数举例2。
18 3
|
3月前
时间函数举例1
时间函数举例1。
18 3
|
3月前
时间函数举例
【2月更文挑战第2天】时间函数举例。
16 3
|
9月前
|
Java
高效实现区间条件过滤:Java字符串转换为条件语句
高效实现区间条件过滤:Java字符串转换为条件语句
45 0
【SQL开发实战技巧】系列(十七):数据仓库中时间类型操作(初级)确定两个日期之间的工作天数、计算—年中周内各日期出现次数、确定当前记录和下一条记录之间相差的天数
如何确定两个日期之间的工作日有多少天、计算—年中每周内各日期出现次数、确定当前记录和下一条记录之间相差的天数【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。本章节的三个需求:确定两个日期之间的工作天数、计算—年中周内各日期出现次数、确定当前记录和下一条记录之间相差的天数有些许难度,不过建议还是学会比较好。
【SQL开发实战技巧】系列(十七):数据仓库中时间类型操作(初级)确定两个日期之间的工作天数、计算—年中周内各日期出现次数、确定当前记录和下一条记录之间相差的天数
使用lambda去重、map排序、按任意时间间隔(小时、半小时、分钟)进行结果统计
使用lambda去重、map排序、按任意时间间隔(小时、半小时、分钟)进行结果统计
214 0
使用lambda去重、map排序、按任意时间间隔(小时、半小时、分钟)进行结果统计
|
关系型数据库 MySQL
ES复杂操作-布尔值查询(多条件精确查询)
ES复杂操作-布尔值查询(多条件精确查询)
|
存储 Unix 关系型数据库
关于日期及时间字段的查询
在项目开发中,一些业务表字段经常使用日期和时间类型,而且后续还会牵涉到这类字段的查询。关于日期及时间的查询等各类需求也很多,本篇文章简单讲讲日期及时间字段的规范化查询方法。
202 0
关于日期及时间字段的查询
|
SQL 关系型数据库 MySQL
MYSQL查询近一年 近一月 近一周 今天数据 没有数据返回0 按时间有序返回数据
MYSQL查询近一年 近一月 近一周 今天数据 没有数据返回0 按时间有序返回数据
591 0
MYSQL查询近一年 近一月 近一周 今天数据 没有数据返回0 按时间有序返回数据