2017-05-04 41 views
2

我想將包含日期的字符串帶入單個格式日期。 EX:oracle - 將許多日期格式轉換爲單一格式日期

  • 13-06-2012至13-JUN-12
  • 13/06/2012至13-JUN-12
  • 13-JUN-2012至 13-JUN-12
  • 13月/ 6-2012 13-JUN-12
  • ...

我試圖刪除所有特殊字符,之後用一個函數來該字符串轉換成日期的單一格式。我的函數返回更多的異常,我不知道爲什麼......

功能:

CREATE OR REPLACE FUNCTION normalize_date (data_in IN VARCHAR2) 
    RETURN DATE 
IS 
    tmp_month   VARCHAR2 (3); 
    tmp_day   VARCHAR2 (2); 
    tmp_year   VARCHAR2 (4); 
    TMP_YEAR_NUMBER NUMBER; 
    result   DATE; 
BEGIN 
    tmp_day := SUBSTR (data_in, 1, 2); 
    tmp_year := SUBSTR (data_in, -4); 

    --if(REGEXP_LIKE(SUBSTR(data_in,3,2), '[:alpha:]')) then 
    if(SUBSTR(data_in,3,1) in ('a','j','i','f','m','s','o','n','d','A','J','I','F','M','S','O','N','D')) then  
    tmp_month := UPPER(SUBSTR (data_in, 3, 3)); 
    else 
    tmp_month := SUBSTR (data_in, 3, 2); 
    end if; 

    DBMS_OUTPUT.put_line (tmp_year); 

    TMP_YEAR_NUMBER := TO_NUMBER (tmp_year); 

    IF (tmp_month = 'JAN') 
    THEN 
     tmp_month := '01'; 
    END IF; 

    IF (tmp_month = 'FEB') 
    THEN 
     tmp_month := '02'; 
    END IF; 

    IF (tmp_month = 'MAR') 
    THEN 
     tmp_month := '03'; 
    END IF; 

    IF (tmp_month = 'APR') 
    THEN 
     tmp_month := '04'; 
    END IF; 

    IF (tmp_month = 'MAY') 
    THEN 
     tmp_month := '05'; 
    END IF; 

    IF (tmp_month = 'JUN') 
    THEN 
     tmp_month := '06'; 
    END IF; 

    IF (tmp_month = 'JUL') 
    THEN 
     tmp_month := '07'; 
    END IF; 

    IF (tmp_month = 'AUG') 
    THEN 
     tmp_month := '08'; 
    END IF; 

    IF (tmp_month = 'SEP') 
    THEN 
     tmp_month := '09'; 
    END IF; 

    IF (tmp_month = 'OCT') 
    THEN 
     tmp_month := '10'; 
    END IF; 

    IF (tmp_month = 'NOV') 
    THEN 
     tmp_month := '11'; 
    END IF; 

    IF (tmp_month = 'DEC') 
    THEN 
     tmp_month := '12'; 
     END IF; 

    -- dbms_output.put_line(tmp_day || '~'||tmp_year || '~' ||tmp_month); 

    IF (LENGTH (tmp_day || tmp_year || tmp_month) <> 8) 
    THEN 
     result := TO_DATE ('31122999', 'DDMMYYYY'); 
     RETURN result; 
    END IF; 

-- dbms_output.put_line('before end'); 
    result:=TO_DATE (tmp_day || tmp_month ||tmp_year , 'DDMMYYYY'); 
-- dbms_output.put_line('date result: '|| result); 
    RETURN result; 
EXCEPTION 
    WHEN NO_DATA_FOUND 
    THEN 
     NULL; 
    WHEN OTHERS 
    THEN 
     result := TO_DATE ('3012299', 'DDMMYYYY'); 
     RETURN result; 
     RAISE; 
END normalize_date; 

使用

SELECT customer_no, 
     str_data_expirare, 
     normalize_date (str_data_expirare_trim) AS data_expirare_buletin 
    FROM (SELECT customer_no, 
       str_data_expirare, 
       REGEXP_REPLACE (str_data_expirare, '[^a-zA-Z0-9]+', '') 
        AS str_data_expirare_trim 
      FROM (SELECT Q1.set_act_id_1, 
         Q1.customer_no, 
         NVL (SUBSTR (set_act_id_1, 
             INSTR (set_act_id_1, 
              '+', 
              1, 
              5) 
            + 1, 
            LENGTH (set_act_id_1)), 
          'NULL') 
          AS str_data_expirare 
        FROM STAGE_CORE.IFLEX_CUSTOMERS Q1 
        WHERE Q1.set_act_id_1 IS NOT NULL 
       ) 
     ); 

回答

3

如果你有所有可能的日期格式的聲音知道它可能更容易使用蠻力:

create or replace function clean_date 
    (p_date_str in varchar2) 
    return date 
is 
    l_dt_fmt_nt sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll 
     ('DD-MON-YYYY', 'DD-MON-YY', 'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD' 
     , 'DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY/MM/DD', 'DD/MM/YY', 'MM/DD/YY'); 
    return_value date; 
begin 
    for idx in l_dt_fmt_nt.first()..l_dt_fmt_nt.last() 
    loop 
     begin 
      return_value := to_date(p_date_str, l_dt_fmt_nt(idx)); 
      exit; 
     exception 
      when others then null; 
     end; 
    end loop; 
    if return_value is null then 
     raise no_data_found; 
    end if; 
    return return_value; 
exception 
    when no_data_found then 
     raise_application_error(-20000, p_date_str|| ' is unknown date format'); 
end clean_date; 
/

請注意,現代版本的Oracle是相當寬容的機智h日期轉換。該函數處理日期的格式它們不在列表中,有一些有趣的結果:

SQL> select clean_date('20160817') from dual; 

CLEAN_DAT 
--------- 
17-AUG-16 

SQL> select clean_date('160817') from dual; 

CLEAN_DAT 
--------- 
16-AUG-17 

SQL> 

這表明數據自動清洗在寬鬆的數據完整性規則面前的限制。罪的工資是腐敗的數據。


@AlexPoole引發了使用'RR'格式的問題。日期掩碼的這個元素是作爲Y2K kludge引入的。令人沮喪的是,我們仍在將近二十年的時間討論新千年。

無論如何,問題是這樣的。如果我們將這個字符串'161225'轉換成它有幾個世紀的日期?那麼,'yymmdd'將給2016-12-15。不夠公平,但'991225'呢?我們真正想要的日期有多可能是2099-12-15?這是'RR'格式發揮作用的地方。基本上,它默認的世紀:數字00-49默認爲20,50-99默認爲19.這個窗口是由2000年問題決定的:在2000年,更有可能'98提到最近的過去比近期和類似適用於'02的邏輯。因此,1950年的中途點。注意這是固定點不是一個滑動窗口。隨着我們從2000年進一步發展,樞軸點變得越來越有用。 Find out more

不管怎樣,關鍵的一點是,「RRRR」不與其他日期格式發揮很好:to_date('501212', 'rrrrmmdd') hurls ORA-01843:不是有效的月份. So, use「RR」 and test for it before using「YYYY'`。所以我的修訂功能(有一些整理)看起來是這樣的:

create or replace function clean_date 
    (p_date_str in varchar2) 
    return date 
is 
    l_dt_fmt_nt sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll 
     ('DD-MM-RR', 'MM-DD-RR', 'RR-MM-DD', 'RR-DD-MM' 
     , 'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD', 'YYYY-DD-MM'); 
    return_value date; 
begin 
    for idx in l_dt_fmt_nt.first()..l_dt_fmt_nt.last() 
    loop 
     begin 
      return_value := to_date(p_date_str, l_dt_fmt_nt(idx)); 
      exit; 
     exception 
      when others then null; 
     end; 
    end loop; 
    if return_value is null then 
     raise no_data_found; 
    end if; 
    return return_value; 
exception 
    when no_data_found then 
     raise_application_error(-20000, p_date_str|| ' is unknown date format'); 
end clean_date; 
/

關鍵點仍然是:有對我們是多麼的聰明可以讓這個功能,當談到解釋日期,所以一定要確保你有過限制最合適的。如果你認爲你的日期字符串大部分符合日月年的話,你仍然會得到一些錯誤的演員,但如果你在年中一天領先,那麼你的演員就會少一些。

+2

我想你可以添加FXFM到所有格式控制腹瀉,但你需要更多的變化(不同的標點一開始),你仍然有美國/歐洲格式之間的歧義問題,然後是日期語言......麥芽酒h a +1僅用於最後一行* 8-)只要您先檢查4位數字,是否有理由不將RR用於兩位數年份的變化? –

+0

@AlexPoole - 關於RR的公平點,即使它是2017年8- | – APC

2

String-to-Date Conversion Rules允許額外的格式規則(沒有應用任何其他修飾符)。 (另見this question)所以:

  • MM也匹配MONMONTH;
  • MON也匹配MONTH(反之亦然);
  • YY也匹配YYYY;
  • RR也匹配RRRR;和
  • 標點符號是可選的。

這意味着你可以這樣做:

CREATE OR REPLACE FUNCTION parse_Date_String(
    in_string VARCHAR2 
) RETURN DATE DETERMINISTIC 
IS 
BEGIN 
    BEGIN 
    RETURN TO_DATE(in_string, 'DD-MM-YY'); 
    EXCEPTION 
    WHEN OTHERS THEN 
     NULL; 
    END; 
    BEGIN 
    RETURN TO_DATE(in_string, 'MM-DD-YY'); 
    EXCEPTION 
    WHEN OTHERS THEN 
     NULL; 
    END; 
    BEGIN 
    RETURN TO_DATE(in_string, 'YY-MM-DD'); 
    EXCEPTION 
    WHEN OTHERS THEN 
     NULL; 
    END; 
    RETURN NULL; 
END; 
/

查詢

WITH dates (value) AS (
    SELECT '010101' FROM DUAL UNION ALL 
    SELECT '02JAN01' FROM DUAL UNION ALL 
    SELECT '03JANUARY01' FROM DUAL UNION ALL 
    SELECT '04012001' FROM DUAL UNION ALL 
    SELECT '05JAN2001' FROM DUAL UNION ALL 
    SELECT '06JANUARY2001' FROM DUAL UNION ALL 
    SELECT 'JAN0701' FROM DUAL UNION ALL 
    SELECT 'JANUARY0801' FROM DUAL UNION ALL 
    SELECT 'JAN0901' FROM DUAL UNION ALL 
    SELECT 'JANUARY1001' FROM DUAL UNION ALL 
    SELECT '990111' FROM DUAL UNION ALL 
    SELECT '99JAN12' FROM DUAL UNION ALL 
    SELECT '99JANUARY13' FROM DUAL UNION ALL 
    SELECT '19990114' FROM DUAL UNION ALL 
    SELECT '2001-01-15' FROM DUAL UNION ALL 
    SELECT '2001JAN16' FROM DUAL UNION ALL 
    SELECT '2001JANUARY17' FROM DUAL UNION ALL 
    SELECT '20010118' FROM DUAL 
) 
SELECT value, parse_Date_String(value) AS dt 
FROM dates; 

輸出

VALUE   DT 
------------- ------------------- 
010101  2001-01-01 00:00:00 
02JAN01  2001-01-02 00:00:00 
03JANUARY01 2001-01-03 00:00:00 
04012001  2001-01-04 00:00:00 
05JAN2001  2001-01-05 00:00:00 
06JANUARY2001 2001-01-06 00:00:00 
JAN0701  2001-01-07 00:00:00 
JANUARY0801 2001-01-08 00:00:00 
JAN092001  2001-01-09 00:00:00 
JANUARY102001 2001-01-10 00:00:00 
990111  2099-01-11 00:00:00 
99JAN12  2099-01-12 00:00:00 
99JANUARY13 2099-01-13 00:00:00 
19990114  1999-01-14 00:00:00 
2001-01-15 2001-01-15 00:00:00 
2001JAN16  2001-01-16 00:00:00 
2001JANUARY17 2001-01-17 00:00:00 
20010118  0118-01-20 00:00:00 

(注意:您使用的日期格式不明確,如最後一個示例所示。您可以交換格式的函數解析的,以獲得不同的結果,但如果你有01020301-FEB-200302-JAN-200303-FEB-2001甚至01-FEB-0003?)

如果你想要的格式DD-MON-YY(但爲什麼YY ?而不是YYYY),然後只需使用:

TO_CHAR(parse_Date_String(value), 'DD-MON-YY')