CREATE or replace PROCEDURE mytransactions (n_transactions_id VARCHAR,
n_transaction_amount SMALLINT,
n_transaction_date TIMESTAMP,
n_Delivery_date Date,
n_customer_id VARCHAR,
n_product_id VARCHAR,
n_store_id VARCHAR)
LANGUAGE plpgsql AS
$BODY$
BEGIN
INSERT INTO transactions
(transactions_id,
transaction_amount,
transaction_date,
Delivery_date,
customer_id,
product_id,
store_id)
VALUES
(n_transactions_id, n_transaction_amount,
n_transaction_date,
n_Delivery_date,
n_customer_id,
n_product_id,
n_store_id);
END;
$BODY$
Here is my stored procedure, it creates successfully, however once I call
CALL mytransactions
('555', 3, current_timestamp , to_date('2022-10-25','YYYY-MM-DD'),
'003', '300', '002RW');
it I get an error.
ERROR: procedure mytransactions(unknown, integer, timestamp with time zone, date, unknown, unknown, unknown) does not exist
LINE 1: CALL mytransactions
^
HINT: No procedure matches the given name and argument types. You might need to add explicit type casts.
Here you can find full tables https://dbfiddle.uk/9_NIQDw6
You need to typecast the parameters. This will work, allthough you'll get other errors when the procedure does it's things.
CALL mytransactions
('555'::varchar, 3::smallint, current_timestamp::timestamp , to_date('2022-10-25','YYYY-MM-DD'),
'003'::varchar, '300'::varchar, '002RW'::varchar);
The issue is with your second and third arguments. As you can see from the error, Postgres is assuming the second argument (3) is an integer, not smallint and the third argument is created using current_timestamp, which returns a timestamp with time zone, not a timestamp.
You can fix the smallint issue by simply casting to a smallint. For the timestamp, you'll need to figure out what value you actually want in there. I would recommend using timestamp with time zone everywhere, if possible.
Here's an example of calling your procedure that will work, but the timestamp may not be what you actually want:
CALL mytransactions
('555', 3::smallint, current_timestamp::timestamp , to_date('2022-10-25','YYYY-MM-DD'),
'003', '300', '002RW');
Be careful with that conversion to timestamp as the result will depend on the timezone settings of the SQL client.
Your argument data types don't match the parameter data types, and there are no implicit type casts that can be applied. For example, 3 is an integer, which cannot be cast to smallint implicitly. You'd have to use an explicit type cast like CAST (3 AS smallint).
To avoid that problem, it is a good practice to use preferred data types for function parameters. Each class of data type has one of them. For string types, it is text, for numbers numeric and for date/time it is timestamp with time zone. Then the type resolution rules will usually work the way you want.
I am using a 3rd party application (Debezium Connector). It has to write date time strings in ISO-8601 format into a TIMESTAMPTZ column. Unfortunately this fails, because there is no implicit cast from varchar to timestamp tz.
I did notice that the following works:
SELECT TIMESTAMPTZ('2021-01-05T05:17:46Z');
SELECT TIMESTAMPTZ('2021-01-05T05:17:46.123Z');
I tried the following:
Create a function and a cast
CREATE OR REPLACE FUNCTION varchar_to_timestamptz(val VARCHAR)
RETURNS timestamptz AS $$
SELECT TIMESTAMPTZ(val) INTO tstz;
$$ LANGUAGE SQL;
CREATE CAST (varchar as timestamptz) WITH FUNCTION varchar_to_timestamptz (varchar) AS IMPLICIT;
Unfortunately, it gives the following errors:
function timestamptz(character varying) does not exist
I also tried the same as above but using plpgsql and got the same error.
I tried writing a manual parse, but had issues with the optional microsecond segment which gave me the following
CREATE OR REPLACE FUNCTION varchar_to_timestamptz (val varchar) RETURNS timestamptz AS $$
SELECT CASE
WHEN $1 LIKE '%.%'
THEN to_timestamp($1, 'YYYY-MM-DD"T"HH24:MI:SS.USZ')::timestamp without time zone at time zone 'Etc/UTC'
ELSE to_timestamp($1, 'YYYY-MM-DD"T"HH24:MI:SSZ')::timestamp without time zone at time zone 'Etc/UTC' END $$ LANGUAGE SQL;
Which worked, but didn't feel correct.
Is there a better way to approach this implicit cast?
If the value should be converted upon insert, define an assignment cast. You need no function; using the type input and output functions will do:
CREATE CAST (varchar AS timestamptz) WITH INOUT AS ASSIGNMENT;
Be warned that messing with the casts on standard data types can lead to problems, because it increases the ambiguity. It would be much better if you could find a way to use an explicit cast.
For context:
I have a sql function that takes an array of strings. Those strings are sql expressions that are stored in a table and used some time later for dynamically creating some queries.
I want to restrict the data types of those expressions to some limited set. For that I intend to evaluate the expressions and check the data type with pg_typeof like so:
create function fun(expressions text[]) returns void as $$
declare
expression_type text;
begin
for i in 1..array_length(expressions, 1) loop
execute format('select pg_typeof((select %s from some_table where false))', expressions[i]) into expression_type ;
-- check that expression_type has legal value, raise exception otherwise
end loop;
-- store expressions for later use
end;
$$ language plpgsql;
For example suppose that integer and timestamp without time zone are the allowed types.
I'd like to list the allowed types in an enum:
create type supported_types as enum ('integer', 'timestamp with time zone');
For some types PostgreSQL documentatsion also mentions alternative names, e.g int4 instead of integer, timestamp instead of timestamp without time zone etc.
My queston is that do I have to worry about these "alternative names" for types when I enumerate the ones I care about?
I.e if I include integer do I also have to include int4 or in other words does pg_typeof ever return int4 instead of integer (or timestamp instead of timestamp without time zone etc)?
It does not.
Function internally (C code) returns OID, but since in PostgreSQL it is declared as returning regtype, it is cast to it. Since OID is the same regardless of name/alias for type, it will always give the same result.
postgres=# SELECT 'int4'::regtype::oid::regtype;
regtype
---------
integer
(1 row)
I'm trying to create an index on the cast of a varchar column to date. I'm doing something like this:
CREATE INDEX date_index ON table_name (CAST(varchar_column AS DATE));
I'm getting the error: functions in index expression must be marked IMMUTABLE But I don't get why, the cast to date doesn't depends on the timezone or something like that (which makes a cast to timestamp with time zone give this error).
Any help?
Your first error was to store a date as a varchar column. You should not do that.
The proper fix for your problem is to convert the column to a real date column.
Now I'm pretty sure the answer to that statement is "I didn't design the database and I cannot change it", so here is a workaround:
CAST and to_char() are not immutable because they can return different values for the same input value depending on the current session's settings.
If you know you have a consistent format of all values in the table (which - if you had - would mean you can convert the column to a real date column) then you can create your own function that converts a varchar to a date and is marked as immutable.
create or replace function fix_bad_datatype(the_date varchar)
returns date
language sql
immutable
as
$body$
select to_date(the_date, 'yyyy-mm-dd');
$body$
ROWS 1
/
With that definition you can create an index on the expression:
CREATE INDEX date_index ON table_name (fix_bad_datatype(varchar_column));
But you have to use exactly that function call in your query so that Postgres uses it:
select *
from foo
where fix_bad_datatype(varchar_column) < current_date;
Note that this approach will fail badly if you have just one "illegal" value in your varchar column. The only sensible solution is to store dates as dates,
Please provide the database version, table ddl, and some example data.
Would making your own immutable function do what you want, like this? Also look into creating a new cast in the docs and see if that does anything for you.
create table emp2 (emp2_id integer, hire_date VARCHAR(100));
insert into emp2(hire_date)
select now();
select cast(hire_date as DATE)
from emp2
CREATE FUNCTION my_date_cast(VARCHAR) RETURNS DATE
AS 'select cast($1 as DATE)'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
CREATE INDEX idx_emp2_hire_date ON emp2 (my_date_cast(hire_date));
I'm trying to find out the parameter length for a varchar parameter passed into a postgres function.
The SQL I have just now has no values in the character_maximum_length column where I would have expected to find this value
SELECT *
FROM information_schema.parameters
WHERE specific_schema='public'
AND specific_name like 'foo'
ORDER BY ordinal_position
I don't think postgresql keeps this information. If I create function foo(varchar(100)) returns boolean ... and then dump the schema with pg_dump, I find:
CREATE FUNCTION foo(character varying) RETURNS boolean
LANGUAGE sql
AS $$select true$$;
The '100' specification is gone. And passing a 150-character string to foo(varchar) is not trapped or anything. By contrast, if I create a domain based on varchar(100) and define the function in terms of that, then passing an overlong string is trapped.