postgresql to_timestamp按设计接受无效日期

时间:2014-07-25 15:53:16

标签: postgresql validation datetime casting timestamp

我尝试将字符串验证为来自多个CSV的时间戳,只是将它们转换为timestamptz会因为无法强制使用唯一的日期时间格式而失败:

select '10/31/2010'::timestamp --fail due to "different datestyle" 
select '31/10/2010'::timestamp --works

我认为 to_timestamp()可以做到这一点,但是类似于:

select to_timestamp('31/02/2014 14:30', 'DD/MM/YYYY HH24:MI'); 

将返回" 2014-03-03 14:30:00-05" 而不是抛出异常

所以我考虑使用这个approach,使用to_char将输出恢复为文本并将其与原始输入进行比较,但是像' DD / MM / YYYY HH24这样的掩码: MI' 将投出" 06/03/2014 0 :06"到" 06/03/2014 00 :06",字符串不同!

CREATE OR REPLACE FUNCTION to_timestamp_safe(IN p_date text, IN p_format text, OUT r_date timestamp without time zone)
  RETURNS timestamp without time zone AS
$BODY$

    BEGIN
      r_date = TO_TIMESTAMP(p_date, p_format);
       IF TO_CHAR(r_date, p_format) != p_date THEN
        RAISE EXCEPTION 'Input date % does not match output date %', p_date, r_date;
      END IF;
    END;
    $BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

以下示例在应该工作的位置失败:

select to_timestamp_safe ('06/03/2014 0:06', 'DD/MM/YYYY HH24:MI');

输出:

ERROR: Input date 06/03/2014 0:06 does not match output date 2014-03-06 00:06:00
SQL state: P0001

在没有上述陷阱的情况下,是否有一种智能的方法可以安全地验证字符串到timestamptz?

1 个答案:

答案 0 :(得分:0)

FM modifier做了诀窍(感谢@ErwinBrandstetter),这确实是一种通用解决方案,因为我们提出了为每种类型的csv创建配置文件的想法,并且日期格式掩码可以存储在那里对于日期/时间的列,无论如何,列的大部分都会转到hstore列,我们需要将它们的类型保存在某处。所以我能够创建这样的东西,其中_colmask是日期时间格式掩码,可能有也可能没有FM修饰符。然后我只是针对我的临时表执行此功能(为每列循环)

CREATE OR REPLACE FUNCTION validate_column_datatype(_colvalue text, _colname text, _type text,  _colmask text)
  RETURNS void AS
$BODY$
BEGIN

declare
    -- error stack
    _returned_sqlstate text := null;
    _column_name text := null;
    _constraint_name text := null;
    _pg_datatype_name text := null; 
    _message_text text := null;
    _table_name text := null;
    _schema_name text := null;
    _pg_exception_detail text := null;
    _pg_exception_hint text := null;
    _pg_exception_context text := null;

    BEGIN


    IF _type = 'timestamptz'  then    
          IF TO_CHAR(TO_TIMESTAMP(_colvalue, _colmask), _colmask) != _colvalue THEN
        RAISE EXCEPTION 'Input date % does not match output date', _colvalue;
          END IF;

    ELSEIF _type = 'timestamp'  then
          IF TO_CHAR(TO_TIMESTAMP(_colvalue, _colmask), _colmask) != _colvalue THEN
        RAISE EXCEPTION 'Input date % does not match output date', _colvalue;
          END IF;

    ELSEIF _type = 'numeric'  then
        perform _colvalue::numeric;   

    ELSEIF _type = 'integer'  then
        perform _colvalue::integer;    

   -- other types

        END IF;   

-- exception occurs      
    EXCEPTION WHEN OTHERS THEN
         get stacked diagnostics 
        _returned_sqlstate      = RETURNED_SQLSTATE,
        _column_name        = COLUMN_NAME,
        _constraint_name    = CONSTRAINT_NAME, 
        _pg_datatype_name   = PG_DATATYPE_NAME, 
        _message_text       = MESSAGE_TEXT,
        _table_name         = TABLE_NAME,
        _schema_name        = SCHEMA_NAME,
        _pg_exception_detail    = PG_EXCEPTION_DETAIL,
        _pg_exception_hint      = PG_EXCEPTION_HINT,
        _pg_exception_context   = PG_EXCEPTION_CONTEXT;

        _message_text :=  -- write something meaningful
        _pg_exception_detail = -- write something meaningful
        _pg_exception_hint := -- write something meaningful

        -- log to something

    END;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;