oracle數值類型--DATE、TIMESTAMP和INTERVAL類型

Oracle固有數據類型DATE、TIMESTAMP和INTERVAL是緊密相關的。DATE和TIMESTAMP類型存儲精度可變的固定日期/時間。INTERVAL類型能夠很容易地存儲一個時間量,如「8個小時」或「30天」。
許多新應用都在使用TIMESTAMP類型,這有兩個緣由:一方面它支持小數秒,而DATE類型不支持;另外一方面TIMESTAMP類型支持時區,這也是DATE類型力所不能及的。sql

1格式

以某種格式對數據庫中的數據進行格式化,以知足你的要求。
告訴數據庫如何將一個輸入串轉換爲DATE、TIMESTAMP或INTERVAL。數據庫

格式對數據如何存儲根本沒有任何影響。格式只是用於將存儲DATE所用的二進制格式轉換爲一個串,或者將一個串轉換爲用於存儲DATE的二進制格式。對於TIMESTAMP和INTERVAL也是如此。session

將一個表示DATE、TIMESTAMP或INTERVAL的 串發送到數據庫時就可使用格式。不要依賴於默認日期格式,默認格式會(並且極可能)在未來每一個時刻被另外每一個人所修改。若是你依賴於一個默認日期格式, 而這個默認格式有了變化,你的應用可能就會受到影響。若是沒法轉換日期,應用可能會向最終用戶返回一個錯誤;或者更糟糕的是,它可能會悄悄地插入錯誤的數 據。考慮如下INSERT語句,它依賴於一個默認的日期掩碼:函數

Insert into t ( date_column ) values ( '01/02/03' );

假設應用依賴於必須有默認日期掩碼DD/MM/YY。這就表示2003年2月1日。如今,假設有人認爲正確並且適當的日期格式應該是MM/DD/YY。忽然之間,原來的日期就會變成2003年1月2日。或者有人認爲YY/MM/DD纔對,如今的日期就會變成2001年2月3日。簡單地說,若是日期串沒有帶相應的日期格式,就會有多種解釋方法。這個INSERT語句最好寫做:性能

Insert into t ( date_column ) values ( to_date( '01/02/2003', 'DD/MM/YYYY' ) );

使用一個4字符的年份。
從數據庫取出的數據也一樣存在上述問題。若是你執行SELECT DATE_COLUMN FROM T,並在應用中把這一列獲取到一個串中,就應該對其應用一個顯示的日期格式。不論你的應用指望何種格式,都應該在這裏顯式指定。不然,若是未來某個時刻有人修改了默認日期格式,你的應用就可能會崩潰,或者有異樣的表現。優化

2 DATE類型

DATE類型是一個7字節的定寬日期/時間數據類型。它老是包含7個屬性,包括:世紀、世紀中哪一年、月份、月中的哪一天、小時、分鐘和秒。Oracle使用一種內部格式來表示這個信息。spa

scott@ORCL>create table t ( x date );

表已建立。

scott@ORCL>alter session set nls_date_language='american' ;

會話已更改。

scott@ORCL>insert into t (x) values
  2  ( to_date( '25-jun-2005 12:01:00',
  3  'dd-mon-yyyy hh24:mi:ss' ) );

已建立 1 行。

scott@ORCL>select x, dump(x) d from t;

X           D
----------- ---------------------- 
25-JUN-05   Typ=12 Len=7: 120,105,6,25,13,2,1

世紀和年份字節(DUMP輸出中的120,105)採用一種「加100」(excess-100)表示法來存儲必須將其減去100來肯定正確的世紀和年份。之因此採用加100表示法,這是爲了支持BC和AD日期。若是從世紀字節減去100獲得一個負數,則是一個BC日期,例如:code

scott@ORCL>insert into t (x) values
  2  ( to_date( '01-jan-4712bc',
  3  'dd-mon-yyyybc hh24:mi:ss' ) );

已建立 1 行。

scott@ORCL>select x, dump(x) d from t;

X            D
---------    ----------------------------------
25-JUN-05    Typ=12 Len=7: 120,105,6,25,13,2,1
01-JAN-12    Typ=12 Len=7: 53,88,1,1,1,1,1

所以,插入01-JAN-4712BC時,世紀字節是53,而53-100=-47,這纔是咱們插入的真實世紀。因爲這是一個負數,因此咱們知道它是一個BC日期。這種存儲格式還容許日期以一種二進制方式天然地排序。因爲4712 BC小於4710 BC,咱們但願能有一種支持這種順序的二進制表示。經過轉儲這兩個日期,能夠看到01-JAN-4710BC比4712 BC中的同一天「更大」,因此它們確實能正確地排序,並很好地進行比較:orm

scott@ORCL>insert into t (x) values
  2  ( to_date( '01-jan-4710bc',
  3  'dd-mon-yyyybc hh24:mi:ss' ) );

已建立 1 行。

scott@ORCL>select x, dump(x) d from t;

X            D
------------ ----------------------------------
25-JUN-05    Typ=12 Len=7: 120,105,6,25,13,2,1
01-JAN-12    Typ=12 Len=7: 53,88,1,1,1,1,1
01-JAN-10    Typ=12 Len=7: 53,90,1,1,1,1,1

接下來兩個字段是月份和日字節,它們會天然地存儲。所以,6月25日的月份字節就是6,日字節是25。小時、分鐘和秒字段採用「加1」(excess-1)表示法存儲,這說明必須將各個部分減1,才能獲得實際的時間。所以,午夜0點在日期字段中就表示爲12.12.1。blog

這種7字節格式能天然地排序。這一個7字節字段,能夠採用一種二進制方式按從小到大(或從大到小)的順序很是高效地進行排序。另外,這種結構容許很容易地進行截斷,而無需把日期轉換爲另外某種格式。例如,要截斷剛纔存儲的日期(25-JUN-2005 12.:01:00)來獲得日信息(去掉小時、分鐘和秒字段),這至關簡單。只需將尾部的3個字節設置爲12.12.1,就能很好地清除時間份量。考慮一個全新的表T,並執行如下插入:

scott@ORCL>create table t ( what varchar2(12), x date );

表已建立。

scott@ORCL>insert into t (what, x) values
  2  ( 'orig', to_date( '25-jun-2005 12:01:00','dd-mon-yyyy hh24:mi:ss' ) );

已建立 1 行。

scott@ORCL>insert into t (what, x)
  2  select 'minute', trunc(x,'mi') from t
  3  union all
  4  select 'day', trunc(x,'dd') from t
  5  union all
  6  select 'month', trunc(x,'mm') from t
  7  union all
  8  select 'year', trunc(x,'y') from t
  9  /

已建立4行。

scott@ORCL>select what, x, dump(x,12) d from t;

WHAT                     X
------------------------ ------------
D
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
orig                     25-JUN-05
Typ=12 Len=7: 120,105,6,25,13,2,1

minute                   25-JUN-05
Typ=12 Len=7: 120,105,6,25,13,2,1

day                      25-JUN-05
Typ=12 Len=7: 120,105,6,25,1,1,1

month                    01-JUN-05
Typ=12 Len=7: 120,105,6,1,1,1,1

year                     01-JAN-05
Typ=12 Len=7: 120,105,1,1,1,1,1

要把這個日期截斷,只取到年份,數據庫所要作的只是在後5個字節上置1,這是一個很是快速的操做。如今咱們就有一個可排序、可比較的DATE字段,它能截斷到年份級,並且咱們能夠儘量高效地作到這一點。不過,許多人並非使用TRUNC,而是在TO_CHAR函數中使用一個日期格式。例如,他們會這樣用:

Where to_char(date_column,'yyyy') = '2005'

而不是

Where trunc(date_column,'y') = to_date('01-jan-2005','dd-mon-yyyy')

後者不只有更出色的表現,並且佔有的資源更少。若是創建ALL_OBJECTS的一個副本,另存儲其中的CREATED列:

scott@ORCL>create table t
  2  as
  3  select created from all_objects;

表已建立。

scott@ORCL>exec dbms_stats.gather_table_stats( user, 'T' );

PL/SQL 過程已成功完成。

而後,啓用SQL_TRACE,咱們反覆使用上述兩種技術查詢這個表,能夠看到如下結果:

select count(*)
from
t where to_char(created,'yyyy') = '2005'

select count(*)
from
t where trunc(created,'y') = to_date('01-jan-2005','dd-mon-yyyy')

能夠看到存在明顯的差別。與使用TRUNC相比,使用TO_CHAR所用的CPU時間與前者相差一個數量級(即相差12倍)。這是由於TO_CHAR必須把日期轉換爲一個串,這要使用一個更大的代碼路徑,並利用當前的全部NLS來完成這個工做。而後必須執行一個串與串的比較。另外一方面,TRUNC只需把後5個字節設置爲1。而後將兩個7字節的二進制數進行比較,就大功告成了。所以,若是隻是要截斷一個DATE列,應該避免使用TO_CHAR。
另外,甚至要儘量徹底避免對DATE列應用函數。把前面的例子 能夠看到其目標是獲取2005年的全部數據。那好,若是CREATED上有一個索引,並且表中只有不多一部分CREATED值是2005年的時間,會怎麼樣呢?咱們可能但願可以使用這個索引,爲此要使用一個簡單的謂詞來避免在數據庫列上應用函數:

select count(*) from t
where created >= to_date('01-jan-2005','dd-mon-yyyy')
and created < to_date('01-jan-2006','dd-mon-yyyy');

這樣有兩個好處:
這樣一來就能夠考慮使用CREATED上的索引了。
根本無需調用TRUNC函數,這就徹底消除了相應的開銷。

這裏使用的是區間比較而不是TRUNC或TO_CHAR,這種技術一樣適用於TIMESTAMP類型。若是能在查詢中避免對一個數據庫列應用函數,就應該全力這樣作。通常來說,避免使用函數會有更好的性能,並且容許優化器在更多的訪問路徑中作出選擇。

1. 向DATE增長或減去時間
經常使用的技術有3種:
向DATE增長一個NUMBER。把DATE加1是增長1天的一種方法。所以,向DATE增長12.24就是增長1個小時,依此類推。
使用INTERVAL類型來增長時間單位。INTERVAL類型支持兩種粒度:年和月,或日/小時/分鐘/秒。
使用內置的ADD_MONTHS函數增長月。

總的來說,使用Oracle DATE類型時 建議:
使用NUMTODSINTERVAL內置函數來增長小時、分鐘和秒。
加一個簡單的數來增長天。
使用ADD_MONTHS內置函數來增長月和年。
我建議不要使用NUMTOYMINTERVAL函數。其緣由與這個函數如何處理月末日期有關。

ADD_MONTHS函數專門處理月末日期。它實際上會爲咱們完成日期的「舍入」。

scott@ORCL>alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

會話已更改。


scott@ORCL>select to_date('29-feb-2000','dd-mon-yyyy') dt from dual;
select to_date('29-feb-2000','dd-mon-yyyy') dt from dual
               *
第 1 行出現錯誤:
ORA-01843: 無效的月份


scott@ORCL>alter session set nls_date_language='american' ;

會話已更改。

scott@ORCL>select dt, add_months(dt,1)
  2  from (select to_date('29-feb-2000','dd-mon-yyyy') dt from dual )
  3  /

DT                      ADD_MONTHS(DT,1)
----------------------- -----------------------
29-feb-2000 00:00:00    31-mar-2000 00:00:00

scott@ORCL>select dt, add_months(dt,1)
  2  from (select to_date('28-feb-2001','dd-mon-yyyy') dt from dual )
  3  /

DT                      ADD_MONTHS(DT,1)
----------------------- -----------------------
28-feb-2001 00:00:00    31-mar-2001 00:00:00

scott@ORCL>select dt, add_months(dt,1)
  2  from (select to_date('30-jan-2001','dd-mon-yyyy') dt from dual )
  3  /

DT                      ADD_MONTHS(DT,1)
----------------------- -----------------------
30-jan-2001 00:00:00    28-feb-2001 00:00:00

scott@ORCL>select dt, add_months(dt,1)
  2  from (select to_date('30-jan-2000','dd-mon-yyyy') dt from dual )
  3  /

DT                      ADD_MONTHS(DT,1)
----------------------- -----------------------
30-jan-2000 00:00:00    29-feb-2000 00:00:00

向2000年2月29日增長1個月,獲得的是2000年3月31日。2月29日是該月的最後一天,因此ADD_MONTHS返回了下一個月的最後一天。注意向2000年和2001年的1月30日增長1個月時,會分別獲得2000年和2001年2月的最後一天(分別是2月29日和2月28日)。

若是與增長一個間隔 相比較,會看到徹底不一樣的結果:

scott@ORCL>select dt, dt+numtoyminterval(1,'month')
  2  from (select to_date('29-feb-2000','dd-mon-yyyy') dt from dual )
  3  /

DT                      DT+NUMTOYMINTERVAL(1,'M
----------------------- -----------------------
29-feb-2000 00:00:00    29-mar-2000 00:00:00

scott@ORCL>select dt, dt+numtoyminterval(1,'month')
  2  from (select to_date('28-feb-2001','dd-mon-yyyy') dt from dual )
  3  /

DT                      DT+NUMTOYMINTERVAL(1,'M
----------------------- -----------------------
28-feb-2001 00:00:00    28-mar-2001 00:00:00

獲得的日期並非下一個月的最後一天,而只是下一個月的同一天。

scott@ORCL>select dt, dt+numtoyminterval(1,'month')
  2  from (select to_date('30-jan-2001','dd-mon-yyyy') dt from dual )
  3  /
select dt, dt+numtoyminterval(1,'month')
             *
第 1 行出現錯誤:
ORA-01839: 指定月份的日期無效


scott@ORCL>select dt, dt+numtoyminterval(1,'month')
  2  from (select to_date('30-jan-2000','dd-mon-yyyy') dt from dual )
  3  /
select dt, dt+numtoyminterval(1,'month')
             *
第 1 行出現錯誤:
ORA-01839: 指定月份的日期無效

2. 獲得兩個日期之差

MONTHS_BETWEEN,它會返回表示兩個日期相隔月數的一個數(包括月小數)。

利用INTERVAL類型,用另外一個方法來查看兩個日期之間的逝去時間。

scott@ORCL>select dt2-dt1 ,
  2  months_between(dt2,dt1) months_btwn,
  3  numtodsinterval(dt2-dt1,'day') days,
  4  numtoyminterval(months_between(dt2,dt1),'month') months
  5  from (select to_date('29-feb-2000 01:02:03','dd-mon-yyyy hh24:mi:ss') dt1,
  6                                             to_date('12-mar-2001 12:22:33','
dd-mon-yyyy hh24:mi:ss') dt2
  7                     from dual );

   DT2-DT1 MONTHS_BTWN DAYS
                   MONTHS
---------- ----------- ---------------------------------------------------------
------------------ -------------------------------------------------------------
--------------
377.472569  12.4668571 +000000377 11:20:30.000000000
                   +000000001-00

這些都是「正確」的值,可是對咱們來講都沒有大用。大多數應用都更願意顯示日期之間相隔的年數、月數、天數、小時數、分鐘數和秒數。經過使用前述函數的一個組合,就能夠實現這個目標。咱們將選出兩個間隔:一個是年和月間隔,另外一個是日/小時/分鐘/秒間隔。咱們使用MONTHS_BETWEEN內置函數來肯定兩個日期之間相隔的月數(包括小數),而後使用NUMTOYMINTERVAL內置函數將這個數轉換爲年數和月數。另外,使用TRUNC獲得兩個日期相隔月數中的整數部分,再使用ADD_MONTHS內置函數將dt1增長12個月(這會獲得‘28-feb-2001 01:02:03),再從兩個日期中的較大者(dt2)減去這個計算獲得的日期,從而獲得兩個日期之間的天數和小時數:

scott@ORCL>select numtoyminterval(months_between(dt2,dt1),'month') years_months,

  2  numtodsinterval(dt2-add_months( dt1, trunc(months_between(dt2,dt1)) ),'day'
 ) days_hours
  3  from (select to_date('29-feb-2000 01:02:03','dd-mon-yyyy hh24:mi:ss') dt1,
  4  to_date('12-mar-2001 12:22:33','dd-mon-yyyy hh24:mi:ss') dt2
  5  from dual ) ;

YEARS_MONTHS    DAYS_HOURS
------------    -----------------------------
+000000001-00   +000000012  11:20:30.000000000

如今就很清楚了,這兩個日期之間相隔1年、12天、11小時、20分鐘和30秒。

3 TIMESTAMP類型

TIMESTAMP類型與DATE很是相似,只不過另外還支持小數秒和時區

1. TIMESTAMP

TIMESTAMP(n)

這裏N是可選的,用於指定TIMESTAMP中秒份量的小數位數,能夠取值爲0~9.若是指定0,TIMESTAMP在功能上則與DATE等價,它們實際上會以一樣的方式存儲相同的值:

scott@ORCL>create table t
  2  ( dt date,
  3  ts timestamp(0)
  4  )
  5  /

表已建立。

scott@ORCL>insert into t values ( sysdate, systimestamp );

已建立 1 行。


scott@ORCL>select dump(dt,12) dump, dump(ts,12) dump from t;

DUMP
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
DUMP
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
Typ=12 Len=7: 120,118,8,29,16,30,6
Typ=180 Len=7: 120,118,8,29,16,30,6

這兩個數據類型是不一樣的(由TYP=字段可知),可是它們採用了相同的方式存儲數據。

若是指定要保留幾位秒小數,TIMESTAMP數據類型與DATE類型的長度將會不一樣,例如:

scott@ORCL>create table t
  2  ( dt date,
  3  ts timestamp(9)
  4  )
  5  /

表已建立。

scott@ORCL>insert into t values ( sysdate, systimestamp );

已建立 1 行。

scott@ORCL>select dump(dt,12) dump, dump(ts,12) dump from t;

DUMP
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
DUMP
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
Typ=12 Len=7: 120,118,8,29,16,32,51
Typ=180 Len=11: 120,118,8,29,16,32,51,52,39,112,192

如今TIMESTAMP佔用12 字節的存儲空間,最後額外的4個字節包含着小數秒,經過查看所存儲的時間就能看出:

scott@ORCL>alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';
會話已更改。

scott@ORCL>alter session set nls_date_language='american' ;
會話已更改。

scott@ORCL>select * from t;
DT                      TS
----------------------- --------------------------------------------------------
29-aug-2018 15:31:50    29-AUG-18 03.31.50.875000000 PM


scott@ORCL>select dump(ts) dump from t;
DUMP
----------------------------------------
Typ=180 Len=11: 120,118,8,29,16,32,51,52,39,112,192

存儲的小數秒都在最後4個字節中。

2. 向TIMESTAMP增長或減去時間

DATE執行日期算術運算所用的技術一樣適用於TIMESTAMP,可是在不少狀況下,TIMESTAMP會轉換爲一個DATE,例如:

scott@ORCL>alter session set nls_date_language='american' ;

會話已更改。

scott@ORCL>alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';

會話已更改。

scott@ORCL>select systimestamp ts, systimestamp+1 dt
  2  from dual;

TS                                  DT
----------------------------------- -----------------------
29-AUG-18 04.01.17.096000 PM +08:00 30-aug-2018 16:01:17

這裏加1實際上將SYSTIMESTAMP推動了1天,可是小數秒沒有了,另外時區信息也沒有了。

這裏使用 INTERVAL 更有意義:

scott@ORCL>select systimestamp ts, systimestamp +numtodsinterval(1,'day') dt
  2  from dual;

TS                                  DT
----------------------------------- ---------------------------------------------------------
29-AUG-18 04.02.33.960000 PM +08:00 30-AUG-18 04.02.33.960000000 PM +08:00

使用返回一個INTERVAL類型的函數能保持TIMESTAMP的真實度。使用TIMESTAMP時要特別小心,以免這種隱式轉換。
向TIMESTAMP增長月間隔或年間隔時存在相關的警告。若是所獲得的「日期」不是一個合法日期,這個操做就會失敗。

3. 獲得兩個TIMESTAMP之差

將DATE相減的結果是一個NUMBER,但TIMESTAMP相減的結果倒是一個INTERVAL:

scott@ORCL>select dt2-dt1
  2  from (select to_timestamp('29-feb-2000 01:02:03.122000',
  3     'dd-mon-yyyy hh24:mi:ss.ff') dt1,
  4     to_timestamp('12-mar-2001 12:22:33.000000',
  5     'dd-mon-yyyy hh24:mi:ss.ff') dt2
  6  from dual )
  7  /

DT2-DT1
---------------------------------------------------------------------------
+000000377 11:20:29.878000000

兩個TIMESTAMP值之差是一個INTERVAL,並且這裏顯示了兩者之間相隔的天數已經小時/分鐘/秒數。若是想獲得兩者之間相差的年數和月數,可使用如下查詢(這個查詢相似於先前用於日期的查詢):

scott@ORCL>select numtoyminterval
  2  (months_between(dt2,dt1),'month')
  3  years_months,
  4  dt2-add_months(dt1,trunc(months_between(dt2,dt1)))
  5  days_hours
  6  from (select to_timestamp('29-feb-2000 01:02:03.122000',
  7     'dd-mon-yyyy hh24:mi:ss.ff') dt1,
  8     to_timestamp('12-mar-2001 12:22:33.000000',
  9     'dd-mon-yyyy hh24:mi:ss.ff') dt2
 10  from dual )
 11  /

YEARS_MONTHS              DAYS_HOURS
------------------------- ---------------------
+000000001-00             +000000012 11:20:30.000000000

在這種狀況下,因爲使用了ADD_MONTHS,DT1會隱式轉換爲一個DATE類型,這樣就丟失了小數秒。爲了保住小數秒,咱們必須編寫更多的代碼。

4. TIMESTAMP WITH TIME ZONE類型

TIMESTAMP WITH TIME ZONE類型繼承了TIMESTAMP類型的全部特色,並增長了時區支持。TIMESTAMP WITH TIME ZONE類型佔12字節的存儲空間,在此有額外的2個字節用於保留時區信息。它在結構上與TIMESTAMP的差異只是增長了這2個字節:

scott@ORCL>create table t
  2  (
  3  ts timestamp,
  4  ts_tz timestamp with time zone
  5  )
  6  /

表已建立。

scott@ORCL>insert into t ( ts, ts_tz )
  2  values ( systimestamp, systimestamp );

已建立 1 行。

scott@ORCL>select * from t;

TS                            TS_TZ
----------------------------- ------------------------------
29-AUG-18 05.54.07.695000 PM  29-AUG-18 05.54.07.695000 PM +08:00

scott@ORCL>select dump(ts), dump(ts_tz) from t;

DUMP(TS)
----------------------------------------
DUMP(TS_TZ)
----------------------------------------
Typ=180 Len=11: 120,118,8,29,18,55,8,41,108,219,192
Typ=181 Len=13: 120,118,8,29,10,55,8,41,108,219,192,28,60

獲取數據時,默認的TIMESTAMP WITH TIME ZONE格式包括有時區信息。

存儲數據時,TIMESTAMP WITH TIME ZONE會在數據中存儲指定的時區。時區成爲數據自己的一部分。注意TIMESTAMP WITH TIME ZONE字段如何存儲小時、分鐘和秒,這裏採用了加1表示法。TIMESTAMP WITH TIME ZONE爲它增長了4個小時,從而存儲爲GWT(也稱爲UTC)時間。獲取時,會使用尾部的2個字節適當地調整TIMESTAMP值。

數據庫能存儲多個時區的數據:

scott@ORCL>create table t
  2  ( ts1 timestamp with time zone,
  3  ts2 timestamp with time zone
  4  )
  5  /

表已建立。

scott@ORCL>insert into t (ts1, ts2)
  2  values ( timestamp'2005-06-05 12:02:32.212 US/Eastern',
  3           timestamp'2005-06-05 12:02:32.212 US/Pacific' );


已建立 1 行。

並對這些數據執行正確的TIMESTAMP運算:

scott@ORCL>select ts1-ts2 from t;

TS1-TS2
---------------------------------------------------------------------------
-000000000 03:00:00.000000

由於這兩個時區之間有3個小時的時差,儘管它們顯示的是「一樣的時間」——12:02:32:212,可是從報告的時間間隔來看確實存在3小時的時差。在TIMESTAMP WITH TIME ZONE類型上執行TIMESTAMP運算時,Oracle會自動地把兩個類型首先轉換爲UTC時間,而後執行運算。

5. TIMESTAMP WITH LOCAL TIME ZONE類型

這種類型與TIMESTAMP類型的工做是相似的。這是一個7字節或12字節的字段(取決於TIMESTAMP的精度),可是會進行規範化,在其中存入數據庫的時區。

首先,建立一個包括3列的表,這3列分別是一個DATE列、一個TIMESTAMP WITH TIME ZONE列和一個TIMESTAMP WITH LOCAL TIME ZONE列,而後向這3列插入相同的值:

scott@ORCL>create table t
  2  ( dt date,
  3  ts1 timestamp with time zone,
  4  ts2 timestamp with local time zone
  5  )
  6  /

表已建立。

scott@ORCL>insert into t (dt, ts1, ts2)
  2  values ( timestamp'2005-06-05 12:02:32.212 US/Pacific',
  3  timestamp'2005-06-05 12:02:32.212 US/Pacific',
  4  timestamp'2005-06-05 12:02:32.212 US/Pacific' );

已建立 1 行。

scott@ORCL>select dbtimezone from dual;

DBTIMEZONE
------------
+00:00

如今,將這些值轉儲以下:

scott@ORCL>select dump(dt), dump(ts1), dump(ts2) from t;

DUMP(DT)
----------------------------------------
DUMP(TS1)
----------------------------------------
DUMP(TS2)
----------------------------------------
Typ=12 Len=7: 120,105,6,5,13,3,33
Typ=181 Len=13: 120,105,6,5,20,3,33,12,162,221,0,137,156
Typ=231 Len=11: 120,105,6,5,20,3,33,12,162,221,0

能夠看到,在這個例子中,會存儲3種徹底不一樣的日期/時間表示:
DT:這一列存儲了日期/時間。時區和小數秒沒有了,由於使用的是DATE類型。這裏根本不會執行時區轉換。
TS1:這一列保留了TIME ZONE信息,並規範化爲該TIME ZONE相應的UTC時間。
TS2:這裏認爲這個列的時區就是數據庫時區。這裏採用了加1表示法:取得實際時間時要記住減1。

因爲TS1列在最後2字節保留了原來的時區,獲取時咱們會看到如下結果:

scott@ORCL>select ts1, ts2 from t;

TS1                                       TS2
---------------------------------------   ----------------------------------------------------------
05-JUN-05 12.02.32.212000 PM US/PACIFIC   06-JUN-05 03.02.32.212000 AM

數據庫應該能顯示這個信息,可是有LOCAL TIME ZONE(數據庫時區)的TS2列只顯示了數據庫時區的時間,並認爲這就是這一列的時區(實際上,這個數據庫中有LOCAL TIME ZONE的全部列的時區都是數據庫時區)。

若是不須要記住源時區,只須要這樣一種數據類型,要求能對日期/時間類型提供一致的全球性處理,那麼TIMESTAMP WITH LOCAL TIME ZONE對大多數應用來講已經能提供足夠的支持。另外,TIMESTAMP(0) WITH LOCAL TIME ZONE是與DATE類型等價但提供了時區支持的一種類型;它佔用7字節存儲空間,容許存儲按UTC形式「規範化」的日期。

關於TIMESTAMP WITH LOCAL TIME ZONE類型有一個警告,一旦建立有這個列的表,將不能修改數據庫的時區。

scott@ORCL>alter database set time_zone = 'PST';
alter database set time_zone = 'PST'
*
第 1 行出現錯誤:
ORA-30079: 當數據庫有 TIMESTAMP WITH LOCAL TIME ZONE 列時不能變動數據庫時區

4 INTERVAL類型

這是表示一段時間或一個時間間隔的一種方法。

YEAR TO MONTH類型,它能存儲按年和月指定的一個時段;

DATE TO SECOND類型,它能存儲按天、小時、分鐘和秒(包括小數秒)指定的時段。

EXTRACT內置函數 能夠處理TIMESTAMP和INTERVAL,並從中返回各部分信息,如從TIMESTAMP返回時區,從INTERVAL返回小時/天/分鐘:

scott@ORCL>select dt2-dt1
  2  from (select to_timestamp('29-feb-2000 01:02:03.122000',
  3     'dd-mon-yyyy hh24:mi:ss.ff') dt1,
  4     to_timestamp('12-mar-2001 12:22:33.000000',
  5     'dd-mon-yyyy hh24:mi:ss.ff') dt2
  6     from dual )
  7  /

DT2-DT1
---------------------------------------------------------------------------
+000000377 11:20:29.878000000

可使用EXTRACT來查看,它能很輕鬆地取出其中的各部分信息:

scott@ORCL>select extract( day from dt2-dt1 ) day,
  2     extract( hour from dt2-dt1 ) hour,
  3     extract( minute from dt2-dt1 ) minute,
  4     extract( second from dt2-dt1 ) second
  5  from (select to_timestamp('29-feb-2000 01:02:03.122000',
  6     'dd-mon-yyyy hh24:mi:ss.ff') dt1,
  7     to_timestamp('12-mar-2001 12:22:33.000000',
  8     'dd-mon-yyyy hh24:mi:ss.ff') dt2
  9     from dual )
 10  /

       DAY       HOUR     MINUTE     SECOND
---------- ---------- ---------- ----------
       377         11         20     29.878

YEAR TO MONTH和DAY TO SECOND間隔時所用的NUMTOYMINTERVAL和NUMTODSINTERVAL,是建立INTERVAL類型實例最容易的方法,遠遠勝於串轉換函數。
INTERVAL類型不僅是能夠用於存儲時段,還能夠以某種方式存儲「時間」。例如,若是但願存儲一個特定的日期時間,可使用DATE或TIMESTAMP類型。可是若是隻想存儲上午8:00這個時間呢?INTERVAL類型就很方便(尤爲是INTERVAL DAY TO SECOND類型)。

1. INTERVAL YEAR TO MONTH

INTERVAL YEAR(n) TO MONTH

在此N是一個可選的位數(用以支持年數),可取值爲0~9,默認爲2(表示年數可爲0~99)。這就容許你存儲任意大小的年數(最多可達9位)和月數。也能夠用 NUMTOYMINTERVAL 函數來建立這種類型的INTERVAL實例。

例如,要建立一個5年2個月的時間間隔,可使用如下命令:

scott@ORCL>select numtoyminterval(5,'year')+numtoyminterval(2,'month')
  2  from dual;

NUMTOYMINTERVAL(5,'YEAR')+NUMTOYMINTERVAL(2,'MONTH')
---------------------------------------------------------------------------
+000000005-02

或者,利用1年有12個月這個事實,可使用一個調用,並使用如下命令:

scott@ORCL>select numtoyminterval(5*12+2,'month')
  2  from dual;

NUMTOYMINTERVAL(5*12+2,'MONTH')
---------------------------------------------------------------------------
+000000005-02

能夠用另外一個函數TO_YMINTERVAL將一個串轉換爲一個年/月INTERVAL類型:

scott@ORCL>select to_yminterval( '5-2' ) from dual;

TO_YMINTERVAL('5-2')
---------------------------------------------------------------------------
+000000005-02

還能夠直接在SQL中使用INTERVAL類型,而不用這些函數:

scott@ORCL>select interval '5-2' year to month from dual;

INTERVAL'5-2'YEARTOMONTH
---------------------------------------------------------------------------
+05-02

2. INTERVAL DAY TO SECOND

INTERVAL DAY(n) TO SECOND(m)

在此N是一個可選的位數,支持天數份量,取值爲0~9,默認爲2。M是秒字段小時部分中保留的位數,其中爲0~9,默認爲6 一樣,能夠用 NUMTODSINTERVAL 函數來建立這種類型的INTERVAL實例:

scott@ORCL>select numtodsinterval( 12, 'day' )+
  2  numtodsinterval( 2, 'hour' )+
  3  numtodsinterval( 3, 'minute' )+
  4  numtodsinterval( 2.3312, 'second' )
  5  from dual;

NUMTODSINTERVAL(12,'DAY')+NUMTODSINTERVAL(2,'HOUR')+NUMTODSINTERVAL(3,'MINU
---------------------------------------------------------------------------
+000000012 02:03:02.331200000

或者只是:

scott@ORCL>select numtodsinterval( 12*86400+2*3600+3*60+2.3312, 'second' )
  2  from dual;

NUMTODSINTERVAL(12*86400+2*3600+3*60+2.3312,'SECOND')
---------------------------------------------------------------------------
+000000012 02:03:02.331200000

這裏利用了一天有89,400秒,一小時有3,600秒等事實

可使用TO_DSINTERVAL函數將一個串轉換爲一個DAY TO SECOND間隔:

scott@ORCL>select to_dsinterval( '12 02:03:02.3312' )
  2  from dual;

TO_DSINTERVAL('1202:03:02.3312')
---------------------------------------------------------------------------
+000000012 02:03:02.331200000

或者只是在SQL自己中使用INTERVAL變量:

scott@ORCL>select interval '12 02:03:02.3312' day to second
  2  from dual;

INTERVAL'1202:03:02.3312'DAYTOSECOND
---------------------------------------------------------------------------
+12 02:03:02.331200
相關文章
相關標籤/搜索