Postgres invalid date format - postgresql

Below are some samples of date from a column in my database. Currently, these date values are stored in varchar and I am planning to set these value as timestamp in another table.
How can I set a default rules to cast all the invalid date format to a NULL value or some sort of '00/00/0000 00:00:00" format?
pickup_time
01/01/2016 07:35:00
00:00:00
31/12/2015
07:35:00
Expectation:
pickup_time
01/01/2016 07:35:00
NULL
31/12/2015 00:00:00
00/00/0000 07:35:00

Ok, here is the basic idea, so you need to set a if/else with regex (or something to figure out the pattern of the string) per each case you want to handle.
create or replace function my_super_converter_to_timestamp(arg text)
returns timestamp language plpgsql
as $$
begin
begin
-- if(arg like '00:00:00') do something ...
return arg::timestamp;
exception when others then
return null;
end;
end $$;

Related

passing variable date with fixed time to a function

I am trying to create a function which accepts two arrays, and a date. The function uses the date
in a way where I want hardcoded values of time (with timezone) which are already stated in the function body (in the orig_dataset CTE). Here is my function so far:
CREATE or replace FUNCTION f_loop_in_lockstep_final(_id_arr int[], _counter_arr int[], d_date date)
RETURNS TABLE (uc_name_ varchar)
LANGUAGE plpgsql AS
$func$
DECLARE
_id int;
_counter int;
d_date date;
BEGIN
FOR _id, _counter IN
SELECT *
FROM unnest (_id_arr, _counter_arr) t
LOOP
RETURN QUERY
with orig_dataset as (
select routes
from campaign_routes cr
where cr.created_at between 'd_date 06:00:00 +05:00' and 'd_date 18:00:00 +05:00'
)
-- a couple of further CTE's result in a final CTE called final_cte
select * from final_cte;
END LOOP;
END
$func$;
When I use the following function call:
SELECT * FROM f_loop_in_lockstep_final('{454,454}'::int[]
, '{2,3}'::int[], to_date('2023-01-17','YYYY-MM-DD'));
I receive the following error:
SQL Error [22007]: ERROR: invalid input syntax for type timestamp with time zone: "d_date 06:00:00 +05:00"
Where: PL/pgSQL function f_loop_in_lockstep_final(integer[],integer[],date) line 14 at RETURN QUERY
Well, obviously 'd_date 06:00:00 +05:00' is not a valid date literal.
You need to add a time value to the variable to create a timestamp value based on that:
where cr.created_at between d_date + '06:00:00 +05:00'::time
and d_date + '18:00:00 +05:00'::time
I am not entirely sure that using a time zone offset in a time constant works correctly, so maybe you need:
where cr.created_at between ((d_date + '06:00:00'::time) at time zone '+05:00')
and ((d_date + '18:00:00'::time) at time zone '+05:00')

Postgres date value dynamic inserting

I am scratching my head trying to fix the following:
create or replace procedure my_schema.Test()
as $$
declare
today date = now();
begin
drop table if exists my_schema.tst_table;
create table my_schema.tst_table (
todays_date varchar );
execute('
insert into my_schema.tst_table
values (' || today || ')
');
end;
$$
language plpgsql;
Basically I am trying to dynamically insert the current date into a table which will be used in later steps.
The issue I am facing is that due to the today variable looking like '2022-02-11', and because I am inserting the record dynamically, postgres is interpreting the "-" as a minus sign and inserting the value 2009 into my table.
Does anyone have a workaround for this?
Don't concatenate input values into dynamic SQL. And never store date values in a varchar column:
create or replace procedure my_schema.Test()
as $$
declare
today date := current_date;
begin
drop table if exists my_schema.tst_table;
create table my_schema.tst_table
(
todays_date date
);
execute('
insert into my_schema.tst_table
values ($1)
') using today;
end;
$$
language plpgsql;
But creating a table just to store the value of current_date seems a bit of an overkill.
You can use the cast operator to force the value conversion to VARCHAR datatype:
execute('
insert into my_schema.tst_table
values ('today'::VARCHAR)
');

Difficulties using Postgresql's EXTRACT tool via function call

I'm trying to figure out how to use the Postgresql EXTRACT function to convert a given date_variable into its equivalent day of the week. I understand that it will convert the date_variable into a numbering from 0 - 6 (0 is Sunday, 6 is Saturday etc)
I've created a simple table to test my queries. Here I will attempt to convert the start_date into its DOW equivalent.
DROP TABLE IF EXISTS test;
CREATE TABLE test(
start_date date PRIMARY KEY,
end_date date
);
INSERT INTO test (start_date, end_date) VALUES ('2021-03-31', '2021-03-31'); -- Today (wed), hence 3
INSERT INTO test (start_date, end_date) VALUES ('2021-03-30', '2021-03-30'); -- Yesterday (tues), hence 2
INSERT INTO test (start_date, end_date) VALUES ('2021-03-29', '2021-03-29'); -- Day before (mon), hence 1
If I were to run the query below
SELECT (EXTRACT(DOW FROM t.start_date)) AS day FROM test t;
It works fine, and returns the result as intended (returns a single column table with values (3, 2, 1) respectively.)
However, when I attempt to write a function to return the exact same query
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
BEGIN
RETURN QUERY
SELECT (EXTRACT(DOW FROM t.start_date)) as day
FROM test t;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_day(); -- throws error "structure of query does not match function result type"
I get an error instead. I cant seem to find the issue and don't know what is causing it.
extract() returns a double precision value, but your function is declared to return an integer. You need to cast the value:
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
BEGIN
RETURN QUERY
SELECT EXTRACT(DOW FROM t.start_date)::int as day
FROM test t;
END;
$$ LANGUAGE plpgsql;
But you don't really need PL/pgSQL for this, a language SQL function would also work.
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
SELECT EXTRACT(DOW FROM t.start_date)::int as day
FROM test t;
$$
LANGUAGE sql
stable;
As you are not passing any parameters, I would actually use a view for this:
create or replace view day_view
AS
SELECT EXTRACT(DOW FROM t.start_date)::int as day
FROM test t;
The extract function returns values of type double precision.
You declare result to be integer.
You should cast the result of EXTRACT to integer:
CREATE OR REPLACE FUNCTION get_day()
RETURNS TABLE (day integer) AS $$
BEGIN
RETURN QUERY
SELECT EXTRACT(DOW FROM t.start_date)::integer as day
FROM test t;
END;
$$ LANGUAGE plpgsql;

Postgres now() vs 'now' in function

Running into an issue where now() behaves different than 'now' when used in a function in Postgres.
drop table if exists test_date_bug;
CREATE TABLE test_date_bug
(
id serial NOT NULL,
date1 timestamp with time zone NOT NULL DEFAULT current_timestamp,
date2 timestamp with time zone NOT NULL DEFAULT 'infinity'
)
WITH (
OIDS=FALSE
);
drop function if exists test_date_bug_function(id_param bigint);
CREATE OR REPLACE FUNCTION test_date_bug_function(id_param bigint)
RETURNS void AS
$$
BEGIN
UPDATE test_date_bug SET date2 = 'now' WHERE id = id_param;
END;
$$
LANGUAGE 'plpgsql' VOLATILE
SECURITY DEFINER
SET search_path = public, pg_temp;
insert into test_date_bug DEFAULT VALUES;
insert into test_date_bug DEFAULT VALUES;
insert into test_date_bug DEFAULT VALUES;
select 1 from test_date_bug_function(1);
wait a couple seconds
select 1 from test_date_bug_function(2);
Results:
select * from test_date_bug;
id | date1 | date2
----+-------------------------------+-------------------------------
3 | 2015-12-10 12:42:01.931554-06 | infinity
1 | 2015-12-10 12:42:01.334465-06 | 2015-12-10 12:42:09.491183-06
2 | 2015-12-10 12:42:01.335665-06 | 2015-12-10 12:42:09.491183-06
(3 rows)
I would not expect the date2 on row 2 to be the same date2 as row 1.
Replacing
UPDATE test_date_bug SET date2 = 'now' WHERE id = id_param;
With
UPDATE test_date_bug SET date2 = now() WHERE id = id_param;
Sets new date as I would expect:
select * from test_date_bug;
id | date1 | date2
----+-------------------------------+-------------------------------
3 | 2015-12-10 12:43:29.480242-06 | infinity
1 | 2015-12-10 12:43:28.451195-06 | 2015-12-10 12:43:38.496625-06
2 | 2015-12-10 12:43:28.451786-06 | 2015-12-10 12:43:43.447715-06
Thoughts ?
It's not a bug, it's a feature... There are two points here.
Substitution of 'now'
Let's have a look into the documentation (Date/Time Functions and Operators):
All the date/time data types also accept the special literal value now
to specify the current date and time (again, interpreted as the
transaction start time). Thus, the following three all return the same
result:
SELECT CURRENT_TIMESTAMP;
SELECT now();
SELECT TIMESTAMP 'now'; -- incorrect for use with DEFAULT
Tip: You do not want to use the third form when specifying a DEFAULT clause while creating a table. The system will convert now to
a timestamp as soon as the constant is parsed, so that when the
default value is needed, the time of the table creation would be used!
The first two forms will not be evaluated until the default value is
used, because they are function calls. Thus they will give the desired
behavior of defaulting to the time of row insertion.
So 'now' is converted to a timestamp at parse time.
Prepared statements
Okay, but what does it mean in regard to functions? It's easy to demonstrate that a function is interpreted each time you call it:
t=# create function test() returns timestamp as $$
begin
return 'now';
end;
$$ language plpgsql;
CREATE FUNCTION
t=# select test();
test
----------------------------
2015-12-11 11:14:43.479809
(1 row)
t=# select test();
test
----------------------------
2015-12-11 11:14:47.350266
(1 row)
In this example 'now' behaves as you expected.
What is the difference? Your function uses SQL statements, and test() does not. Lets look into the documentation again (PL/pgSQL Plan Caching):
As each expression and SQL command is first executed in the function,
the PL/pgSQL interpreter parses and analyzes the command to create a
prepared statement.
And here (Prepare Statement):
PREPARE creates a prepared statement. A prepared statement is a
server-side object that can be used to optimize performance. When the
PREPARE statement is executed, the specified statement is parsed,
analyzed, and rewritten. When an EXECUTE command is subsequently
issued, the prepared statement is planned and executed. This division
of labor avoids repetitive parse analysis work, while allowing the
execution plan to depend on the specific parameter values supplied.
Hence 'now' was converted to a timestamp when prepared statement was parsed. Let's demonstrate this by creating a prepared statement outside of a function:
t=# prepare s(integer) as UPDATE test_date_bug SET date2 = 'now' WHERE id = $1;
PREPARE
t=# execute s(1);
UPDATE 1
t=# execute s(2);
UPDATE 1
t=# select * from test_date_bug;
id | date1 | date2
----+-------------------------------+-------------------------------
3 | 2015-12-11 11:01:38.491656+03 | infinity
1 | 2015-12-11 11:01:37.91818+03 | 2015-12-11 11:40:44.339623+03
2 | 2015-12-11 11:01:37.931056+03 | 2015-12-11 11:40:44.339623+03
(3 rows)
That's what happend. 'now' was converted to a timestamp once (when prepared statement was parsed), and now() was called twice.
Egor has already pointed out the reason, but since you don't seem to be convinced I will discuss the issue further. The important part from the documentation is the following:
The system will convert now to a timestamp as soon as the constant is
parsed, so that when the default value is needed, the time of the
table creation would be used!
While this does not exactly apply to your case, it can somehow explain the behavior of your procedure. As stated in the documentation, PostgreSQL will convert 'now' to a timestamp as soon as it parses it. Since it is a procedure, this will happen after the first call of the procedure. For each consecutive call the value will be the same, because it had already been converted after the first parse. So what basically happens is that you execute select 1 from test_date_bug_function(1); which converts 'now' to a timestamp that is now persistent in your procedure, and then select 1 from test_date_bug_function(2); simply uses the already converted value.
If you're not convinced yet, you can try executing(simply the statements outside of the procedure):
UPDATE test_date_bug SET date2 = 'now' WHERE id = 1;
UPDATE test_date_bug SET date2 = 'now' WHERE id = 2;
to see that you will achieve the desired behavior. This is because each UPDATE is newly compiled, while the procedure is persistent which makes the converted value persistent as well.

How to convert a text variable into a timestamp in postgreSQL?

I know how to convert a text to timestamp in postgreSQL using
SELECT to_timestamp('05 Dec 2000', 'DD Mon YYYY')
but how can I convert a text variable (inside a function) to timestamp??
In my table (table_ebscb_spa_log04) "time" is a character varying column, in which I have placed a formated date time (15-11-30 11:59:59.999 PM).
I have tried this function, in order to convert put the date time text into a variable (it always change) and convert it into timestamp...
CREATE OR REPLACE FUNCTION timediff()
RETURNS trigger AS
$BODY$
DECLARE
timeascharvar character varying;
timeastistamp timestamp;
BEGIN
IF NEW.time_type = 'Lap' THEN
SELECT t.time FROM table_ebscb_spa_log04 t INTO timeascharvar;
SELECT to_timestamp('timeascharvar', 'yy-mm-dd HH24:MI:SS.MS') INTO timeastistamp;
END IF;
RETURN timeastistamp;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION timediff()
OWNER TO postgres;
but whenever I run it in the table, it shows this ERROR message...
It seems that "to_timestamp" waits for a number to be the year, how can I get it to recognize the variable as if it were numbers?
The first parameter to to_timestamp should be your var not a string containing the name of your var:
to_timestamp(timeascharvar, 'yy-mm-dd HH24:MI:SS.MS')