Вычитание дат в Oracle - тип числа или интервала? - PullRequest
20 голосов
/ 17 февраля 2012

У меня есть вопрос о некоторых внутренних операциях для типов данных Oracle DATE и INTERVAL. Согласно Oracle 11.2 SQL Reference , когда вы вычитаете 2 типа данных DATE, результатом будет тип данных NUMBER.

При поверхностном тестировании это выглядит так:

CREATE TABLE test (start_date DATE);
INSERT INTO test (start_date) VALUES (date'2004-08-08');
SELECT (SYSDATE - start_date) from test;

вернет тип данных NUMBER.

Но теперь, если вы это сделаете:

SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;

вы получаете ИНТЕРВАЛЬНЫЙ тип данных. Другими словами, Oracle может преобразовать NUMBER из вычитания DATE в тип INTERVAL.

Так что теперь я решил, что могу попытаться поместить тип данных NUMBER непосредственно в скобки (вместо того, чтобы делать 'SYSDATE - start_date', который в любом случае приводит к NUMBER):

SELECT (1242.12423) DAY(5) TO SECOND from test;

Но это приводит к ошибке:

ORA-30083: syntax error was found in interval value expression

Итак, мой вопрос: что здесь происходит? Кажется, что вычитание дат должно привести к NUMBER (как продемонстрировано в операторе SELECT # 1), который НЕ МОЖЕТ автоматически приводиться к типу INTERVAL (как продемонстрировано в инструкции SELECT # 3). Но Oracle, похоже, может сделать это каким-то образом, если вы используете выражение вычитания DATE вместо ввода необработанного NUMBER (оператор SELECT # 2).

Спасибо

Ответы [ 5 ]

31 голосов
/ 19 февраля 2012

Ладно, я обычно не отвечаю на свои вопросы, но после некоторой переделки я определил, как Oracle хранит результат вычитания ДАТЫ.

Когда вы вычитаете 2 даты, значениене является типом данных NUMBER (как вы могли бы поверить Справочное руководство по Oracle 11.2 SQL ).Внутренний номер типа вычитания DATE равен 14, что является недокументированным внутренним типом данных (NUMBER внутренний тип данных 2 ).Однако на самом деле он хранится в виде двух отдельных чисел с двумя дополнительными знаками, причем первые 4 байта используются для представления количества дней, а последние 4 байта используются для представления количества секунд.

Пример DATEвычитание, приводящее к положительной разнице в целых числах:

select date '2009-08-07' - date '2008-08-08' from dual;

Результат:

DATE'2009-08-07'-DATE'2008-08-08'
---------------------------------
                              364

select dump(date '2009-08-07' - date '2008-08-08') from dual;

DUMP(DATE'2009-08-07'-DATE'2008
-------------------------------
Typ=14 Len=8: 108,1,0,0,0,0,0,0

Напомним, что результат представлен в виде двоичного числа с двумя дополнительными двумя, дополненного 4-значными числами.Поскольку в этом случае десятичные дроби отсутствуют (ровно 364 дня и 0 часов), последние 4 байта равны нулю и могут быть проигнорированы.Для первых 4 байтов, поскольку мой ЦП имеет архитектуру с прямым порядком байтов, байты меняются местами и должны читаться как 1,108 или 0x16c, что является десятичным числом 364.

Пример вычитания ДАТЫ, приводящего к отрицательному значениюразница целых чисел:

select date '1000-08-07' - date '2008-08-08' from dual;

Результат:

DATE'1000-08-07'-DATE'2008-08-08'
---------------------------------
                          -368160

select dump(date '1000-08-07' - date '2008-08-08') from dual;

DUMP(DATE'1000-08-07'-DATE'2008-08-0
------------------------------------
Typ=14 Len=8: 224,97,250,255,0,0,0,0

Опять же, поскольку я использую машину с прямым порядком байтов, байты меняются местами и должны читаться как 255,250,97,224, что соответствует11111111 11111010 01100001 11011111. Теперь, так как это двоичное двоичное числовое кодирование со знаком дополнения до двух, мы знаем, что число является отрицательным, потому что крайняя левая двоичная цифра - 1. Чтобы преобразовать это в десятичное число, мы должны были бы повернуть дополнение до 2 (вычтите 1, затем выполните дополнение), в результате чего: 00000000 00000101 10011110 00100000, что соответствует -368160, как и предполагалось.

Пример вычитания ДАТЫ, приводящего к десятичной разнице:

select to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS'
 - to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS') from dual;

TO_DATE('08/AUG/200414:00:00','DD/MON/YYYYHH24:MI:SS')-TO_DATE('08/AUG/20048:00:
--------------------------------------------------------------------------------
                                                                             .25

разница между этими двумя датами составляет 0,25 дня или 6 часов.

select dump(to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS')
 - to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS')) from dual;

DUMP(TO_DATE('08/AUG/200414:00:
-------------------------------
Typ=14 Len=8: 0,0,0,0,96,84,0,0

Нетw на этот раз, поскольку разница составляет 0 дней и 6 часов, ожидается, что первые 4 байта равны 0. Для последних 4 байтов мы можем обратить их (поскольку ЦП имеет младший порядок) и получить 84,96 = 0101010001100000 основание 2 = 21600 в десятичном виде.Преобразование 21600 секунд в часы дает вам 6 часов, что является той разницей, которую мы ожидали.

Надеюсь, что это поможет любому, кто интересуется, как на самом деле сохраняется вычитание ДАТЫ.

6 голосов
/ 17 февраля 2012

Вы получаете синтаксическую ошибку, потому что математика даты не возвращает НОМЕР, но возвращает ИНТЕРВАЛ:

SQL> SELECT DUMP(SYSDATE - start_date) from test;

DUMP(SYSDATE-START_DATE)
-------------------------------------- 
Typ=14 Len=8: 188,10,0,0,223,65,1,0

Вам необходимо сначала преобразовать число в вашем примере в ИНТЕРВАЛ, используя Функция NUMTODSINTERVAL

Например:

SQL> SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;

(SYSDATE-START_DATE)DAY(5)TOSECOND
----------------------------------
+02748 22:50:04.000000

SQL> SELECT (SYSDATE - start_date) from test;

(SYSDATE-START_DATE)
--------------------
           2748.9515

SQL> select NUMTODSINTERVAL(2748.9515, 'day') from dual;

NUMTODSINTERVAL(2748.9515,'DAY')
--------------------------------
+000002748 22:50:09.600000000

SQL>

На основании обратного приведения с помощью функции NUMTODSINTERVAL () кажется, что при преобразовании теряется некоторое округление.

1 голос
/ 26 июля 2012

Несколько баллов:

  • Вычитание одной даты из другой приводит к числу;вычитание одной временной метки из другой приводит к интервалу.

  • Oracle преобразовывает временные метки в даты при выполнении арифметики временных меток.

  • Константы интервала использовать нельзяв арифметике с указанием даты или времени.

Oracle 11gR2 SQL Справочная матрица даты и времени

0 голосов
/ 12 июня 2019

Используйте функцию extract () для извлечения часов / минут / секунд из значения интервала. Ниже приведен пример, как получить часы из двух столбцов отметок времени. Надеюсь, это поможет!

выбрать IHB_INS_TS, MAIL_SENT_TS, извлечь (час (IHB_INS_TS - MAIL_SENT_TS)) hourDiff из IHB_ADJSMT_WKFL_NTFCTN;

0 голосов
/ 14 декабря 2014

выберите TIMEDIFF (STR_TO_DATE (' 07: 15 PM ', '% h:% i% p'), STR_TO_DATE (' 9: 58 AM ', '% h :% i% p '))

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...