Необычное время выполнения функций даты в Oracle - PullRequest
3 голосов
/ 19 июля 2010

Я выполняю запрос, который возвращает мне коллекцию объектов дат по месяцам между определенным диапазоном дат. Запрос работает нормально, но очень медленно (~ 2 секунды на моей локальной машине, ~ 30 в нашей корпоративной среде разработки). Вот оно:

SELECT ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rownum) AS MONTH
FROM all_objects
WHERE ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rownum) <= TO_DATE('200805', 'YYYYMM')

В настоящее время он вернет только один месяц, но если вы расширите вторую строку даты, он вернет больше.

У меня два вопроса. Во-первых, почему это работает так медленно? Я знаю, что функции Oracle действительно замедляют запрос, но на моей машине разработки это занимает около 30 секунд.

Второй, более загадочный вопрос: почему время выполнения сокращается до доли секунды, когда вы расширяете диапазон, скажем, до «201805»? Я думаю, что больший диапазон займет больше времени. Вроде бы обратный эффект.

Ответы [ 4 ]

4 голосов
/ 19 июля 2010

Используйте это вместо

SELECT ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rn) AS MONTH
FROM (select level rn from dual connect by level < 4000)
WHERE ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rn) <= TO_DATE('200805', 'YYYYMM')
;

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

all_objects - сложное представление, поэтому оно не будет таким же производительным, как встроенноевид используется выше.Если вы не хотите использовать синтаксис «connect by», создайте таблицу целых чисел и используйте ее.

3 голосов
/ 19 июля 2010

Небольшой вариант функции Янека, которая избавляется от произвольного ограничения в 4000 месяцев с помощью функции MONTHS_BETWEEN ()

SELECT ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rn) AS MONTH 
  FROM ( select level rn 
           from dual 
           connect by level < abs(months_between(TO_DATE('200804', 'YYYYMM'),TO_DATE('201805', 'YYYYMM')))+2
       ) 
 WHERE ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rn) <= TO_DATE('201805', 'YYYYMM') 
; 
2 голосов
/ 20 июля 2010

Нет необходимости использовать встроенные представления, и я вижу слишком много используемых функций даты. Если вы пропустите все это, это останется:

SQL> var START_YM varchar2(6)
SQL> var END_YM varchar2(6)
SQL> exec :START_YM := '200804'; :END_YM := '201805'

PL/SQL procedure successfully completed.

SQL>  select add_months(to_date(:START_YM,'yyyymm'),level-1) m
  2     from dual
  3  connect by level <= months_between(to_date(:END_YM,'yyyymm'),to_date(:START_YM,'yyyymm'))+1
  4  /

M
-------------------
01-04-2008 00:00:00
01-05-2008 00:00:00
01-06-2008 00:00:00
<... 116 rows skipped ...>
01-03-2018 00:00:00
01-04-2018 00:00:00
01-05-2018 00:00:00

122 rows selected.

Что выглядит еще проще ...

С уважением, Роб.

0 голосов
/ 21 июля 2010

Часть сложности здесь заключается в том, что для каждой строки в представлении ALL_OBJECTS необходимо оценить ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rownum).Если переписать предложение where, оно будет использовать другой план с COUNT STOPKEY вместо COUNT.

Вместо этого попробуйте выполнить приведенный ниже запрос.У меня это работало намного быстрее.

SELECT ADD_MONTHS(TO_DATE('200804', 'YYYYMM'), -1+rownum) AS MONTH
FROM all_objects
where 
  months_between(date '2008-05-01, date '2008-04-01') >= rownum

Комментарий, который вы сделали об использовании 201805 для ускорения выполнения запроса, на самом деле неверен.Запрос не выполняется быстрее, он просто возвращает первые строки быстрее, поэтому он выглядит быстрее.

С датой окончания, установленной на 2008-05-01, он должен пройти через всю таблицу ALL_OBJECTS, прежде чем возвращать какие-либо строки, но с более длительным периодом времени он вернет вам строки, когда буферполный.Каждый запрос будет выполнен до завершения в одно и то же время.

...