CREATE OR REPLACE FUNCTION fbind(IN p_psudo_datatype_i character varying,IN p_parameter_position_i int)RETURNS text AS$BODY$BEGINCASE p_psudo_datatype_iWHEN 'cvs num' THENreturn '= ANY ((''{''||$'||p_parameter_position_i::text||'||''}'')::bigint[] ) '::text;WHEN 'bigint' THENreturn '= TO_NUMBER($'||p_parameter_position_i::text||',''99999999999999999999'') '::text;ELSEreturn 'datatype not implemented'::text;END CASE;END;$BODY$LANGUAGE plpgsql VOLATILECOST 100;
DO $$DECLAREsql text := 'select vendor_name from tx_vendor WHERE active_flag = ''Y'' ';v_bind_values text[];v_vendor_key bigint := 1017;v_cvs_code2tcode character varying (30) := '2222,5235,7979';v_vendor_name text;BEGINv_bind_values := array_append(v_bind_values, v_vendor_key::text);sql := sql || 'and vendor_key'||fbind('bigint'::text,array_length(v_bind_values,1));-- in a real use situation, the following 2 lines of code may or may not be-- executed, meaning, we will not know how many bind variablesv_bind_values := array_append(v_bind_values,v_cvs_code2tcode::text);sql := sql || 'and code2tcode'||fbind('cvs num'::text,array_length(v_bind_values,1));case array_length(v_bind_values,1)when 1 thenexecute sql into strict v_vendor_name using v_bind_values[1];when 2 thenexecute sql into strict v_vendor_name using v_bind_values[1], v_bind_values[2];elseraise exception 'undefined number of bind variables' ;end case;raise notice 'sql:%', sql ;raise notice 'v_vendor_name:%',v_vendor_name;END$$;
NOTICE:sql:select vendor_name from tx_vendor WHERE active_flag = 'Y'and vendor_key= TO_NUMBER($1,'99999999999999999999')and code2tcode= ANY (('{'||$2||'}')::bigint[] )NOTICE:v_vendor_name:Irwin Union BankQuery returned successfully with no result in 15 msec.
I'm a postgres noob, so I am looking for advice/ comments.My example code here demonstrates the solution to a real world situation where I am converting a PL/SQL Package (Oracle) to a pgPL/SQL function. The original package constructs an elaborate SELECT statement which can look extremely different depending on the the values of various parameters and the results of table lookups. The only thing that does not vary are the columns of the constructed SELECT statement.
v_vendor_name text;BEGINv_bind_values := array_append(v_bind_values, v_vendor_key::text);sql := sql || 'and vendor_key'||fbind('bigint'::text,array_length(v_bind_values,1));-- in a real use situation, the following 2 lines of code may or may not be-- executed, meaning, we will not know how many bind variablesv_bind_values := array_append(v_bind_values,v_cvs_code2tcode::text);sql := sql || 'and code2tcode'||fbind('cvs num'::text,array_length(v_bind_values,1));case array_length(v_bind_values,1)when 1 thenexecute sql into strict v_vendor_name using v_bind_values[1];when 2 thenexecute sql into strict v_vendor_name using v_bind_values[1], v_bind_values[2];elseraise exception 'undefined number of bind variables' ;end case;raise notice 'sql:%', sql ;raise notice 'v_vendor_name:%',v_vendor_name;END$$;
RUN results ... shows the SQL statement that was constructedNOTICE:sql:select vendor_name from tx_vendor WHERE active_flag = 'Y'and vendor_key= TO_NUMBER($1,'99999999999999999999')and code2tcode= ANY (('{'||$2||'}')::bigint[] )NOTICE:v_vendor_name:Irwin Union BankQuery returned successfully with no result in 15 msec.All comments or suggestions for improvement are welcome.
The usual solution to $subject is to either use something like " value = ANY(array) " or populate a temporary table and write your SQL to join against that temporary table. You incorporate ANY into your query but you are trying to populate it dynamically. The construction of the array should be from a serialized input:
DO $$
DECLARE
-- simulate input parameters
v_csv_text character varying (30) := ' 2222 , aaaa , 79 Q9 ';
v_csv_bigint character varying (30) := ' 2222 , 3333 , 4444 ';
sql text;
--
rslt text;
v_bind_values text[];
parm_num int;
BEGIN
v_bind_values := array_append(v_bind_values , v_csv_text::text);
parm_num := array_length(v_bind_values,1);
sql :=
$z$select 'true' where '79 Q9' $z$|| fbind_csv_text(parm_num) ;
v_bind_values := array_append(v_bind_values , v_csv_bigint::text);
parm_num := array_length(v_bind_values,1);
sql := SQL ||
$z$ and 3333 $z$|| fbind_csv_number(parm_num);
raise notice '
sql:%',sql;
execute sql into rslt using v_bind_values[1], v_bind_values[2];
raise notice 'result:%',rslt;
end$$;
CREATE OR REPLACE FUNCTION fbind_csv_text(p_parameter_position_i integer)
RETURNS text AS
$BODY$
BEGIN
return '= ANY(csv_text( $'||p_parameter_position_i::text||'))'::text;
END;$BODY$
LANGUAGE plpgsql VOLATILE
CREATE OR REPLACE FUNCTION fbind_csv_bigint(p_parameter_position_i integer)
RETURNS text AS
$BODY$
BEGIN
return '= ANY(csv_bigint( $'||p_parameter_position_i::text||'))'::text;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE OR REPLACE FUNCTION csv_text(dirty_string text)
RETURNS text[] AS
$BODY$
BEGIN
--does trim per application requirement and returns text[] for text based ANY function
return string_to_array( string_agg(clean_node,','),',' ) FROM (
SELECT trim(regexp_split_to_table(dirty_string, ',')) AS clean_node) alias;
END;$BODY$
LANGUAGE plpgsql VOLATILE
CREATE OR REPLACE FUNCTION csv_bigint(dirty_string text)
RETURNS bigint[] AS
$BODY$
BEGIN
-- does TRIM, though technically not needed for numeric values,
-- and returns bigint[] for number based ANY function
-- This function can also serve as an EXIT in case there
-- is any future need to massage the input csv string for example
-- convert "555,'$1,234.09',777" into "555,1234.09,777" for numeric comparison.
return string_to_array( string_agg(clean_node,','),',' ) FROM (
SELECT trim(regexp_split_to_table(dirty_string, ',')) AS clean_node) alias;
END;$BODY$
LANGUAGE plpgsql VOLATILE
I tried the $ quoting and was not able to get it to work because I am trying to also concatenate in the number portion of the bind variable ...bind_number = 1;sql := $qq$SELECT vendor_name FROM tx_vendor WHERE active_flag = 'Y'AND vendor_key = $$qq$||bind_number::text||$qq$AND code2tcode = 123$qq$
You point about "two better-named functions" is well taken. I will do as you suggest.Next point. I have concerns about using EXECUTE FORMAT. Specifically about the number of cursors that would be generated as a result. Would using a literal cause more cursors than using a bind variable?1) select vendor_name from tVendor where credit = 'CAT'; -- cursor 12) select vendor_name from tVendor where credit = 'DOG'; -- cursor 23) select vendor_name from tVendor where credit = $1; -- can be any value, reuses the same cursorPerhaps Postgres is smart enough to reuse the same cursor for 1 and 2 above? In the real world, my function will be called thousands of times per minute, so performance is critical.
NextIn reference to the part where you said:The usual solution to $subject is to either use something like " value = ANY(array) " or populate a temporary table and write your SQL to join against that temporary table. You incorporate ANY into your query but you are trying to populate it dynamically. The construction of the array should be from a serialized input:I'm confused about what you meant by "solution to $subject ".
As for the "temporary table" approach, I have a concern that joining additional tables might create a less than optimal execution plans especially given that there are no stats for these temporary tables. I have no idea if this concern is valid.
Per my best interpretation of your advice tempered by my understanding of the application, here is my new approach:
sql := SQL ||$z$ and 3333 $z$|| make_any_bigint_array(parm_num);--> ... and 3333 < $pre$= ANY( ($pre$|| string_to_array(cleaninput_embedded_integers_only(<input>), ',')::bigint[]::text || $post$)::bigint[])$post$ >