Как оптимизировать запрос с помощью встроенной функции? - PullRequest
0 голосов
/ 17 февраля 2020

У меня есть таблица TAB_1 со следующей схемой

    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 хранит дату в качестве значения метки времени, я пытаюсь найти все автомобили, которые заканчиваются 10 февраля-2020 года. Данные могут отсутствовать в любом из таблиц, поэтому я использовал операцию UNION здесь, в верхней части этого запроса я должен применить больше операций, так что в целом это занимает около 35 секунд. Пожалуйста, руководство для оптимизации этого запроса.

Ответы [ 3 ]

1 голос
/ 17 февраля 2020

Странный способ справиться с датой и временем, я думаю. Прежде всего, изучите ваш план выполнения.

Я полагаю, что при небольшом количестве данных он работает как чудо, но при большом количестве данных производительность снижается.

Начиная с UNION, это проблема производительности сама по себе.

Встроенные операции, которые вы выполняете в TAB_1, запрещают использование индексов. Я надеюсь, что у вас есть индексы в полях ACTUAL_ARRIVAL_TIME и ACTUAL_ARRIVAL_TIME, но в вашем случае они не используются. Я полагаю, что этот запрос находится в середине процесса, поэтому вы можете сделать небольшой обходной путь перед его выполнением

1. Определите начальную минуту для своего периода: например, параметр 'startMinute'.

2. Знайте свою конечную минуту для своего периода: например, параметр '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, но также с использованием trun c, не использует индекс. Возможно, вам нужно создать индекс на основе функции TRUN C (ACTL_TIME_OF_ARRVL) Индекс. Я уверен, что вы найдете следующую ссылку очень интересной:

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

1 голос
/ 17 февраля 2020

Я бы предложил начать с переписывания запроса следующим образом:

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

Вы также можете перефразировать запрос, если вы хотите включить в индекс компонент времени даты. Неясно, будет ли это полезно для других запросов.

1 голос
/ 17 февраля 2020

Использование trunc() для значения столбца предотвратит использование индекса для этого столбца (если это не индекс на основе функций). Лучше использовать диапазон дат, охватывающий весь интересующий вас день. Добавление временного смещения к start_date также повлияет на индекс; и '10-Feb-2020' - это строка, а не дата, так что вы выполняете неявное преобразование - никогда не бывает хорошей идеей. Вам также не нужно distinct с union как (в отличие от 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 к желаемой дате вместо жесткого кодирования через день. В любом случае, будут искать значения в или после полуночи первой даты и до полуночи второй даты - что охватывает все возможные времена в этот день.

Первая ветвь все еще может ' на самом деле использовать индекс правильно из-за изменчивости ссылки на другое значение столбца в расчете диапазона, но если вы знаете, например, что 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 как виртуальный столбец и проиндексировать его.

...