PostgreSQL Custom operator compare varchar and integer - postgresql

-- PostgreSQL 9.6.2 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-17), 64-bit.
-- Installed from offical repository.
-- No any changes in postgresql.conf .
-- CentOS release 6.8.
-- User: postgres.
-- Used pgAdmin3 LTS by BigSQL.
-- No any unusual log in server.
I have many queries.
in that case I need compare character varying data type (may be a table field) with integer value.
--Result is True or False
select '10' = 10;
select '10' = '10';
select '10'::character varying = '10'::character varying;
select '10'::character varying = 'foo bar';
select '10'::character varying = 'foo bar'::character varying;
select 'foo bar' = 'foo bar';
select '10'::character varying = '10';
--Result is "operator does not exist: character varying = integer"
select '10'::character varying = 10;
so i create a custom operator for compare character varying and integer.
step 1: create simple function
CREATE OR REPLACE FUNCTION public.is_equal_char_int(character varying, integer) RETURNS boolean AS
$BODY$
BEGIN
IF $1 = $2::character varying THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
End;
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
step 2: create new operator
CREATE OPERATOR public.=(
PROCEDURE = is_equal_char_int,
LEFTARG = character varying,
RIGHTARG = integer);
so i resoleved my problem and
select '10'::character varying = 10;
return true value.
and new problem is:
when i compare character varying value with unkown data type value, postgresql use my custom operator.
select '10'::character varying = 'foo bar';
result is :
invalid input syntax for integer: "foo bar"
select pg_typeof('foo bar');
return unkown data type.
and next step I create new operator for compare character varying and unkown data type.
Step 1:
CREATE OR REPLACE FUNCTION public.is_equal_char_unknown(character varying, unknown)
RETURNS boolean AS
$BODY$
BEGIN
IF $1 = $2::character varying THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
End;
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
step 2:
CREATE OPERATOR public.=(
PROCEDURE = is_equal_char_unknown,
LEFTARG = character varying,
RIGHTARG = unknown);
when I run
select '10'::character varying = 'foo bar';
I give
ERROR: operator is not unique: character varying = unknown.
So I'm in a hole.

To understand how type resolution is done for operators in PostgreSQL, read the operator type resolution rules in the documentation.
In your special case, the following operators remain after step 3.a:
Your custom operator (character varying = integer).
character = character (implicit conversion from character varyingto character).
name = name (implicit conversion from character varyingto name).
text = text (implicit conversion from character varyingto text).
Rule 3.c then chooses your operator, because it is the only one with an exact type match. Without your operator, step 3.d would have chosen text = text, because text is the only preferred type of the string category.
What you are doing at the moment is discovering why certain operators are not defined in PostgreSQL, namely that defining new comparison operators for new type combinations results in ambiguities that lead to errors because PostgreSQL cannot decide which operator to use.
At the heart of the problem is PostgreSQL's ability to overload operators, i.e. to have several operators with the same name. Yet this is a good feature, and the system of casts and operators has been carefully balanced to make the experience as good as possible. The unknown type is also part of that system.
In other words, PostgreSQL is trying to guess what you mean, but this is not always possible. If you want to compare a (not unknown) string and a number, what do you want? Should they be compared as numbers or as strings? Should '010' be the same as 10 or not? PostgreSQL doesn't know what you mean and gives up.

There could be another option by defining how the cast between varchars and numerics should be made:
CREATE CAST (VARCHAR AS NUMERIC) WITH INOUT AS IMPLICIT;
This would make it possible to do comparisons like this:
SELECT '1'::character varying = 1::int;
> true
SELECT '01'::character varying = 1::int;
> true
SELECT '2'::character varying = 1::int;
> false
select '10'::character varying = 'foo bar';
> false
More about creating casts in postgresql here:
https://www.postgresql.org/docs/current/sql-createcast.html

Related

Postgresql function invocation

I've written a postgresql function shown as follows
CREATE OR REPLACE FUNCTION public."UpdateTx"(IN "instructionId" character varying,IN txdata character varying,IN txdetail character varying,IN txstatus character varying,IN resid character varying,IN "timestamp" bigint)
RETURNS character varying
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100
AS $BODY$DECLARE updateClause varchar;
BEGIN
IF instructionId = '' THEN
RAISE EXCEPTION 'instruction id is missing';
END IF;
IF txstatus = '' THEN
RAISE EXCEPTION 'tx status is missing';
END IF;
updateClause := CONCAT('txstatus= ', txstatus);
IF txData != '' THEN
updateClause = CONCAT(updateClause, ', ', 'txdata=', txdata);
END IF;
EXECUTE 'UPDATE transactions SET $1 WHERE instructionid=instructionid' USING updateClause;
END;
$BODY$;
So it expects 5 varchar & 1 bigint as input arguments.
I've tried to execute the following SQL query
Select UpdateTx('123'::varchar, 'test'::varchar, 'test'::varchar, 'test'::varchar, 'test2'::varchar, 4124::bigint)
but it keeps showing this error message
ERROR: function updatetx(character varying, character varying, character varying, character varying, character varying, bigint) does not exist
LINE 1: Select UpdateTx('123'::varchar, 'test'::varchar, 'test'::var...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
SQL state: 42883
Character: 8
Is the syntax incorrect?
Appreciate any suggestions or answers :)
Your immediate problem is that your function is declared as case-sensitive (with surrounding double quotes), but you call it in a case-insensitive manner (without the quotes, which, to Postgres, is equivalent to all lower caps). The names just do not match.
But there is more to it:
the way you pass variables is not OK; instead of concatenating part of the query into variable updateClause, you should pass a parameter for each value that needs to be passed to the variable - but better yet, you don't actually need dynamic SQL for this
Don't use variables that conflict with column names
I suspect that you want nulls instead of empty string (it makes much more sense to indicate the "lack" of a value)
the function needs to return something
I also notice that you are not using all arguments that are passed to function
Here is a function code, that, at least, compiles. You can start from there and adapt it to your exact requirement:
CREATE OR REPLACE FUNCTION public.UpdateTx(
IN pInstructionId character varying,
IN pTxdata character varying,
IN pTxdetail character varying, -- not used in the function
IN pTxstatus character varying,
IN pResid character varying, -- not used in the function
IN pTimestamp bigint - - not used in the function
)
RETURNS character varying
LANGUAGE plpgsql
VOLATILE
PARALLEL UNSAFE
COST 100
AS $BODY$
BEGIN
IF pInstructionId IS NULL THEN
RAISE EXCEPTION 'instruction id is missing';
END IF;
IF pTxstatus IS NULL THEN
RAISE EXCEPTION 'tx status is missing';
END IF;
UPDATE transactions
SET txstatus = pTxstatus, txData = COALESCE(pTxdata, txData)
WHERE instructionid = pInstructionId;
RETURN 1; -- put something more meaningful here
END;
$BODY$;
You would invoke it as follows:
select UpdateTx(
'123'::varchar,
'test'::varchar,
'test'::varchar,
'test'::varchar,
'test2'::varchar,
4124::bigint
)
Demo on DB Fiddle

How to make VARCHAR the preferred type for strings in PostgreSQL?

PostgreSQL have the "preferred types" for type categories.
For strings, the preferred type is TEXT, so if one calls
SELECT PG_TYPEOF('FOO' || 'BAR');
The result will be TEXT.
I've already tried to change the pg_type table like this:
UPDATE PG_CATALOG.PG_TYPE
SET TYPISPREFERRED = (UPPER(TYPNAME) = 'VARCHAR')
WHERE TYPCATEGORY = 'S'
Now the TYPISPREFERRED column for the type TEXT is false and for VARCHAR is true, but SELECT PG_TYPEOF('FOO' || 'BAR'); still returns TEXT.
I need it to return VARCHAR because I'm migrating an old Delphi system from Firebird and there are too much code that use 'FOO' || 'BAR' as a TStringField, but with the TEXT type it needs to be a TMemoField, which is totally different.
Edit¹:
I tried this:
with(oConnection.FormatOptions)do
begin
with MapRules.Add do
begin
SizeMax := 200;
TypeMask := 'text';
TargetDataType := dtWideString;
end;
end;
Tried this too
with(oConnection.FormatOptions)do
begin
with MapRules.Add do
begin
SizeMax := 1; // Tried with -1 and 0 too
SizeMax := 200;
SourceDataType := dtMemo;
TargetDataType := dtAnsiString;
end;
end;
But it doesn't work. I think it gets the size set to the value, not the actual size of it. In PostgreSQL the TEXT type do not need any size to be set, so it always get it as a 0 size and convert all TEXT to String doing a big mess that is not pretty.
The || operator returns type text, so I don't think changing the preferred type will affect anything. You could define your own operator:
-- Before custom operator
select pg_typeof('a'::varchar || 'b'::varchar);
pg_typeof
-----------
text
(1 row)
CREATE OR REPLACE FUNCTION public.varcharcat(first varchar, second varchar) RETURNS VARCHAR
IMMUTABLE
PARALLEL SAFE
RETURNS NULL ON NULL INPUT
LANGUAGE SQL
AS $$
SELECT (first operator(pg_catalog.||) second)::varchar;
$$;
CREATE OR REPLACE OPERATOR public.|| (
FUNCTION = varcharcat,
LEFTARG = varchar,
RIGHTARG = varchar
);
select pg_typeof('a'::varchar || 'b'::varchar);
pg_typeof
-------------------
character varying
(1 row)
-- Note that text || text still returns text
select pg_typeof('a' || 'b');
pg_typeof
-----------
text
(1 row)
I will say that I don't think this is a great idea and is likely to have unintended side effects. It would be much better to get the Delphi system to cast the postgres types appropriately.

Why am I getting syntax error at or near "#"

When trying to join two tables and update one of them, I'm receiving an unexpected error from this function right here:
CREATE OR REPLACE FUNCTION tsi.update_data(_creation_time int)
RETURNS VOID
AS $$
BEGIN
EXECUTE format($sql$
UPDATE tsi.forecasts_%s a SET
a."incidents # 01:00" = b.n_incid,
a."road # 01:00" = b.n_roads
FROM tgi_tmp b WHERE a.link_ix = b.link_id;
$sql$,_creation_time);
END $$ LANGUAGE plpgsql;
It gives me this error message:
syntax error at or near "#"
cidents # 01:00" = n_incid,
^
Do anyone know why I'm getting this error? The tables do contain the mentioned columns, so that is not the problem. Is postgres having a hard time dealing with string-columns in an Execute-format?
Postgres version: 10.5
Simplified table structure:
CREATE TABLE tsi.forecasts_%s (
link_ix int PRIMARY KEY,
"slipincidents # 00:00" SMALLINT NOT NULL,
"roadcoverage # 00:00" SMALLINT NOT NULL,
);
and tgi_tmp:
CREATE TEMPORARY TABLE tgi_tmp (
link_id TEXT,
n_road SMALLINT,
n_incid SMALLINT
CONSTRAINT tgi_tmp_pkey PRIMARY KEY (link_id)
);
Weird it complaints about the # doesn't do that for me. What however is wrong is specifying the table (alias) for the columns you are assigning to in the set. You should only specify the column names.
CREATE OR REPLACE FUNCTION tsi.update_data(_creation_time int)
RETURNS VOID
AS $$
BEGIN
EXECUTE format($sql$
UPDATE tsi.forecasts_%s a SET
"incidents # 01:00" = b.n_incid,
"road # 01:00" = b.n_roads
FROM tgi_tmp b WHERE a.link_ix = b.link_id;
$sql$,_creation_time);
END $$ LANGUAGE plpgsql;
Trying to debug your function, I get these error messages, one after the other:
ERROR: operator does not exist: integer = text
ERROR: column b.n_roads does not exist
ERROR: column "a" of relation "tsi_forecasts_1" does not exist
ERROR: column "incidents # 01:00" of relation "tsi_forecasts_1" does not exist
Each after fixing the previous error.
I arrive at this working version:
CREATE OR REPLACE FUNCTION tsi_update_data(_creation_time int)
RETURNS VOID AS
$func$
BEGIN
EXECUTE format($sql$
UPDATE tsi_forecasts_%s a
SET "slipincidents # 00:00" = b.n_incid -- don't table-qualify target cols
, "roadcoverage # 00:00" = b.n_road -- col names in q don't match
FROM tgi_tmp b
WHERE a.link_ix = b.link_id::int; -- your db design forces a cast
$sql$, _creation_time);
END
$func$ LANGUAGE plpgsql;
But I cannot reproduce your error:
syntax error at or near "#"
cidents # 01:00" = n_incid,
^
Which must be invoked by something that's not in the question, like outer double-quoting or special meaning of characters in your unnamed client program.
All that aside, it might pay to reconsider your naming convention and your db design. Use legal, lower-case, unquoted identifiers and matching data types (link_ix is int while link_ix is text).
Works for some reason when I'm not specifying the offset. Like this:
CREATE OR REPLACE FUNCTION tsi.update_data(_creation_time int)
RETURNS VOID
AS $$
BEGIN
EXECUTE format($sql$
UPDATE tsi.forecasts_%s a SET
"incidents # %s" = b.n_incid,
"road # %s" = b.n_roads
FROM tgi_tmp b WHERE a.link_ix = b.link_id;
$sql$,_creation_time, '01:00', '01:00');
END $$ LANGUAGE plpgsql;

how to implement contains operator in Postgres

How to implement contains operator for strings which returns true if left string contains in right string.
Operator name can be any. I tried ## and code below but
select 'A' ## 'SAS'
returns false.
How to fix ?
CREATE OR REPLACE FUNCTION public.contains(searchFor text, searchIn text)
RETURNS bool
AS $BODY$ BEGIN
RETURN position( searchFor in searchIn)<>0;
END; $BODY$ language plpgsql immutable RETURNS NULL ON NULL INPUT;
CREATE OPERATOR public.## (
leftarg = text,
rightarg = text,
procedure = public.contains
);
Using Postgres 9.1 and above in windows and linux.
select contains('A' , 'SAS' )
returns true as expected.
Update
I tried in 9.1 code from answer:
CREATE OR REPLACE FUNCTION public.contains(searchFor text, searchIn text)
RETURNS bool
LANGUAGE sql IMMUTABLE
AS $BODY$
SELECT position( searchFor in searchIn )<>0;
$BODY$;
CREATE OPERATOR public.<# (
leftarg = text,
rightarg = text,
procedure = public.contains
);
but got error
ERROR: column "searchin" does not exist
LINE 5: SELECT position( searchFor in searchIn )<>0;
How to make it work in 9.1 ? In 9.3 it works.
Using
"PostgreSQL 9.1.2 on x86_64-unknown-linux-gnu, compiled by gcc-4.4.real (Debian 4.4.5-8) 4.4.5, 64-bit"
PostgreSQL already defines an ## operator on (text,text) in the pg_catalog schema, which is defined as:
regress=> \do ##
List of operators
Schema | Name | Left arg type | Right arg type | Result type | Description
------------+------+---------------+----------------+-------------+-------------------
pg_catalog | ## | text | text | boolean | text search match
That is taking precedence over the ## you defined in the public schema.
I suggest using the operator <# for contains, because that's consistent with the array contains and contained-by operators. There's no usage of it in pg_catalog for text ... but there's no guarantee one won't be added in a future version or by an extension.
If you want to guarantee that your operators take precedence over those in pg_catalog, you need to put them in a different schema and put it first on the search path, explicitly before pg_catalog. So something like:
CREATE SCHEMA my_operators;
CREATE OR REPLACE FUNCTION my_operators.contains(searchFor text, searchIn text)
RETURNS bool
LANGUAGE sql IMMUTABLE
AS $BODY$
SELECT position( searchFor in searchIn)<>0;
$BODY$;
CREATE OPERATOR my_operators.<# (
leftarg = text,
rightarg = text,
procedure = public.contains
);
then
SET search_path = 'my_operators, pg_catalog, public';
which you can do with ALTER USER and/or ALTER DATABASE to make it a default.

Return SETOF rows from PostgreSQL function

I have a situation where I want to return the join between two views. and that's a lot of columns. It was pretty easy in sql server. But in PostgreSQL when I do the join. I get the error "a column definition list is required".
Is there any way I can bypass this, I don't want to provide the definitions of returning columns.
CREATE OR REPLACE FUNCTION functionA(username character varying DEFAULT ''::character varying, databaseobject character varying DEFAULT ''::character varying)
RETURNS SETOF ???? AS
$BODY$
Declare
SqlString varchar(4000) = '';
BEGIN
IF(UserName = '*') THEN
Begin
SqlString := 'select * from view1 left join ' + databaseobject + ' as view2 on view1.id = view2.id';
End;
ELSE
Begin
SqlString := 'select * from view3 left join ' + databaseobject + ' as view2 on view3.id = view2.id';
End;
END IF;
execute (SqlString );
END;
$BODY$
Sanitize function
What you currently have can be simplified / sanitized to:
CREATE OR REPLACE FUNCTION func_a (username text = '', databaseobject text = '')
RETURNS ????
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE
format ('SELECT * FROM %s v1 LEFT JOIN %I v2 USING (id)'
, CASE WHEN username = '*' THEN 'view1' ELSE 'view3' END
, databaseobject);
END
$func$;
You only need additional instances of BEGIN ... END in the function body to start separate code blocks with their own scope, which is rarely needed.
The standard SQL concatenation operator is ||. + is a "creative" addition of your former vendor.
Don't use CaMeL-case identifiers unless you double-quote them. Best don't use them at all See:
Are PostgreSQL column names case-sensitive?
varchar(4000) is also tailored to a specific limitation of SQL Server. It has no specific significance in Postgres. Only use varchar(4000) if you actually need a limit of 4000 characters. I would just use text - except that we don't need any variables at all here, after simplifying the function.
If you have not used format(), yet, consult the manual here.
Return type
Now, for your actual question: The return type for a dynamic query can be tricky since SQL requires that to be declared at call time at the latest. If you have a table or view or composite type in your database already matching the column definition list, you can just use that:
CREATE FUNCTION foo()
RETURNS SETOF my_view AS
...
Else, spell the column definition list with out with (simplest) RETURNS TABLE:
CREATE FUNCTION foo()
RETURNS TABLE (col1 int, col2 text, ...) AS
...
If you are making the row type up as you go, you can return anonymous records:
CREATE FUNCTION foo()
RETURNS SETOF record AS
...
But then you have to provide a column definition list with every call, so I hardly ever use that.
I wouldn't use SELECT * to begin with. Use a definitive list of columns to return and declare your return type accordingly:
CREATE OR REPLACE FUNCTION func_a(username text = '', databaseobject text = '')
RETURNS TABLE(col1 int, col2 text, col3 date)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE
format ($f$SELECT v1.col1, v1.col2, v2.col3
FROM %s v1 LEFT JOIN %I v2 USING (id)$f$
, CASE WHEN username = '*' THEN 'view1' ELSE 'view3' END
, databaseobject);
END
$func$;
For completely dynamic queries, consider building the query in your client to begin with, instead of using a function.
You need to understand basics first:
Refactor a PL/pgSQL function to return the output of various SELECT queries
PL/pgSQL in the Postgres manual
Then there are more advanced options with polymorphic types, which allow you to pass the return type at call time. More in the last chapter of:
Refactor a PL/pgSQL function to return the output of various SELECT queries