Pass entire where condition as argument in postgresql - postgresql

I want to pass entire where condition as parameter in Postgrel.
Is it possible? I have created procedure like,
CREATE OR REPLACE FUNCTION public.pro_select_all_item_query_builder_data(IN cond character varying)
RETURNS TABLE(id integer, name character varying) AS
$BODY$
BEGIN
FOR id, name IN
SELECT product.id, product.name FROM product WHERE cond
.............
and call it like,
SELECT *
FROM pro_select_all_item_query_builder_data('product.status_id = 1')
It showing ERROR: argument of WHERE must be type boolean, not type character varying
Can you please support me to solve this issue?

Make sure cond has a valid condition, otherwise it will throw syntax error.
FOR id, name IN
EXECUTE ('SELECT product.id, product.name FROM product WHERE ' || cond)
LOOP
....
END LOOP;

Related

Declare a Table as a variable in a stored procedure?

I am currently working a stored procedure capable of detecting continuity on a specific set of entries..
The specific set of entries is extracted from a sql query
The function takes in two input parameter, first being the table that should be investigated, and the other being the list of ids which should be evaluated.
For every Id I need to investigate every row provided by the select statement.
DROP FUNCTION IF EXISTS GapAndOverlapDetection(table_name text, entity_ids bigint[]);
create or replace function GapAndOverlapDetection ( table_name text, enteity_ids bigint[] )
returns table ( entity_id bigint, valid tsrange, causes_overlap boolean, causes_gap boolean)
as $$
declare
x bigint;
var_r record;
begin
FOREACH x in array $2
loop
EXECUTE format('select entity_id, valid from' ||table_name|| '
where entity_id = '||x||'
and registration #> now()::timestamp
order by valid ASC') INTO result;
for var_r in result
loop
end loop;
end loop ;
end
$$ language plpgsql;
select * from GapAndOverlapDetection('temp_country_registration', '{1,2,3,4}')
I currently get an error in the for statement saying
ERROR: syntax error at or near "$1"
LINE 12: for var_r in select entity_id, valid from $1
You can iterate over the result of the dynamic query directly:
create or replace function gapandoverlapdetection ( table_name text, entity_ids bigint[])
returns table (entity_id bigint, valid tsrange, causes_overlap boolean, causes_gap boolean)
as $$
declare
var_r record;
begin
for var_r in EXECUTE format('select entity_id, valid
from %I
where entity_id = any($1)
and registration > now()::timestamp
order by valid ASC', table_name)
using entity_ids
loop
... do something with var_r
-- return a row for the result
-- this does not end the function
-- it just appends this row to the result
return query
select entity_id, true, false;
end loop;
end
$$ language plpgsql;
The %I injects an identifier into a string and the $1 inside the dynamic SQL is then populated through passing the argument with the using keyword
Firstly, decide whether you want to pass the table's name or oid. If you want to identify the table by name, then the parameter should be of text type and not regclass.
Secondly, if you want the table name to change between executions then you need to execute the SQL statement dynamically with the EXECUTE statement.

Using parameter as column name in Postgres function

I have a Postgres table bearing the following form
CREATE TABLE "public"."days"
(
"id" integer NOT NULL,
"day" character varying(9) NOT NULL,
"visits" bigint[] NOT NULL,
"passes" bigint[] NOT NULL
);
I would like to write a function that allows me to return the visits or the passees column as its result for a specified id. My first attempt goes as follows
CREATE OR REPLACE FUNCTION day_entries(INT,TEXT) RETURNS BIGINT[] LANGUAGE sql AS
'SELECT $2 FROM days WHERE id = $1;'
which fails with an error along the lines of
return type mismatch in function declared to return bigint[]
DETAIL: Actual return type is text.
If I put in visits in place of the $2 things work just as expected. It would make little sense to define several functions to match different columns from the days table. Is there a way to pass the actual column name as a parameter while still keeping Postgres happy?
You can't use parameters as identifiers (=column name), you need dynamic SQL for that. And that requires PL/pgSQL:
CREATE OR REPLACE FUNCTION day_entries(p_id int, p_column text)
RETURNS BIGINT[]
AS
$$
declare
l_result bigint[];
begin
execute format('SELECT %I FROM days WHERE id = $1', p_column)
using p_id
into l_result;
return l_result;
end;
$$
LANGUAGE plpgsql;
format() properly deals with identifiers when building dynamic SQL. The $1 is a parameter placeholder and the value for that is passed with the using p_id clause of the execute statement.

Getting Error - No function matches the given name and argument types. in Postgres

I create a function and inside the function, I used predefined function array_to_string. When I try to execute my function I am getting the following error.
ERROR: function array_to_string(integer, unknown) does not exist
LINE 1: SELECT array_to_string(id, ',') FROM ame.stops where custome...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT array_to_string(id, ',') FROM ame.stops where customer_id=customer_id and driver_id=driver_id
CONTEXT: PL/pgSQL function ame.fn_get_stopids(integer,integer) line 4 at SQL statement
SQL state: 42883
When I use array_to_string in select statement it's working fine.
select array_to_string(array(SELECT id FROM ame.stops where customer_id='31' and driver_id='770'), ', ')
Function Code
CREATE OR REPLACE FUNCTION ame.fn_Get_StopIds(customer_id integer, driver_id integer)
RETURNS text AS $$
DECLARE StopIds text;
BEGIN
SELECT array_to_string(id, ',') FROM ame.stops where customer_id=customer_id and driver_id=driver_id into StopIds;
RETURN StopIds;
END;
$$ LANGUAGE plpgsql;
the function is expecting an array, but you are passing an integer (id).
In your working attempt, you are first converting the id to an array.
You may want to build the array first using array_agg
SELECT array_to_string(array_agg(id), ',')
FROM ame.stops
where customer_id=customer_id and driver_id=driver_id
into StopIds;
But then you don't need to build and concatenate the array, you can simply do
SELECT string_agg(id::text, ',')
FROM ame.stops
where customer_id=customer_id and driver_id=driver_id
into StopIds;
You may want to order by IDs
CREATE OR REPLACE FUNCTION ame.fn_get_stopids
(_customer_id integer,
_driver_id integer)
RETURNS text AS
$$
DECLARE
stopids text;
BEGIN
SELECT string_agg(id::text, ',') INTO stopids;
FROM ame.stops
WHERE customer_id = _customer_id
AND driver_id = _driver_id
RETURN stopids;
END;
$$
LANGUAGE plpgsql;

catching select query return value in postgresql function and use it

I want to execute this function. But it got error said
ERROR:
syntax error at or near ":="
LINE 7: select result:=MAX(path_history_id)as path INTO result from...
In this function I want to:
execute select with (MAX) and it will return maximum id from a table;
catch that value (it is an integer value);
put that value into last select query where condition.
I cant find a way in postgresql to do this.
CREATE OR REPLACE FUNCTION memcache(IN starting_point_p1 character varying, IN ending_point_p1 character varying)
RETURNS TABLE(path integer, movement_id_out integer, object_id_fk_out integer, path_history_id_fk_out integer, walking_distance_out real, angel_out real, direction_out character varying, time_stamp_out timestamp without time zone, x_coordinate_out real, y_coordinate_out real, z_coordinate_out real) AS
$BODY$
DECLARE result int;
BEGIN
select result:=MAX(path_history_id)as path INTO result from path_history_info where starting_point=starting_point_p1 and ending_point =ending_point_p1 and achieve='1';
return query
select * from movement_info where path_history_id_fk=result;
END;
$BODY$
LANGUAGE plpgsql
Syntax Error
The first query inside your function needs to be changed as follows:
select MAX(path_history_id)as path INTO result
from path_history_info
where starting_point=starting_point_p1
and ending_point =ending_point_p1 and achieve='1';
A single Query
You don't actually need a stored procedure for this. A single query can achieve the same result.
select * from movement_info where path_history_id_fk =
(SELECT MAX(path_history_id) FROM path_history_info
where starting_point=starting_point_p1
and ending_point =ending_point_p1 and achieve='1';

postgres check if null then cast to numeric

I am trying check if a value is null if so the select null else cast to numeric, but it throws an error. This is actually part of an insert statement
INSERT into someTable(name,created,power)
SELECT 'xyz',now(),
case when :power ='null' then NULL else cast(:power as numeric) end from abc
error that I get is
Error: ERROR: invalid input syntax for type numeric: "null"
:power is a variable that can be given any value using java code. If I give a value of null it give an error.
In code I get the following error from the java stack trace
org.postgresql.util.PSQLException: ERROR: cannot cast type bytea to numeric
Error:
SELECT CASE WHEN 'null' = 'null' THEN NULL ELSE cast('null' AS numeric) END
No error:
DO $$
DECLARE
power text := 'null';
BEGIN
PERFORM CASE WHEN power = 'null' THEN NULL ELSE cast(power AS numeric) END;
END;
$$
Explanation:
If you build a query string, the expression cast('null' AS numeric) or simply 'null'::numeric always raises an exception, even in an ELSE block that is never executed, because it is invalid input syntax and the exception is raised during the syntax check (like the error message implies), not during execution.
A CASE statement like you display only makes sense with a parameter or variable not with literals. The second instance of the literal has no connection to the first instance whatsoever after the query string has been assembled.
For dynamic SQL like that, you need to check the value before you build the query string. Or you use a function or prepared statement and pass the value as parameter. That would work, too.
More advice after comment:
In your particular case you could check the value in the app and build a query string like this:
INSERT INTO tbl(name, abc_id, created, power)
SELECT 'xyz'
, abc_id
, now()
, <insert_value_of_power_or_NULL_here> -- automatically converted to numeric
FROM abc
You may be interested in a different approach to INSERT data from a file conditionally.
Use COPY for files local to the server or psql's meta-command \copy for files local to the client.
if the field value is null, and you want in this case to map it to some value you can use coalesce(field_name, 'Some value') or coalesce(field_name, 123).
For full documentation see here.
You have to check with the IS operator, and not with the equal when you dealing with NULL :
INSERT into someTable(name,created,power)
SELECT 'xyz',now(),
case when :power IS null then NULL else cast(:power as numeric) end from abc
INSERT into someTable(name,created,power) SELECT 'xyz',now(),
case :power when 'null' then NULL else :power end::numeric from abc
I was trying to do something similar in order to update/insert some records where a numeric value can be null or not.
You can validate a variable before you send it to the function or inside the function depending the value passed
(For me using a variable is better than use CASE WHEN THEN ELSE END CASE every time you need to validate the value)
So to work with the NULL values using a regular comparison operand in order to find a record to update can be done by turning transform_null_equals to ON
I hope this help someone
CREATE OR REPLACE FUNCTION update_insert_transaction(vcodaccount integer, vcodaccountaux text,
vdescription text, vcodgroup integer)
RETURNS integer AS $$
DECLARE
n integer = 0;
vsql text = 'NULL';
BEGIN
IF vcodaccountaux <> '' THEN
vsql = vcodaccountaux;
END IF;
SET LOCAL transform_null_equals TO ON;
EXECUTE 'UPDATE account_import_conf SET (codaccount, codaccountaux, description, codgroup) =
('||vcodaccount||','||vsql||',trim('||quote_literal(vdescription)||'),'||vcodgroup||')
WHERE codaccount='||vcodaccount||' AND codaccountaux = '||vsql||' RETURNING * ';
GET DIAGNOSTICS n = ROW_COUNT;
IF n = 0 THEN
EXECUTE 'INSERT INTO account_import_conf (codaccount, codaccountaux, description, codgroup)
SELECT '||vcodaccount||','||vsql||' ,trim('||quote_literal(vdescription)||'),'||vcodgroup||';';
END IF;
RETURN n;
END;$$
LANGUAGE plpgsql;