oracle - 将许多日期格式转换为单个格式化日期

时间:2017-05-04 07:37:21

标签: oracle date format

我想将包含日期的字符串带到单个格式的日期。 EX:

  • 13-06-2012至13-JUN-12
  • 13/06/2012至13-JUN-12
  • 13-JUN-2012 to 13-JUN-12
  • 13 / jun-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
                  )
        );

2 个答案:

答案 0 :(得分:4)

如果您对所有可能的日期格式有充分的了解,那么使用暴力可能会更容易:

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对日期转换非常宽容。此函数以不在列表中的格式处理日期,并带来一些有趣的后果:

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.此窗口由Y2K问题决定:在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;
/

关键点仍然是:在解释日期方面,我们能够将这项功能变得多么聪明,因此请确保以最合适的方式领先。如果你认为你的大多数日期字符串都符合日 - 月 - 年的要求;你仍然会得到一些错误的演员阵容,但如果你带领一年一天的话会少一些。

答案 1 :(得分: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

(注意:您使用的日期格式不明确,如最后一个示例所示。您可以交换函数中解析格式的顺序以获得不同的结果,但如果您有010203则它是01-FEB-200302-JAN-200303-FEB-2001还是01-FEB-0003?)

如果您希望采用DD-MON-YY格式(但为什么YY而不是YYYY?),那么只需使用:

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