|
|
|||||||||||||||||||||||||||||
|
Работа с датами в OracleИсточник: oracle
При отображении информации о времени или датах в Oracle критично, какую версию сервера вы используете. Например, начиная с версии 9, стало возможным представление моментов времени и временных интервалов используя типы данных ANSI SQL. Например, timestamp and interval. Ранние версии использовали тип данных date, с точностью одна секунда и временными интервалами как числа (где 1=один день). Если вы используете новые версии сервера Oracle, то настоятельно рекомендуется использовать новые типы данных в ваших приложениях, если конечно не планируется использовать приложение на серверах не поддерживающих такие типы. Выборка по датеДля иллюстрации используем простой пример регистрации пользователей в системе, например на каком либо форуме. Для этого создадим таблицу и наполним ее данными: CREATE TABLE USERS ( USER_ID INTEGER PRIMARY KEY, NICKNAME VARCHAR(50), REALNAME VARCHAR(50) NOT NULL, EMAIL VARCHAR(100) NOT NULL UNIQUE, PASSWORD VARCHAR(30) NOT NULL, -- точность 1 секунда REGISTRATION_DATE TIMESTAMP(0) ); -- добавляем тестовые данные
INSERT INTO USERS
(USER_ID, NICKNAME, REALNAME, EMAIL, PASSWORD, REGISTRATION_DATE)
VALUES
(1,'DBA','Елена','elena@all-oracle.ru','qwerty',
TO_TIMESTAMP('2009-06-13 09:15:00','YYYY-MM-DD HH24:MI:SS'));
INSERT INTO USERS
(USER_ID, NICKNAME, REALNAME, EMAIL, PASSWORD, REGISTRATION_DATE)
VALUES
(2,'User','Виталий','vitaly@all-oracle.ru','qwerty',
TO_TIMESTAMP('2009-06-13 15:18:22','YYYY-MM-DD HH24:MI:SS'));
INSERT INTO USERS
(USER_ID, NICKNAME, REALNAME, EMAIL, PASSWORD, REGISTRATION_DATE)
VALUES
(3,'Junior','Олег','oleg@all-oracle.ru','qwerty',
TO_TIMESTAMP('2009-06-16 10:11:52','YYYY-MM-DD HH24:MI:SS'));
INSERT INTO USERS
(USER_ID, NICKNAME, REALNAME, EMAIL, PASSWORD, REGISTRATION_DATE)
VALUES
(4,'XXX','Александр','alexandr@all-oracle.ru','qwerty',
TO_TIMESTAMP('2009-06-16 13:01:36','YYYY-MM-DD HH24:MI:SS'));Теперь выберем пользователей, которые зарегистрировались в течение последнего дня: COLUMN EMAIL FORMAT A35 COLUMN REGISTRATION_DATE FORMAT A25 SELECT EMAIL, REGISTRATION_DATE FROM USERS WHERE REGISTRATION_DATE > CURRENT_DATE - INTERVAL '1' DAY; EMAIL REGISTRATION_DATE ----------------------------------- ------------------------- oleg@all-oracle.ru 16-JUN-09 10.11.52 AM alexandr@all-oracle.ru 16-JUN-09 01:01:36 PM или, в зависимости от национальных настроек EMAIL REGISTRATION_DATE ----------------------------------- ------------------------- oleg@all-oracle.ru 16.06.09 10:11:52 alexandr@all-oracle.ru 16.06.09 13:01:36 Для явного задания формата вывода даты, перепишем предложение так: ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'; SELECT EMAIL, REGISTRATION_DATE FROM USERS WHERE REGISTRATION_DATE > CURRENT_DATE - INTERVAL '1' DAY; EMAIL REGISTRATION_DATE ----------------------------------- ------------------------- oleg@all-oracle.ru 2009-06-16 10:11:52 alexandr@all-oracle.ru 2009-06-16 13:01:36 Кроме того, по своему желанию, вы можете изменять интервал, увеличивая или уменьшая его, в зависимости от задачи. Например: SELECT EMAIL, REGISTRATION_DATE FROM USERS WHERE REGISTRATION_DATE > CURRENT_DATE - INTERVAL '1' HOUR; EMAIL REGISTRATION_DATE ----------------------------------- ------------------------- anna_cat@all-oracle.ru 16.06.09 19:44:09 или SELECT EMAIL, REGISTRATION_DATE FROM USERS WHERE REGISTRATION_DATE > CURRENT_DATE - INTERVAL '1' MINUTE; no rows selected или SELECT EMAIL, REGISTRATION_DATE FROM USERS WHERE REGISTRATION_DATE > CURRENT_DATE - INTERVAL '1' SECOND; no rows selected Вы можете явно указывать формат для даты и времени: SELECT EMAIL, TO_CHAR(REGISTRATION_DATE,'Day, Month DD, YYYY') AS REG_DAY
FROM USERS ORDER BY REGISTRATION_DATE; EMAIL REG_DAY ----------------------------------- ------------------------------ elena@all-oracle.ru Суббота , Июнь 13, 2009 vitaly@all-oracle.ru Суббота , Июнь 13, 2009 oleg@all-oracle.ru Вторник , Июнь 16, 2009 alexandr@all-oracle.ru Вторник , Июнь 16, 2009 В результате видим, мягко говоря, неудобоваримое форматирование. Исправляем ситуацию, переписав SQL предложение так: SELECT EMAIL,
TRIM(TO_CHAR(REGISTRATION_DATE,'Day')) // ', ' //
TRIM(TO_CHAR(REGISTRATION_DATE,'Month')) // ' ' //
TRIM(TO_CHAR(REGISTRATION_DATE,'DD, YYYY')) AS REG_DAY
FROM USERS
ORDER BY REGISTRATION_DATE;
EMAIL REG_DAY
----------------------------------- ------------------------------
elena@all-oracle.ru Суббота, Июнь 13, 2009
vitaly@all-oracle.ru Суббота, Июнь 13, 2009
oleg@all-oracle.ru Вторник, Июнь 16, 2009
alexandr@all-oracle.ru Вторник, Июнь 16, 2009Подводные камниНекоторые вещи следует знать, и для простоты покажу на примерах: ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'; -- старый вариант
SELECT ADD_MONTHS(TO_DATE('2009-06-20','YYYY-MM-DD'),-1) FROM DUAL;
ADD_MONTHS
----------
2009-05-20-- новый вариант
SELECT TO_TIMESTAMP('2003-07-31','YYYY-MM-DD') - INTERVAL '1' MONTH FROM dual;
ERROR at line 1:
ORA-01839: DATE not valid for month specified-- старый вариант
SELECT TO_DATE('2009-06-20','YYYY-MM-DD') - 100 FROM DUAL;
TO_DATE('2
----------
2009-03-12-- новый вариант
SELECT TO_TIMESTAMP('2009-06-20','YYYY-MM-DD') - INTERVAL '100' DAY FROM DUAL;
ERROR at line 1:
ORA-01873: the leading precision of the interval is too small-- новый вариант (точность "(3)")
SELECT TO_TIMESTAMP('2009-06-20','YYYY-MM-DD') - interval '100' DAY(3) FROM DUAL;
TO_TIMESTAMP('2009-06-20','YYYY-MM-DD')-INTERVAL'100'DAY(3)
---------------------------------------------------------------------------
2009-03-12 00:00:00Маленькие неприятностиВычисление временных интервалов может быть довольно неприятной задачей, поскольку в стандартном SQL нет возможности обратится к значению этой колонки из предыдущий строки в отчете. Это легко сделать в любом языке программирования, например C#, Visual Basic, Java или Delphi, которые могут прочитать базу данных, и затем сделать это своими средствами не прибегая к SQL. Добавим еще несколько строк к нашей таблице со списком пользователей: INSERT INTO USERS
(USER_ID, NICKNAME, REALNAME, EMAIL, PASSWORD, REGISTRATION_DATE)
VALUES
(5,'Kate','Екатерина','kate@all-oracle.ru','qwerty',
TO_TIMESTAMP('2009-06-16 06:00:00','YYYY-MM-DD HH24:MI:SS'));
INSERT INTO USERS
(USER_ID, NICKNAME, REALNAME, EMAIL, PASSWORD, REGISTRATION_DATE)
VALUES
(6,'Cat','Анна','anna_cat@all-oracle.ru','qwerty',
TO_TIMESTAMP('2009-06-16 19:44:09','YYYY-MM-DD HH24:MI:SS'));Например, нам интересна средняя продолжительность времени между регистрацией пользователей: SELECT REGISTRATION_DATE FROM USERS ORDER BY REGISTRATION_DATE; REGISTRATION_DATE ------------------------- 2009-06-13 09:15:00 2009-06-13 15:18:22 2009-06-16 06:00:00 2009-06-16 10:11:52 2009-06-16 13:01:36 2009-06-16 19:44:09 Перепишем запрос с объединением: COLUMN R1 FORMAT A21
COLUMN R2 FORMAT A21
SELECT U1.REGISTRATION_DATE AS R1,
U2.REGISTRATION_DATE AS R2
FROM USERS U1, USERS U2
WHERE U2.USER_ID = (SELECT MIN(USER_ID) FROM USERS
WHERE REGISTRATION_DATE > U1.REGISTRATION_DATE)
ORDER BY R1;
R1 R2
--------------------- ---------------------
2009-06-13 09:15:00 2009-06-13 15:18:22
2009-06-13 15:18:22 2009-06-16 10:11:52
2009-06-16 06:00:00 2009-06-16 10:11:52
2009-06-16 10:11:52 2009-06-16 13:01:36
2009-06-16 13:01:36 2009-06-16 19:44:09Для обеспечения уникальности используем колонку USER_ID, поскольку время и дата регистрации не обеспечивают уникальность, т.к. возможна ситуация одновременной регистрации двух пользователей. Сейчас у нас имеется рядом расположенные колонки с данными, представляющие собой некий отчет, и можно подсчитать интервалы: COLUMN REG_GAP FORMAT A21
SELECT U1.REGISTRATION_DATE AS R1,
U2.REGISTRATION_DATE AS R2,
U2.REGISTRATION_DATE-U1.REGISTRATION_DATE AS REG_GAP
FROM USERS U1, USERS U2
WHERE U2.USER_ID = (SELECT MIN(USER_ID) FROM USERS
WHERE REGISTRATION_DATE > U1.REGISTRATION_DATE)
ORDER BY R1;
R1 R2 REG_GAP
--------------------- --------------------- ---------------------
2009-06-13 09:15:00 2009-06-13 15:18:22 +000000000 06:03:22
2009-06-13 15:18:22 2009-06-16 10:11:52 +000000002 18:53:30
2009-06-16 06:00:00 2009-06-16 10:11:52 +000000000 04:11:52
2009-06-16 10:11:52 2009-06-16 13:01:36 +000000000 02:49:44
2009-06-16 13:01:36 2009-06-16 19:44:09 +000000000 06:42:33Интервал для каждой колонки возвращает день, часы, минуты и секунды. По этим временным точкам можно рассчитать средний интервал. Для этого напишем такой запрос: SELECT AVG(REG_GAP)
FROM
(SELECT
U1.REGISTRATION_DATE AS R1,
U2.REGISTRATION_DATE AS R2,
U2.REGISTRATION_DATE-U1.REGISTRATION_DATE AS REG_GAP
FROM USERS U1, USERS U2
WHERE U2.USER_ID = (SELECT MIN(USER_ID) FROM USERS
WHERE REGISTRATION_DATE > U1.REGISTRATION_DATE));
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected NUMBER got INTERVAL DAY TO SECONDSOracle выдал ошибку. В чем же дело? К сожалению Oracle не оказался достаточно умным, чтобы рассчитать среднее значение временного интервала. И как бы ни было печально, рассчитать среднее значение оказалось не так просто как хотелось бы. Если у вас есть желание, то вы можете переписать предложение SQL в таком виде: SELECT AVG(REG_GAP)
FROM
(SELECT
U1.REGISTRATION_DATE AS R1,
U2.REGISTRATION_DATE AS R2,
TO_DATE(TO_CHAR(U2.REGISTRATION_DATE,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD HH24:MI:SS')
- TO_DATE(TO_CHAR(U1.REGISTRATION_DATE,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD HH24:MI:SS')
AS REG_GAP
FROM USERS U1, USERS U2
WHERE U2.USER_ID = (SELECT MIN(USER_ID) FROM USERS
WHERE REGISTRATION_DATE > U1.REGISTRATION_DATE));
AVG(REG_GAP)
------------
,722363426Если вы хотите использовать такие дикие запросы, то пожалуйста, но в данном случае целесообразнее создать представление: CREATE VIEW REGISTRATION_INTERVALS
AS
SELECT
U1.REGISTRATION_DATE AS R1,
U2.REGISTRATION_DATE AS R2,
TO_DATE(TO_CHAR(U2.REGISTRATION_DATE,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD HH24:MI:SS')
- TO_DATE(TO_CHAR(U1.REGISTRATION_DATE,'YYYY-MM-DD HH24:MI:SS'),'YYYY-MM-DD HH24:MI:SS')
AS REG_GAP
FROM USERS U1, USERS U2
WHERE U2.USER_ID = (SELECT MIN(USER_ID) FROM USERS
WHERE REGISTRATION_DATE > U1.REGISTRATION_DATE);И теперь, можно посчитать среднее значение в минутах: SELECT 24*60*AVG(REG_GAP) AS AVG_GAP_MINUTES
FROM REGISTRATION_INTERVALS;
AVG_GAP_MINUTES
---------------
1040,20333Ссылки по теме
|
|
|||||||