ORA-01873: ведущая точность интервала слишком мала с функцией EXTRACT - PullRequest
0 голосов
/ 14 октября 2019

Я столкнулся со странной проблемой с функцией EXTRACT при попытке получить интервал секунд 8-12 часов.

Это проверено с Oracle 12.2 Пример: https://rextester.com/ZMR79428

declare 
  start_time_ TIMESTAMP;  
  exect_time_ NUMBER;  
begin
start_time_ := SYSDATE- 1/3;  -- 8 hours, error is for SYSDATE- 1/2
exect_time_ := extract(day from ((sysdate- start_time_)*86400));
dbms_output.put_line(exect_time_);
end;

Я не вижу, что я сделал, чтобы получить ведущую точность интервала, слишком маленькую ошибка в этом коде. Это работало для всех других сценариев. Например: SYSDATE -1/4 отлично работает.

Ответы [ 2 ]

1 голос
/ 14 октября 2019

Если вы вычесть два значения типа данных DATE, вы получите NUMBER, представляющее количество (дробных) дней между двумя значениями. Если вы вычтете два значения типа данных TIMESTAMP, вы получите тип данных INTERVAL.

Таким образом, ваш ответ может быть просто:

declare 
  start_time_ DATE   := SYSDATE- 1/3;
  exect_time_ NUMBER;  
begin
  exect_time_ := ( sysdate - start_time_ ) *86400;
  dbms_output.put_line( exect_time_ );
end;
/

Какие выходные данные: 28800

Ваша проблема в том, что вы умножаете на 86400 внутри функции EXTRACT, а не снаружи;поэтому SYSDATE - start_time_ дает INTERVAL '8' HOUR, а затем вы умножаете на 86400, а INTERVAL '8' HOUR * 86400 дает значение INTERVAL '28800' DAY, которое не соответствует точности по умолчанию для типа данных INTERVAL DAY TO SECOND (и дает вамнеправильный ответ в любом случае).

Что бы вы хотели (если вы действительно хотите использовать TIMESTAMP s):

declare 
  start_time_ TIMESTAMP := SYSTIMESTAMP - 1/3;
  difference  INTERVAL DAY TO SECOND := SYSTIMESTAMP - start_time_;
  exect_time_ NUMBER;  
begin
  exect_time_ := EXTRACT( DAY    FROM difference ) * 24 * 60 * 60
               + EXTRACT( HOUR   FROM difference )      * 60 * 60
               + EXTRACT( MINUTE FROM difference )           * 60
               + EXTRACT( SECOND FROM difference );
  dbms_output.put_line( exect_time_ );
end;
/

, который выдает что-то вроде 28800.246382 (так как естьдоля секунды между двумя SYSTIMESTAMP вызовами).

или, если вам не нужны дробные секунды, то:

declare 
  start_time_ TIMESTAMP := SYSTIMESTAMP- 1/3;
  exect_time_ NUMBER;  
begin
  exect_time_ := ( SYSDATE - CAST( start_time_ AS DATE ) ) * 86400;
  dbms_output.put_line( exect_time_ );
end;
/

Какие выходные данные 28800.

db <> fiddle


я не понимаю, почему он выдает ошибку

Это странная ошибка;код ниже тестирует различные случаи:

DECLARE
  TYPE test_case IS RECORD(
    units      VARCHAR2(20),
    difference INTERVAL DAY TO SECOND,
    multiplier NUMBER(8,0)
  );
  TYPE test_case_list IS TABLE OF test_case;

  FUNCTION createTestCase(
    units      VARCHAR2,
    difference INTERVAL DAY TO SECOND,
    multiplier NUMBER
  ) RETURN test_case;

  test_cases test_case_list := test_case_list(
    createTestCase( 'SECOND',               INTERVAL '1' SECOND, 24 * 60 * 60 ),
    createTestCase( 'SECOND PLUS A LITTLE', INTERVAL '1' SECOND + INTERVAL '0.001' SECOND, 24 * 60 * 60 ),
    createTestCase( 'MINUTE',               INTERVAL '1' MINUTE, 24 * 60 ),
    createTestCase( 'MINUTE PLUS A LITTLE', INTERVAL '1' MINUTE + INTERVAL '0.001' SECOND, 24 * 60 ),
    createTestCase( 'HOUR',                 INTERVAL '1' HOUR, 24 ),
    createTestCase( 'HOUR PLUS A LITTLE',   INTERVAL '1' HOUR + INTERVAL '0.001' SECOND, 24 ),
    createTestCase( 'DAY',                  INTERVAL '1' DAY, 1 ),
    createTestCase( 'DAY PLUS A LITTLE',    INTERVAL '1' DAY + INTERVAL '0.001' SECOND, 1 )
  );

  FUNCTION createTestCase(
    units      VARCHAR2,
    difference INTERVAL DAY TO SECOND,
    multiplier NUMBER
  ) RETURN test_case
  IS
    tc test_case;
  BEGIN
    tc.units      := units;
    tc.difference := difference;
    tc.multiplier := multiplier;
    RETURN tc;
  END;
BEGIN
  FOR i IN 1 .. test_cases.COUNT LOOP
    BEGIN
      DBMS_OUTPUT.PUT_LINE( test_cases(i).units );
      DBMS_OUTPUT.PUT_LINE( test_cases(i).difference * test_cases(i).multiplier * 100000 );
      DBMS_OUTPUT.PUT_LINE( (SYSTIMESTAMP + test_cases(i).difference - SYSTIMESTAMP) * test_cases(i).multiplier * 10000 );
      DBMS_OUTPUT.PUT_LINE( (SYSTIMESTAMP + test_cases(i).difference - SYSTIMESTAMP) * test_cases(i).multiplier * 100000 );
    EXCEPTION
      WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE( SQLERRM );
    END;
  END LOOP;
END;
/

и выходы:

SECOND
+000100000 00:00:00.000000000
+000009999 21:21:36.000000000
ORA-01873: the leading precision of the interval is too small
SECOND PLUS A LITTLE
+000100100 00:00:00.000000000
+000010009 22:33:36.000000000
+000100099 19:12:00.000000000
MINUTE
+000100000 00:00:00.000000000
+000009999 23:59:31.200000000
ORA-01873: the leading precision of the interval is too small
MINUTE PLUS A LITTLE
+000100001 16:00:00.000000000
+000010000 03:59:02.400000000
+000100001 15:55:12.000000000
HOUR
+000100000 00:00:00.000000000
+000009999 23:59:59.280000000
ORA-01873: the leading precision of the interval is too small
HOUR PLUS A LITTLE
+000100000 00:40:00.000000000
+000010000 00:03:59.040000000
+000100000 00:39:55.200000000
DAY
+000100000 00:00:00.000000000
+000009999 23:59:59.970000000
ORA-01873: the leading precision of the interval is too small
DAY PLUS A LITTLE
+000100000 00:01:40.000000000
+000010000 00:00:09.960000000
+000100000 00:01:39.800000000

db <> Fiddle здесь

  • Использование INTERVAL непосредственно в каждом тестовом примере работает.
  • Когда вы заставляете механизм PL / SQL работать SYSTIMESTAMP + an_interval - SYSTIMESTAMP, тогда он должен вызывать функцию SYSTIMESTAMPдважды (что означает, что между значениями есть несколько долей секунды), и тестовые случаи не пройдены.
  • Когда вы добавляете небольшое количество времени к интервалу, тогда все тестовые примеры снова проходят, предполагая, чтоИнтервал с другой точностью был получен в результате расчета. (Это не предназначено для хакерского решения, просто интересное замечание).
  • db <> fiddle показывает, что это происходит только в области PL / SQL;если вы выполняете те же операторы в операторах SQL, то исключений нет.

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

Однако это имеет отношение к решению;не умножайте свои INTERVAL на 86400;вы должны использовать EXTRACT несколько раз с DAY, HOUR, MINUTE и SECOND соответствующими аргументами и преобразовывать возвращаемые значения каждого в секунды и добавлять их или, альтернативно, использовать CAST для обратного преобразованияиспользовать DATE арифметику.

1 голос
/ 14 октября 2019

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

На самом деле проблема заключается в умножении.

Если вы выполняете его без или смалый множитель, чем вы получите ответ.

SQL> select extract(day from ((systimestamp - (systimestamp - 1/3)))) from dual ;

EXTRACT(DAYFROM((SYSTIMESTAMP-(SYSTIMESTAMP-1/3))))
---------------------------------------------------
                                                  0

SQL> select extract(day from ((systimestamp - (systimestamp - 1/3))*8640)) from dual ;

EXTRACT(DAYFROM((SYSTIMESTAMP-(SYSTIMESTAMP-1/3))*8640))
--------------------------------------------------------
                                                    2880

SQL> select extract(day from ((systimestamp - (systimestamp - 1/3))*86400)) from dual ;
select extract(day from ((systimestamp - (systimestamp - 1/3))*86400)) from dual
                                                              *
ERROR at line 1:
ORA-01873: the leading precision of the interval is too small


SQL>

Что бы вы ни делали с этим числом, вам нужно подумать о другой логике, так как умножение на столбец отметки времени не рекомендуется.

Cheers!!

...