【问题标题】:How to optimize query with Inline Function in it?如何使用内联函数优化查询?
【发布时间】:2020-02-17 11:06:16
【问题描述】:

我有一个表 TAB_1 具有以下 Schema

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

具有以下架构的表 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 将日期存储为时间戳值,我试图找到所有在 2 月 10 日终止的汽车- 2020 . 任何表中都可能不存在数据,因此我在这里使用了 UNION 操作,在此查询之上我必须应用更多操作,因此总体而言大约需要 35 秒的时间。请指导优化此查询。

【问题讨论】:

  • 样本数据和期望的结果会很有帮助。

标签: sql oracle query-performance


【解决方案1】:

在列值上使用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' minute 提供与ACTUAL_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 &gt;= date '2020-02-10' 为索引提供了搜索的下限(假设actual_arrival_time 不能为负数,这似乎是合理的); start_date &lt; 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 作为虚拟列,并为其编制索引。

【讨论】:

  • 你能解释一下 1) start_date >= date '2020-02-10' - 间隔'1'天,这里我们需要日期 10Feb ,但是从间隔'1'天减去,会导致2 月 9 日) start_date + actual_arrival_time * interval '1' 分钟,为什么我们要乘 actual_arrival_time*interval '1' 分钟
  • @radha - 我不确定 (1) 是否完全正确,假设 actual_arrival_time 不能为负数,所以我已经调整并添加了进一步的解释;并添加了关于 (2) 的注释。
【解决方案2】:

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

我想用很少的数据它就像一个魅力,但有了很多数据,性能就消失了。

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

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

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

2.了解您的月经结束时间:例如参数'endMinute'。

3.重写查询

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) 索引。 我相信您会发现以下链接非常有趣:

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

【讨论】:

    【解决方案3】:

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

    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));
    

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

    【讨论】:

      猜你喜欢
      • 2017-03-04
      • 1970-01-01
      • 2015-11-22
      • 1970-01-01
      • 2018-04-07
      • 2022-01-27
      • 2016-10-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多