Cast produces 'Returned type character varying does not match expected type character varying(8)' - postgresql

Yesterday we had a PostgreSQL database upgraded to version 9.1.3. We thought we had everything tested and ready, but there is a function we missed. It returns a table type like this:
CREATE OR REPLACE FUNCTION myfunc( patient_number varchar
, tumor_number_param varchar, facility_number varchar)
RETURNS SETOF patient_for_registrar
LANGUAGE plpgsql
AS
$body$
BEGIN
RETURN QUERY
SELECT cast(nfa.patient_id_number as varchar),
...
I only only give the first column of the select because that is where the error happens. Before today this function ran fine, but now it gives this error:
ERROR: structure of query does not match function result type
Detail: Returned type character varying does not match expected type
character varying(8) in column 1. Where: PL/pgSQL function
"getwebregistrarpatient_withdeletes" line 3 at RETURN QUERY [SQL
State=42804]
The column nfa.patient_id_number is text and is being cast for the column patient_id_number in patient_for_registrar that is varchar(8). After reading about this some I think the problem is because the column length isn't being specified when casting from text. But the problem is I've tried various combinations of substrings to fix this and none are solving the problem:
substring(cast(nfa.patient_id_number as varchar) from 1 for 8),
cast(substring(nfa.patient_id_number from 1 for 8) as varchar),
cast(substring(nfa.patient_id_number from 1 for 8) as varchar(8)),
Does anyone have any pointers?

Your function ..
RETURNS SETOF patient_for_registrar
The returned row type must match the declared type exactly. You did not disclose the definition of patient_for_registrar, probably the associated composite type of a table. I quote the manual about Declaration of Composite Types:
Whenever you create a table, a composite type is also automatically
created, with the same name as the table, to represent the table's row
type.
If the first column of that type (table) is defined varchar(8) (with length modifier) - as the error message indicates, you have to return varchar(8) with the same length modifier; varchar won't do. It is irrelevant for that matter whether the string length is only 8 characters, the data type has to match.
varchar, varchar(n) and varchar(m) are different data types for PostgreSQL.
Older versions did not enforce the type modifiers, but with PostgreSQL 9.0 this was changed for plpgsql:
PL/pgSQL now requires columns of composite results to match the
expected type modifier as well as base type (Pavel Stehule, Tom Lane)
For example, if a column of the result type is declared as
NUMERIC(30,2), it is no longer acceptable to return a NUMERIC of some
other precision in that column. Previous versions neglected to check
the type modifier and would thus allow result rows that didn't
actually conform to the declared restrictions.
Two basic ways to fix your problem:
You can cast the returned values to match the definition of patient_for_registrar:
nfa.patient_id_number::varchar(8)
Or you can change the RETURNS clause. I would use RETURNS TABLE and declare a matching composite type. Here is an example.
RETURNS TABLE (patient_for_registrar varchar, col2 some_type, ...)
As an aside: I never use varchar if I can avoid it - especially not with length modifier. It offers hardly anything that the type text couldn't do. If I need a length restriction, I use a column constraint which can be changed without rewriting the whole table.

Related

Error calling procedure in posgres "No procedure matches the given name and argument types. You might need to add explicit type casts."

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.

Postgres: getting "... is out of range for type integer" when using NULLIF

For context, this issue occurred in a Go program I am writing using the default postgres database driver.
I have been building a service to talk to a postgres database which has a table similar to the one listed below:
CREATE TABLE object (
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(255) UNIQUE,
some_other_id BIGINT UNIQUE
...
);
I have created some endpoints for this item including an "Install" endpoint which effectively acts as an upsert function like so:
INSERT INTO object (name, some_other_id)
VALUES ($1, $2)
ON CONFLICT name DO UPDATE SET
some_other_id = COALESCE(NULLIF($2, 0), object.some_other_id)
I also have an "Update" endpoint with an underlying query like so:
UPDATE object
SET some_other_id = COALESCE(NULLIF($2, 0), object.some_other_id)
WHERE name = $1
The problem:
Whenever I run the update query I always run into the error, referencing the field "some_other_id":
pq: value "1010101010144" is out of range for type integer
However this error never occurs on the "upsert" version of the query, even when the row already exists in the database (when it has to evaluate the COALESCE statement). I have been able to prevent this error by updating COALESCE statement to be as follows:
COALESCE(NULLIF($2, CAST(0 AS BIGINT)), object.some_other_id)
But as it never occurrs with the first query I wondered if this inconsitency had come from me doing something wrong or something that I don't understand? And also what the best practice is with this, should I be casting all values?
I am definitely passing in a 64 bit integer to the query for "some_other_id", and the first query works with the Go implementation even without the explicit type cast.
If any more information (or Go implementation) is required then please let me know, many thanks in advance! (:
Edit:
To eliminate confusion, the queries are being executed directly in Go code like so:
res, err := s.db.ExecContext(ctx, `UPDATE object SET some_other_id = COALESCE(NULLIF($2, 0), object.some_other_id) WHERE name = $1`,
"a name",
1010101010144,
)
Both queries are executed in exactly the same way.
Edit: Also corrected parameter (from $51 to $2) in my current workaround.
I would also like to take this opportunity to note that the query does work with my proposed fix, which suggests that the issue is in me confusing postgres with types in the NULLIF statement? There is no stored procedure asking for an INTEGER arg inbetween my code and the database, at least that I have written.
This has to do with how the postgres parser resolves types for the parameters. I don't know how exactly it's implemented, but given the observed behaviour, I would assume that the INSERT query doesn't fail because it is clear from (name,some_other_id) VALUES ($1,$2) that the $2 parameter should have the same type as the target some_other_id column, which is of type int8. This type information is then also used in the NULLIF expression of the DO UPDATE SET part of the query.
You can also test this assumption by using (name) VALUES ($1) in the INSERT and you'll see that the NULLIF expression in DO UPDATE SET will then fail the same way as it does in the UPDATE query.
So the UPDATE query fails because there is not enough context for the parser to infer the accurate type of the $2 parameter. The "closest" thing that the parser can use to infer the type of $2 is the NULLIF call expression, specifically it uses the type of the second argument of the call expression, i.e. 0, which is of type int4, and it then uses that type information for the first argument, i.e. $2.
To avoid this issue, you should use an explicit type cast with any parameter where the type cannot be inferred accurately. i.e. use NULLIF($2::int8, 0).
COALESCE(NULLIF($51, CAST(0 AS BIGINT)), object.some_other_id)
Fifty-one? Realy?
pq: value "1010101010144" is out of range for type integer
Pay attention, the data type in the error message is an integer, not bigint.
I think the reason for the error is out of showed code. So I take out a magic crystal ball and make a pass with my hands.
an "Install" endpoint which effectively acts as an upsert function like so
I also have an "Update" endpoint
Do you call endpoint a PostgreSQL function (stored procedure)? I think yes.
Also $1, $2 looks like PostgreSQL function arguments.
The magic crystal ball says: you have two PostgreSQL function with different data types of arguments:
"Install" endpoint has $2 function argument as a bigint data type. It looks like CREATE FUNCTION Install(VARCHAR(255), bigint)
"Update" endpoint has $2 function argument as an integer data type, not bigint. It looks like CREATE FUNCTION Update(VARCHAR(255), integer).
At last, I would rewrite your condition more understandable:
UPDATE object
SET some_other_id =
CASE
WHEN $2 = 0 THEN object.some_other_id
ELSE $2
END
WHERE name = $1

Postgres: how to excecute the query for Sum as it is giving error?

I am using sum function to find the total but getting error.
Here is the query:
select sum(col1)
from table_name
where col2="abc"
Error: function sum(text) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts
Assuming the text column contains text numbers, not actual integers, then it would explain the error you are seeing. You might get around this by first casting text to integer, then summing:
SELECT SUM(text::int)
FROM yourTable;

Postgres - insert NUMERIC[] value '0' in lower UNION select statement

In POSTGRES - I am trying to create a view from 2 tables. When the value of '0' is coded for insertion as a value for the EAST_LONGITUDE_NMBR column of datatype NUMERIC[24,20] in the lower portion of a UNION select statement, an ERROR Message is generated.
The view EXTENTS' column EAST_LONGITUDE_NMBR comes from the table and column, CELL_EXTENT.EAST_LONGITUDE_NMBR with a datatype of NUMERIC[24,20]
The following is the code.
CREATE VIEW EXTENTS
(
ID,
EXTENT_TYPE,
NAME,
EAST_LONGITUDE_NMBR
)
AS
SELECT
"CELL_EXTENT"."CELL_ID_NMBR",
'CELL',
UPPER ("CELL_EXTENT"."CELL_NAME"),
"CELL_EXTENT"."EAST_LONGITUDE_NMBR"
FROM "EARTH"."CELL_EXTENT"
UNION
(SELECT
"AREA_INTEREST"."AREA_ID_NMBR",
'GEOPOLITICAL',
UPPER ("AREA_INTEREST"."AREA_NAME"),
0
FROM "EARTH"."AREA_INTEREST");
The inserted value '0' in the lower UNION select causes the following error in the creation of view EXTENTS.
ERROR: UNION types numeric[] and integer cannot be matched
I have tried the following and received the errors shown:
0 ERROR: UNION types numeric[] and integer cannot be matched
0.0 ERROR: UNION types numeric[] and numeric cannot be matched
0.0::NUMERIC[] ERROR: cannot cast type numeric to numeric[]
0::NUMERIC[] ERROR: cannot cast type integer to numeric[]
I have checked numerous websites with discussions about the Postgres datatypes, particularly NUMERIC, NUMERIC[], INTEGER, DECIMAL
Difference between DECIMAL and NUMERIC datatype in PSQL
https://github.com/npgsql/npgsql/issues/655
https://www.cybertec-postgresql.com/en/mapping-oracle-datatypes-to-postgresql/
http://www.postgresqltutorial.com/postgresql-cast/
http://www.postgresqltutorial.com/postgresql-to_number/
I could go on, but you get the picture. There is a lot about datatypes but there are no examples for '0' as an actual value in Postgres code for a column of datatype NUMERIC[] in a UNION statement.
I feel this is a simple fix, a couple of keystrokes here or there to set the value proper, but it eludes me. I am using pgAdmin4.
Can you help?
Thanks,
Margaret
Seems easy: use an array instead of 0.
Depending on what you prefer, you could use
ARRAY[]::numeric[] -- empty array
or
ARRAY[0]::numeric[] -- array with a single 0

How to find out the length of a varchar parameter in a postgres function

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.