本文討論了在Amazon RDS和Aurora 中使用PostgreSQL數據庫時,與日期/時間相關的函數,並確定PostgreSQL數據庫里的clock_timestamp()函數與Oracle中的SYSDATE函數最匹配。同時我們可以自定義基於clock_timestamp()的改進函數(設置遷移的Oracle數據庫服務器時區),具體參考“建議”部分中所述。
作者:Baji Shaik and Sudip Acharya
來源:https://aws.amazon.com/cn/blogs/database/converting-the-sysdate-function-from-oracle-to-postgresql/
譯者:多米爸比
在AWS Cloud 中遷移Oracle數據庫到PostgreSQL數據庫是一個復雜的過程,從最初評估階段到遷移轉換階段,多個階段過程中會涉及不同的技術和技能。有關遷移過程的更多信息,請參閱下面幾篇文章:
Database Migration—What Do You Need to Know Before You Start?
Migration process and infrastructure considerations
Source database considerations
Target database considerations for the PostgreSQL environment
Oracle數據庫遷移到Amazon RDS或者Amazon Aurora 環境下的PostgreSQL數據庫時,最常見的問題之一是SYSDATE函數。應用程序或存儲過程以及觸發器中最常用的日期/時間函數是SYSDATE。
建表字段如creation_date,last_updated_date,approved_date,在做更新操作時會被更新為設置的默認值SYSDATE或通過觸發器賦值為SYSDATE。
本文描述了在PostgreSQL數據庫里替代Oracle數據庫SYSDATE函數的一種方法。
在評估Oracle和PostgreSQL的日期/時間函數時,要考慮三個方面:
Statement級別或者事務級別的影響
客戶端時區設置的影響
Daylight Saving Time (DST)
PostgreSQL日期/時間功能概述
PostgreSQL提供了幾個函數,這些函數返回與當前日期和時間有關的值。其中一些功能是SQL標准功能,而其他功能是非SQL標准功能。
支持的SQL標准函數
以下是受支持的SQL標准函數:
CURRENT_DATE
CURRENT_TIMESTAMP/CURRENT_TIMESTAMP(precision)
CURRENT_TIME/CURRENT_TIME(precision)
LOCALTIME/LOCALTIME(precision)
LOCALTIMESTAMP/LOCALTIMESTAMP(precision)
這些函數返回當前事務的開始時間。如果您在同一事務中多次運行這些函數,則值不會更改。這是一個內部功能特性。如果要在整個事務中使用一致的時間戳,可以使用這些功能。
CURRENT_DATE
CURRENT_DATE函數以yyyy-mm-dd格式顯示當前日期。請參見以下代碼:
postgres=> select CURRENT_DATE; current_date-------------- 2020-01-03(1 row)
CURRENT_TIMESTAMP/CURRENT_TIMESTAMP(precision)
CURRENT_TIME/CURRENT_TIME(precision)
這些函數返回帶時區的當前日期和時間。您可以選擇precision參數控制時間精度。此精度舍入小數位數(毫秒)。請參見以下代碼:
postgres=> select CURRENT_TIMESTAMP; current_timestamp------------------------------- 2020-01-03 04:38:15.662514+00(1 row) postgres=> select CURRENT_TIMESTAMP(2); current_timestamp--------------------------- 2020-01-03 04:38:19.75+00(1 row) postgres=> select CURRENT_TIME; current_time-------------------- 04:40:29.409115+00(1 row) postgres=> select CURRENT_TIME(2); current_time---------------- 04:40:38.01+00(1 row)
LOCALTIME/LOCALTIME(precision)
LOCALTIMESTAMP/LOCALTIMESTAMP(precision)
這些函數返回沒有時區的當前日期和時間。您可以選擇precision參數控制時間精度。此精度舍入小數位數(毫秒)。請參見以下代碼:
postgres=> select LOCALTIMESTAMP;
localtimestamp
----------------------------
2020-01-03 04:42:39.405423
(1 row)
postgres=> select LOCALTIMESTAMP(2);
localtimestamp
------------------------
2020-01-03 04:42:41.97
(1 row)
postgres=> select LOCALTIME;
localtime
-----------------
04:42:24.022253
(1 row)
postgres=> select LOCALTIME(2);
localtime
-------------
04:42:32.01
(1 row)
支持的非SQL標准函數
以下是受支持的非SQL標准函數:
transaction_timestamp()
statement_timestamp()
clock_timestamp()
timeofday()
now()
PostgreSQL還提供了返回當前語句的開始時間和調用該函數時的實際當前時間的函數。
transaction_timestamp()和 statement_timestamp()
transaction_timestamp函數的行為與current_timestamp相同。但是,顧名思義,它返回事務的開始時間,並且在整個事務中保持一致。statement_timestamp函數返回語句的開始時間,與事務無關。
statement_timestamp()和transaction_timestamp()在事務的第一個命令期間返回相同的值,但在后續命令期間可能有所不同。請參見以下代碼:
postgres=> begin;BEGINpostgres=> select statement_timestamp(), transaction_timestamp(); statement_timestamp | transaction_timestamp-------------------------------+------------------------------- 2020-01-03 04:58:39.271915+00 | 2020-01-03 04:58:37.690723+00(1 row) postgres=> select pg_sleep(5); pg_sleep---------- (1 row) postgres=> select statement_timestamp(), transaction_timestamp(); statement_timestamp | transaction_timestamp-------------------------------+------------------------------- 2020-01-03 04:58:49.770003+00 | 2020-01-03 04:58:37.690723+00(1 row)
從上面可以看出transaction_timestamp()兩次執行結果值是一樣的,statement_timestamp()值發生了改變。
clock_timestamp()和 statement_timestamp()
clock_timestamp()函數返回當前時間的真實時間(clock時間),其值在單個SQL命令中動態生成。
下面的代碼示例演示clock_timestamp()在同一命令中返回不同的時間戳值,但statement_timestamp()函數返回相同的值:
postgres=> WITH time_testpostgres-> AS (SELECT Statement_timestamp())postgres-> SELECT *,postgres-> Pg_sleep(3) AS "<- see the difference ->",postgres-> Statement_timestamp()postgres-> FROM time_test; statement_timestamp | <- see the difference -> | statement_timestamp-------------------------------+--------------------------+------------------------------- 2020-01-03 05:05:08.458192+00 | | 2020-01-03 05:05:08.458192+00(1 row) postgres=>postgres=> WITH time_testpostgres-> AS (SELECT clock_timestamp())postgres-> SELECT *,postgres-> Pg_sleep(3) AS "<- see the difference ->",postgres-> clock_timestamp()postgres-> FROM time_test; clock_timestamp | <- see the difference -> | clock_timestamp-------------------------------+--------------------------+------------------------------- 2020-01-03 05:05:18.040189+00 | | 2020-01-03 05:05:21.042861+00(1 row)
timeofday() vs clock_timestamp()
函數timeofday()和clock_timestamp()兩者的行為均相同。唯一的區別是timeofday()返回文本數據類型,clock_timestamp()返回帶有時區的時間戳。在以下代碼示例中的pg_typeof列顯示了clock_timestamp()和timeofday()函數的返回類型,分別是時間類型和文本:
postgres=> select clock_timestamp(), pg_typeof(clock_timestamp()), timeofday(), pg_typeof(timeofday()); clock_timestamp | pg_typeof | timeofday | pg_typeof-------------------------------+--------------------------+-------------------------------------+----------- 2020-01-03 05:28:50.203961+00 | timestamp with time zone | Fri Jan 03 05:28:50.203961 2020 UTC | text(1 row)
now()
now()函數是PostgreSQL的傳統函數,等效於transaction_timestamp()。在下面的代碼示例中,兩個函數顯示相同的時間戳(並且在同一個事務中多次執行,結果值是一樣的):
postgres=> begin;BEGINpostgres=> select now(), transaction_timestamp(); now | transaction_timestamp-------------------------------+------------------------------- 2020-01-03 05:29:25.805646+00 | 2020-01-03 05:29:25.805646+00(1 row) postgres=> select pg_sleep(3); pg_sleep---------- (1 row) postgres=> select now(), transaction_timestamp(); now | transaction_timestamp-------------------------------+------------------------------- 2020-01-03 05:29:25.805646+00 | 2020-01-03 05:29:25.805646+00(1 row)
如何選擇要使用的PostgreSQL時間函數
Oracle 的SYSDATE函數返回語句執行時的數據庫庫服務器日期/時間。因此,在長時間運行的事務中,如果您有多個SYSDATE函數,則每個語句執行都會返回不同的時間。
在下面的Oracle代碼示例中,您可以看到兩個不同的SYSDATE函數執行值。在Oracle中SYSDATE函數返回語句的開始時間,與事務開始的時間無關:
SET SERVEROUTPUT ON ;
BEGIN
DBMS_OUTPUT.PUT_LINE('Start : ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
dbms_lock.sleep(30);
DBMS_OUTPUT.PUT_LINE('End : ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/
Start : 2020-01-03 06:11:06
End : 2020-01-03 06:11:36
在下面的PostgreSQL代碼示例中,在一個事務內,您可以按不同的時間間隔捕獲不同的PostgreSQL日期和時間函數返回的時間。以下代碼在兩者之間休眠15秒,比較之前和之后的結果。確定哪個函數提供與以下行為相同的輸出值SYSDATE:
DO$BODY$BEGINRAISE NOTICE 'clock_timestamp() : %', clock_timestamp();RAISE NOTICE 'statement_timestamp() : %', statement_timestamp();RAISE NOTICE 'now() : %', now();RAISE NOTICE 'current_timestamp : %', current_timestamp;RAISE NOTICE 'transaction_timestamp() : %', transaction_timestamp();RAISE NOTICE '';RAISE NOTICE 'sleep for 15 secs and see the difference below: %', pg_sleep(15);RAISE NOTICE '';RAISE NOTICE 'clock_timestamp() : %', clock_timestamp();RAISE NOTICE 'statement_timestamp() : %', statement_timestamp();RAISE NOTICE 'now() : %', now();RAISE NOTICE 'current_timestamp : %', current_timestamp;RAISE NOTICE 'transaction_timestamp() : %', transaction_timestamp();END;$BODY$;
上面的代碼執行結果如下:
NOTICE: clock_timestamp() : 2020-01-03 06:20:52.3715+00
NOTICE: statement_timestamp() : 2020-01-03 06:20:52.371345+00
NOTICE: now() : 2020-01-03 06:20:52.371345+00
NOTICE: current_timestamp : 2020-01-03 06:20:52.371345+00
NOTICE: transaction_timestamp() : 2020-01-03 06:20:52.371345+00
NOTICE:
NOTICE: sleep for 15 secs and see the difference below:
NOTICE:
NOTICE: clock_timestamp() : 2020-01-03 06:21:07.438274+00
NOTICE: statement_timestamp() : 2020-01-03 06:20:52.371345+00
NOTICE: now() : 2020-01-03 06:20:52.371345+00
NOTICE: current_timestamp : 2020-01-03 06:20:52.371345+00
NOTICE: transaction_timestamp() : 2020-01-03 06:20:52.371345+00
只有clock_timestamp()函數在單個事務中返回不同的時間信息。因此,最佳匹配替代SYSDATE的PostgreSQL函數是clock_timestamp()。但僅此信息還不夠,因為這些值只是時間戳值。由於不同的時區在同一時間點具有不同的時間戳值,因此您還必須考慮時區和DST。否則,您可能會看到與預期不同的值。
客戶端時區設置的影響
Oracle日期/時間函數(例如SYSDATE()和SYSTIMESTAMP())返回數據庫服務器時區的當前日期和時間,而與客戶端或會話時區設置無關。但是PostgreSQL日期/時間函數會根據您的客戶端或會話時區設置返回時間。在PostgreSQL中,帶有時區值的時間戳在UTC內部存儲,並在顯示給客戶端時轉換為時區配置參數指定的時區中的本地時間。
對Oracle的影響
在Oracle中,SYSDATE對於客戶端或會話級時區設置,該功能不起作用。在以下代碼示例中,更改時區設置不會影響SYSDATE結果:
SQL> (select ‘dbtimezone’ as config, dbtimezone as offset from dual) union (select ‘sessiontimezone’ as config, sessiontimezone as offset from dual); CONFIG OFFSET--------------- ------------------------------dbtimezone +00:00sessiontimezone +05:30 SQL> select sysdate from dual; SYSDATE
03/01/2020 09:56:53 SQL> alter session set time_zone = ‘-08:30’;Session altered. SQL> (select ‘dbtimezone’ as config, dbtimezone as offset from dual) union (select ‘sessiontimezone’ as config, sessiontimezone as offset from dual); CONFIG OFFSET--------------- ------------------------------dbtimezone +00:00sessiontimezone. -08:30 SQL> select sysdate from dual; SYSDATE
03/01/2020 09:57.34
SYSDATE無論客戶端或會話如何設置時區,仍會返回服務器時區的時間戳。
對PostgreSQL的影響
在PostgreSQL中,clock_timestamp()和其他時間函數返回客戶端會話時區的當前日期和時間。請參見以下代碼示例:
postgres=> show timezone;
TimeZone
UTC
(1 row)
postgres=> select clock_timestamp();
clock_timestamp
2020-01-03 06:25:36.165378+00
(1 row)
postgres=> set timezone = ‘America/New_York’;
SET
postgres=> show timezone;
TimeZone
America/New_York
(1 row)
postgres=> select clock_timestamp();
clock_timestamp
2020-01-03 01:25:49.329555-05
(1 row)
postgres=>
如果這些時間戳是由不同客戶端在不同時區返回並存儲在TIMESTAMP WITHOUT TIME ZONE類型列中,則數據會產生誤導。
DST注意事項
Oracle和PostgreSQL數據庫之間的時區名稱和偏移量實現並不一致。您可能沒有從UTC獲得匹配的時區名稱或時間偏移。另外,PostgreSQL中時區設置的行為取決於您使用完整的時區名稱還是時區縮寫。PostgreSQL中的時區縮寫定義了與UTC的特定偏移量,但是完整的時區名稱可能意味着一組DST日期規則。
系統目錄表pg_timezone_names中包含全時區名稱的詳細信息,pg_timezone_abbrevs表具有時區縮寫的詳細信息。
示例:遷移Oracle數據庫
在以下示例中,您必須將Oracle數據庫(數據庫時區MET)遷移到PostgreSQL數據庫,而應用程序代碼使用了SYSDATE。在PostgreSQL中,您需要一個類似的時間函數。
在Oracle中,MET時區支持DST,UTC偏移為+02:00:00。PostgreSQL具有時區名稱MET(UTS偏移+02:00:00和DST支持),並且還具有時區縮寫MET(UTC偏移+01:00:00和不支持DST)。
在PostgreSQL數據庫中,當您在會話級別或設置AT TIME ZONE時區,如果存在匹配的全名和縮寫,則使用縮寫。如果將MET設置為timezone DB參數,則偏移量為+01:00:00,並且DST無效。
以下代碼示例關聯全時區表和縮寫表,過濾顯示偏移量不同的值:
postgres=> select n.name, n.abbrev N_abbrev,a.abbrev, n.utc_offset N_utc_offset , a.utc_offset, n.is_dst N_is_dst, a.is_dst from pg_timezone_names n, pg_timezone_abbrevs a where n.name = a.abbrev and n.utc_offset <> a.utc_offset order by 1; name | n_abbrev | abbrev | n_utc_offset | utc_offset | n_is_dst | is_dst------+----------+--------+--------------+------------+----------+--------CET | CEST | CET | 02:00:00 | 01:00:00 | t | fEET | EEST | EET | 03:00:00 | 02:00:00 | t | fMET | MEST | MET | 02:00:00 | 01:00:00 | t | fWET | WEST | WET | 01:00:00 | 00:00:00 | t | f(4 rows) postgres=> show timezone; TimeZone---------- UTC(1 row) postgres=> select clock_timestamp(); clock_timestamp------------------------------- 2020-01-03 06:29:09.672859+00(1 row) postgres=> set session time zone 'MET';SETpostgres=> select clock_timestamp() AT TIME ZONE 'MET'; timezone---------------------------- 2020-01-03 07:29:16.261098(1 row)
將會話時區設置為MET時,UTC與MET之間的時差為1小時,這是與時區縮寫相關的偏移量。另外,此時區不支持DST。您可以使用以下代碼手動添加間隔來進行檢查:
postgres=> select clock_timestamp() AT TIME ZONE 'UTC' + interval '02:00:00'; ?column?---------------------------- 2020-01-03 08:29:19.732955(1 row)
對於MET時區,正確的時區應為Europe / Berlin。請參見以下代碼:
postgres=> select * from pg_timezone_names where lower(name) like '%berlin%'; name | abbrev | utc_offset | is_dst---------------+--------+------------+-------- Europe/Berlin | CEST | 02:00:00 | t
要驗證DST是否影響時區,請完成以下步驟:
查找歷史或即將發生的DST更改。有關更多信息,請參見即將進行的夏時制時鍾更改。
在Oracle中,檢查DST更改前后UTC偏移量是否更改。
在PostgreSQL中,檢查DTC更改前后UTC偏移是否更改。
示例:DST更改
在以下示例中,當本地時鍾倒退1小時,MET(中歐時間)的DST更改發生在2018年10月28日上午03:00:00。原始DST UTC偏移應為02:00:00,而新DST UTC偏移應為01:00:00。
在以下Oracle示例代碼中,DST更改時需將時鍾延后1小時。
SQL> ALTER SESSION SET TIME_ZONE='UTC';Session altered. -- Before DST, 28-OCT-2018 at 00:00:00 UTC equivalent to 28-OCT-2018 at 02:00:00 MET SQL> select to_timestamp('2020-01-03 00:00:00','YYYY-MM-DD HH24:MI:SS') at time zone 'MET' from dual; TO_TIMESTAMP('2018-10-2800:00:00','YYYY-MM-DDHH24:MI:SS')ATTIMEZONE'MET'---------------------------------------------------------------------------28-OCT-18 02.00.00.000000000 AM MET -- Before DST, 28-OCT-2018 at 01:00:00 UTC equivalent to 28-OCT-2018 at 02:00:00 MET SQL> select to_timestamp('2018-10-28 01:00:00','YYYY-MM-DD HH24:MI:SS') at time zone 'MET' from dual; TO_TIMESTAMP('2018-10-2801:00:00','YYYY-MM-DDHH24:MI:SS')ATTIMEZONE'MET'---------------------------------------------------------------------------28-OCT-18 02.00.00.000000000 AM MET
以下代碼在PostgreSQL中顯示了相同的效果:
postgres=> show timezone;
TimeZone
----------
UTC
postgres => select '2018-10-28 00:00:00' AT TIME ZONE 'Europe/Berlin';
timezone
---------------------
2018-10-28 02:00:00
postgres => select '2018-10-28 01:00:00' AT TIME ZONE 'Europe/Berlin';
timezone
---------------------
2018-10-28 02:00:00
建議
考慮到PostgreSQL日期和時間函數不同方面的影響(例如客戶端時區設置和DST更改的影響)之后,可以在PostgreSQL中使用如下函數來模擬Oracle的SYSDATE 函數。它提供了一個語句級的時間戳,並不受客戶端設置的影響:
CREATE OR REPLACE FUNCTION <<Your schema>>.sysdate()
RETURNS TIMESTAMP WITHOUT TIME ZONE
AS
$BODY$
SELECT clock_timestamp() AT TIME ZONE '<<DB Timezone>>';
$BODY$
LANGUAGE sql;
在下面的代碼示例中,修改客戶端時區后也返回一致的結果:
CREATE OR REPLACE FUNCTION public.sysdate()
RETURNS TIMESTAMP WITHOUT TIME ZONE
AS
$BODY$
SELECT clock_timestamp() AT TIME ZONE 'Europe/Berlin';
$BODY$
LANGUAGE sql;
postgres=> set session time zone 'UTC';
SET
postgres=> select sysdate();
sysdate
----------------------------
2020-01-03 07:34:54.441904
(1 row)
postgres=> set session time zone 'Asia/Kolkata';
SET
postgres=> select sysdate();
sysdate
----------------------------
2020-01-03 07:35:02.392743
(1 row)
墨天輪原文鏈接:https://www.modb.pro/db/24525(復制到瀏覽器中打開或者點擊“閱讀原文”)
推薦下載:144頁!分享珍藏已久的數據庫技術年刊
數據和雲
ID:OraNews
如有收獲,請划至底部,點擊“在看”,謝謝!
點擊下圖查看更多 ↓
雲和恩墨大講堂 | 一個分享交流的地方
長按,識別二維碼,加入萬人交流社群
請備注:雲和恩墨大講堂
點個“在看”
你的喜歡會被看到❤