2017-05-04 42 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')