I'm trying to validate strings to timestamps from several CSVs and simply casting them to timestamptz will fail due to the impossibility of forcing an unique datetime format:
select '10/31/2010'::timestamp --fail due to "different datestyle"
select '31/10/2010'::timestamp --works
I thought to_timestamp() would do the trick, but something like:
select to_timestamp('31/02/2014 14:30', 'DD/MM/YYYY HH24:MI');
will return "2014-03-03 14:30:00-05" instead of throwing an exception
So I thought of using this approach , that reverts back the output to text using to_char and comparing it with the original input, but a mask like 'DD/MM/YYYY HH24:MI' will cast "06/03/2014 0 :06" to "06/03/2014 00 :06", the strings are different!
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;
The following example fails where it should work:
select to_timestamp_safe ('06/03/2014 0:06', 'DD/MM/YYYY HH24:MI');
output:
ERROR: Input date 06/03/2014 0:06 does not match output date 2014-03-06 00:06:00
SQL state: P0001
Is there a smart way of safely validate strings to timestamptz without the above pitfalls?
The FM modifier did the trick (thanks @ErwinBrandstetter) and it's indeed kind of a general solution because we came up with this idea of creating a profile for each type of csv and the date format mask can be stored there for the columns that are date/time, most part of the columns go to an hstore column anyway and we need to keep their types stored somewhere. So I was able to create something like this, where _colmask is the datetime format mask that may or may not have a FM modifier. Then I simply perform this function against my staging table (looping for each column)
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;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.