如何使用内联函数优化查询?

How to optimize query with Inline Function in it?

我有一个 table TAB_1 具有以下架构

    CAR_NO(Varchar)  START_DATE(Date)  ACTUAL_ARRIVAL_TIME(Number)  SOURCE_POINT(Varchar) 
   END_POINT(Varchar)

Table TAB_2 具有以下架构

CAR_NO(Varchar)   ACTL_TIME_OF_ARRVL(Date)  EVENT_CODE(Varchar) 

我的查询是:

 SELECT DISTINCT CAR_NO,START_DATE FROM TAB_1 WHERE
(TRUNC(START_DATE +CASE WHEN ACTUAL_ARRIVAL_TIME=0 THEN NULL ELSE ACTUAL_ARRIVAL_TIME END/1440)='10-Feb-2020' )  
    AND SOURCE_POINT=END_POINT
    UNION
    SELECT DISTINCT  CAR_NO,START_DATE FROM TAB_2 WHERE EVENT_CODE='TD' 
    AND TRUNC( ACTL_TIME_OF_ARRVL)='10-Feb-2020' 

ACTUAL_ARRIVAL_TIME存储以分钟为单位的时间值,列ACTL_TIME_OF_ARRVL存储日期作为时间戳值,我试图找到所有在 2020 年 2 月 10 日终止的汽车。table 中的任何一个都可能缺少数据,所以我在这里使用了 UNION 操作,在此查询之上我必须应用更多操作,因此总体而言大约需要 35 秒的时间。请指导优化此查询。

在列值上使用 trunc() 将阻止使用该列上的索引(除非它是基于函数的索引)。最好使用日期范围,涵盖您感兴趣的一整天。将时间偏移添加到 start_date 也会影响索引; '10-Feb-2020' 是一个字符串而不是日期,所以你强制隐式转换 - 从来都不是一个好主意。您也不需要 distinctunion 作为(与 union all 不同)无论如何都可以消除重复项。

我建议这样作为起点:

select car_no, start_date
from tab_1
where source_point = end_point
and start_date >= date '2020-02-10' - actual_arrival_time * interval '1' minute
and start_date < date '2020-02-11' - actual_arrival_time * interval '1' minute
union
select car_no, start_date
from tab_2
where event_code='TD' 
and actl_time_of_arrvl >= date '2020-02-10'
and actl_time_of_arrvl < date '2020-02-11'

actual_arrival_time * interval '1' minuteACTUAL_ARRIVAL_TIME END/1440 的效果相同;第一个是间隔数据类型,第二个是一天的小数部分,但两者都将分钟数表示为可以添加到日期值的值。

我使用的是日期文字,它仍然是硬编码的。如果您真的在使用参数,则可以将 interval '1' day 添加到您想要的日期,而不是在一天后进行硬编码。无论哪种方式,都会在第一个日期的午夜或之后查找值,并在第二个日期的 before 午夜查找值 - 涵盖当天所有可能的时间。

第一个分支仍然不能真正正确使用索引,因为在范围计算中引用另一个列值的可变性,但是如果您知道例如 actual_arrival_time 在 24 小时内,您可以帮助它解决更严格的限制:

select car_no,start_date
from tab_1
where source_point = end_point
and start_date >= date '2020-02-10'
and start_date < date '2020-02-10' + interval '2' day -- depending on allowed ranges
and start_date + actual_arrival_time * interval '1' minute >= date '2020-02-10'
and start_date + actual_arrival_time * interval '1' minute < date '2020-02-10' + interval '1' day 
union
select car_no, start_date
from tab_2
where event_code='TD' 
and actl_time_of_arrvl >= date '2020-02-10'
and actl_time_of_arrvl < date '2020-02-10' + interval '1' day

这里start_date >= date '2020-02-10'给索引一个下界去搜索(假设actual_arrival_time不能为负数,这似乎是合理的); start_date < date '2020-02-10' + interval '2' day 给出上限。上限是多少取决于允许的值,特别是 actual_arrival_time.

在您的问题中不清楚 tab1.start_date 是否总是午夜,因此您可以将检查的第一部分简化为精确的日期匹配而不是范围。但是然后查看 actual_arrival_time 可能没有必要......如果 start_date 实际上总是午夜并且 actual_arrival_time 被限制在 0 到 1440 之间那么它可以像这样简单:

select car_no,start_date
from tab_1
where source_point = end_point
and start_date >= date '2020-02-10'
and start_date < date '2020-02-10' + interval '1' day
union
...

就像第二个分支一样。但是您尝试处理它的方式表明情况可能并非如此,您只需要尽可能缩小初始索引搜索范围,然后再根据确切时间进行过滤。我怀疑 actual_arrival_time 可能代表几天,甚至几周或几个月;所以回到以前的版本,

and start_date < date '2020-02-10' + interval '2' day -- depending on allowed ranges

会扩展到您期望看到的最大值;或者可能完全省略。

您需要查看执行计划以了解它实际上在做什么,可能是针对整个查询和联合的每个分支。

如果您要经常这样做,可能值得将计算值 start_date + actual_arrival_time * interval '1' minute 添加到 tab1 作为虚拟列,并为其编制索引。

我猜是处理日期和时间的奇怪方式。 首先检查你的执行计划。

我想在数据很少的情况下效果很好,但在数据很多的情况下,性能就没了。

从 UNION 开始,它本身就是一个性能问题。

您在 TAB_1 中所做的内联操作取消了索引的使用。我希望你在 ACTUAL_ARRIVAL_TIME 和 ACTUAL_ARRIVAL_TIME 字段中有索引,但在你的情况下它们没有被使用。 我想这个查询是在一个进程的中间,所以你可以在执行它之前完成一些解决方法

1.Know 您的月经开始时间:例如参数 'startMinute'.

2.Know 您经期的结束分钟:例如参数 'endMinute'.

3.Rewite查询

SELECT DISTINCT CAR_NO,START_DATE 
  FROM TAB_1 
 WHERE START_DATE >=  startMinute
   AND START_DATE <   endMinute
   AND SOURCE_POINT = END_POINT
UNION
.
.
.

我也希望你在TAB_2.ACTL_TIME_OF_ARRVL有一个索引,而且用的是trunc,不使用索引。 也许您需要创建一个基于 函数的索引 TRUNC(ACTL_TIME_OF_ARRVL) 索引。 我相信您会发现以下 link 非常有趣:

https://blog.dbi-services.com/index-on-truncdate-do-you-still-need-old-index/

我建议首先将查询重写为:

SELECT CAR_NO, START_DATE
FROM TAB_1
WHERE SOURCE_POINT = END_POINT AND
      TRUNC(START_DATE + ACTUAL_ARRIVAL_TIME * INTERVAL '1' MINUTE) = DATE '2020-02-10'
UNION -- ON PURPOSE TO REMOVE DUPLICATES
SELECT CAR_NO, START_DATE
FROM TAB_2
WHERE EVENT_CODE = 'TD' AND
      TRUNC(ACTL_TIME_OF_ARRVL) = DATE '2020-02-10' ;

那么对于这个查询,你可以定义以下基于函数的索引:

create index idx_tab_1_f1 on tab_1 (TRUNC(START_DATE + ACTUAL_ARRIVAL_TIME * INTERVAL '1' MINUTE), SOURCE_POINT, END_POINT);

create index idx_tab_2_f2 on tab_2 (EVENT_CODE, TRUNC(ACTL_TIME_OF_ARRVL));

如果要在索引中包含日期的时间部分,也可以改写查询。目前还不清楚这对其他查询是否有用。