Oracle SQL To_Char(интервал) не использует мою строку формата?

#sql #oracle

Вопрос:

В следующем фрагменте SQL я хочу напечатать интервал (на самом деле несколько секунд) в формате HH:MM:SS. Я преобразую секунды в интервал, а затем пытаюсь использовать форматирование даты TO_CHAR. Сервер Oracle игнорирует мой шаблон и печатает целую метку времени с нулевыми символами в датах и микросекундах. Что я делаю не так?

 CASE WHEN (TR.RUN_LENGTH > 0) THEN TO_CHAR(NUMTODSINTERVAL(TR.RUN_LENGTH,'second'), 'HH24:MI:SS')
ELSE '0' END AS RUN_LENGTH,
 

И я получаю: 000000000 00:03:22.000000000

Комментарии:

1. Вы должны использовать SUBSTR или REGEXP_SUBSTRING

2. Что вам нравится получать с интервалами более 24 часов?

3. Я собирался рекомендовать сначала выполнить приведение interval day(2) to second(0) , а затем использовать substr() 5 — й символ, однако я вижу, что теперь в 21c с ORA-25137 не получается: значение данных выходит за пределы диапазона .

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

Ответ №1:

Как вы заметили, to_char() не работает с интервалами. Так что не думайте в терминах интервалов. Вместо этого у вас есть несколько секунд, и вы хотите преобразовать их в строку в формате ЧЧ:ММ:СС.

Используйте арифметические и строковые операции:

 select lpad(floor(tr.run_length / 3600), 2, '0') || ':' || lpad(mod(floor(tr.run_length / 60),  60), 2, '0') || ':' || lpad(mod(tr.run_length, 60), 2, '0') as hhmmss
from (select 1 as run_length from dual union all
      select 10 as run_length from dual union all
      select 100 as run_length from dual union all
      select 1000 as run_length from dual union all
      select 10000 as run_length from dual union all
      select 100000 as run_length from dual 
     ) tr;
 

Вот скрипка db<>.

Ответ №2:

Я просмотрел другие связанные ответы; Ясно, что Oracle на самом деле не реализует указанный пользователем формат для TO_CHAR(DSINTERVAL). Как некоторые предположили, вы можете добавить дату, а затем использовать форматирование даты. Но это не удается, если интервал составляет более 24 часов.

В итоге я использовал REGEXP_SUBSTRING (), но затем столкнулся с проблемой, что Oracle, очевидно, не поддерживает группы без захвата. Я смог собрать воедино это:

 REGEXP_SUBSTR(TO_CHAR(NUMTODSINTERVAL(TR.RUN_LENGTH,'second')), '^([ 0: ]*)(.*)(.)',1,1,NULL,2 )
 

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

Комментарии:

1. Я бы предложил выражение 'd{2}:d{2}:d{2}'

Ответ №3:

Если ваш интервал всегда меньше 24 часов, то вы можете использовать один из следующих:

 TO_CHAR(TRUNC(SYSDATE)   NUMTODSINTERVAL(TR.RUN_LENGTH, 'second'), 'hh24:mi:ss')

REGEXP_SUBSTR(NUMTODSINTERVAL(TR.RUN_LENGTH,'second'), 'd{2}:d{2}:d{2}')
 

В противном случае вам необходимо использовать функцию ИЗВЛЕЧЕНИЯ:

 SELECT
    TO_CHAR(
       EXTRACT(DAY FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second')) * 24 
         EXTRACT(HOUR FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second')) )
   || ':' EXTRACT(MINUTE FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second') 
   || ':' TRUNC(EXTRACT(SECOND FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second'))
 

При желании вы должны добавить LPAD(..., 2, '0') вокруг деталей, если это требуется для вашего вывода.

Ответ №4:

Попробуй это:

 TO_CHAR (TRUNC (SYSDATE)   NUMTODSINTERVAL ((TR.RUN_LENGTH, 'second'),   
    'hh24:mi:ss' )
 

В документе для NUMTODSINTERVAL говорится, что он возвращает строковый литерал, поэтому to_char не имеет никакого эффекта. Этот хак выше просто берет текущую дату/время (sysdate), усекает компонент HH:mm:ss, затем добавляет заданные вами #секунды, а затем форматирует их, чтобы просто отобразить значение HH:MM:SS

Комментарии:

1. Какой док? Функция возвращает интервал, а не строку.

2. Но этот хак работает только с интервалами менее 24 часов.

3. @mathguy, я так и предполагал, но в документе говорится: «NUMTODSINTERVAL преобразует n в ИНТЕРВАЛ ДЕНЬ ВО ВТОРОЙ литерал». Вызывая, выберите length( NUMTODSINTERVAL (100, «секунда»)) из dual ; возвращает 29, затем длину возвращаемого значения.

4. @oldprogrammer, потому length что принимает символьное выражение в качестве входных данных, поэтому тип данных интервала преобразуется в его строковое представление. Документация для numtodsinterval содержит вводящее в заблуждение описание, поскольку она возвращает не какой-либо литерал, а значение встроенного типа данных