'DD-MON-RR'日期格式模式未按预期工作

时间:2014-03-14 07:35:01

标签: oracle postgresql datetime types postgresql-9.1

我正在插入格式为

的查询
to_date('25-JUN-13','DD-MON-RR')

oracle 中,它正常工作,输出为25-JUN-13 postgresql 中,同样适用于0001-06-25 BC

oracle数据库到postgresql 迁移项目。任何解决方案都适用于 oracle 的情况。

如果我使用的是 DD-MM-YY 格式,那么效果不正常,那么结果就会大不相同。

在POSTGRESQL中运行此查询 - >

select to_char(to_date('25-JUN-53','DD-MON-YY'),'YYYY') as YEAR 

ANSWER IS --> 2053 

从查询中检索oracle中的相同结果

select to_char(to_date('25-JUN-53','DD-MON-RR'),'YYYY') as YEAR from dual

ASSWER IS --> 1953

当我迁移项目时,Postgresql中应该有相同的功能,以便最终结果应该相同。

3 个答案:

答案 0 :(得分:5)

  

同样不能正常工作......

当然它运作正常。 Per documentation:

  

如果年份格式规范少于四位数,例如YYY,   并且提供的年份少于四位数,年份将是   调整为最接近年份 2020 ,例如95成为1995年。

因此,70变为1970,但69变为2069。

Oracle对格式说明符RR有不同的规则(Postgres中不存在),基本上年份将调整为最接近年份 2000 (距离当前日期最近的世纪):

解决方法

我会将功能封装在一个函数中,该函数根据字符串中的年份数来切换世纪。由于Postgres允许function overloading,您甚至可以使用具有不同参数类型的相同函数名to_date()

根据上面的文档,Oracle在YY = '50'时回绕,这个函数相当于2049年:

CREATE OR REPLACE FUNCTION to_date(varchar, text)
  RETURNS date AS
$func$
SELECT CASE WHEN right($1, 2) > '49' THEN
         to_date(left($1, -2) || '19' || right($1, 2), 'DD-MON-YYYY')
      ELSE
         to_date(left($1, -2) || '20' || right($1, 2), 'DD-MON-YYYY')
      END
$func$  LANGUAGE sql STABLE;

STABLE,而非IMMUTABLE,因为to_date仅为STABLE。否则你禁用函数内联。

我选择varchar表示第一个参数与原始参数不同,后者使用text
如果年份数是> 49,该函数在转换之前将20世纪(带有'19'),21加入到日期字符串中。第二个参数是忽略

呼叫:

SELECT to_date('25-JUN-53'         , 'DD-MON-YY') AS original
     , to_date('25-JUN-53'::varchar, 'DD-MON-YY') AS patched1
     , to_date('25-JUN-53'::varchar, 'DD-MON-RR') AS patched2
     , to_date('25-JUN-53'::varchar, 'FOO-BAR')   AS patched3

我们的自定义函数无论如何都会忽略第二个参数。

结果:

original    | patched
------------+-----------
2053-06-25  | 1953-06-25

SQL Fiddle.

你可能会让它在2049年之后更加复杂,并考虑第二个参数......


警告:基本功能的功能重载最好小心。如果它留在你的系统中,有人可能会在以后得到令人惊讶的结果。

最好在特殊模式中创建该函数,并有选择地设置search_path,以便在适当时使用它。在这种情况下,您也可以使用text作为参数类型:

CREATE SCHEMA specialfunc;
CREATE OR REPLACE FUNCTION specialfunc.to_date(text, text) AS ...

然后:

SET search_path = specialfunc, pg_catalog;
SELECT to_date('25-JUN-53', 'DD-MON-YY') AS patched;

使用临时功能。参见:

答案 1 :(得分:0)

这是我为解决此问题而编写的示例函数。我自己正在进行同样的迁移。希望它对你有所帮助。你可以使用函数重载来实现,只需将函数重命名为。

CREATE OR REPLACE FUNCTION to_date_rr(TEXT, TEXT)
  RETURNS DATE AS
  $$
      DECLARE
        date_v DATE;
        fmt text := upper($2);
        DATE_VALUE TEXT :=$1;
        digit_diff numeric := length($1) - length($2);

      BEGIN

    $2 = upper($2);

    IF substring(fmt from position('RRRR' in fmt) for 4) = 'RRRR' THEN

        IF digit_diff < 0 THEN
            fmt := replace($2, 'RRRR', 'YYYY');
            IF substring(DATE_VALUE from position('RRRR' in $2) for 2) > '50' THEN
                date_v := to_date(overlay(DATE_VALUE placing '19' from position('RRRR' in $2) for 0), fmt);
            ELSE
                date_v := to_date(overlay(DATE_VALUE placing '20' from position('RRRR' in $2) for 0), fmt);
            END IF;
        ELSE
            fmt := replace($2, 'RRRR', 'YYYY');
            date_v := to_date($1, fmt);
        END IF;

    ELSIF substring(fmt from position('RR' in fmt) for 2) = 'RR' THEN
        IF digit_diff = 0 THEN
            fmt := replace($2, 'RR', 'YY');         
            IF substring(DATE_VALUE from position('RR' in $2) for 2) > '50' THEN
                date_v := to_date(overlay(DATE_VALUE placing '19' from position('RR' in $2) for 0), fmt);
            ELSE
                date_v := to_date(overlay(DATE_VALUE placing '20' from position('RR' in $2) for 0), fmt);
            END IF;


        ELSE
            fmt := replace($2, 'RR', 'YY');
            date_v := to_date($1, fmt);

        END IF;


    ELSIF substring(fmt from position('YY' in fmt) for 2) = 'YY' and substring(fmt from position('YYYY' in fmt) for 4) != 'YYYY' THEN
        IF digit_diff = 0 THEN  
            IF substring(DATE_VALUE from position('YY' in $2) for 2) >= '00' THEN
                date_v := to_date(overlay(DATE_VALUE placing '20' from position('YY' in $2) for 0), fmt);

            END IF;

        ELSIF digit_diff < 0 THEN
            IF substring(DATE_VALUE from position('YY' in $2) for 2) >= '00' THEN
                date_v := to_date(overlay(DATE_VALUE placing '200' from position('YY' in $2) for 0), fmt);      
            END IF;
        ELSE
            date_v := to_date($1, fmt);
        END IF;

    ELSE
        SELECT to_date($1, $2) INTO date_v;

    END IF;


        RETURN date_v;

      END;
  $$
LANGUAGE plpgsql;

答案 2 :(得分:0)

可以使用 to_char(Date Field, 'format to display')