How to pass table name to plpgsql function - postgresql

I am trying to run the code:
CREATE OR REPLACE FUNCTION anly_work_tbls.testfncjh (tablename text) returns int
AS $$
DECLARE
counter int;
rec record;
tname text;
BEGIN
counter = 0;
tname := tablename;
FOR rec IN
select *
from tname
loop
counter = counter + 1;
end loop;
RETURN counter;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
The goal of this code is to return the number of rows in the table you input. I know that this might not be the best way to accomplish this task, but the structure of this function would extend nicely to another question I am trying to tackle. Every time I run the code, I get the error:
ERROR: syntax error at or near "$1"
All online resources I have found tell me how to use the input variable within and EXECUTE block, but not in the above situation.
Currently running PostgreSQL 8.2.15.

CREATE OR REPLACE FUNCTION anly_work_tbls.testfncjh (tbl regclass, OUT row_ct int) AS
$func$
BEGIN
EXECUTE 'SELECT count(*) FROM '|| tbl
INTO row_ct;
END
$func$ LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER;
Call:
SELECT anly_work_tbls.testfncjh('anly_work_tbls.text_tbl');
This should work for Postgres 8.2, but you consider upgrading to a current version anyway.
I pass the table name as object identifier type regclass, which takes care of quoting automatically and works with schema-qualified names. Details:
Table name as a PostgreSQL function parameter
Using an OUT parameter simplifies the function.
Don't quote the language name. It's an identifier.
If you actually need to loop through the result of a dynamic query:
CREATE OR REPLACE FUNCTION anly_work_tbls.testfncjh (tbl regclass)
RETURNS int AS
$func$
DECLARE
counter int := 0; -- init at declaration time
rec record;
BEGIN
FOR rec IN EXECUTE
'SELECT * FROM ' || tbl
LOOP
counter := counter + 1; -- placeholder for some serious action
END LOOP;
RETURN counter;
END
$func$ LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER;
Read this chapter in the manual: Looping Through Query Results
The documented assignment operator in plpgsql is :=:
The forgotten assignment operator "=" and the commonplace ":="

Yes is really not the best way, but this would work:
CREATE OR REPLACE FUNCTION testfncjh (tablename text) returns int
AS $$
DECLARE
counter int;
rec record;
BEGIN
counter = 0;
FOR rec IN
EXECUTE 'select * from '||quote_ident(tablename) loop
counter = counter + 1;
end loop;
RETURN counter;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
This would be nicer:
CREATE OR REPLACE FUNCTION testfncjh (tablename text) returns int
AS $$
DECLARE _count INT;
BEGIN
EXECUTE 'SELECT count(*) FROM '|| quote_ident(tablename) INTO _count;
RETURN _count;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;

Related

How should I extract duplicated logic in a Postgres function?

I have a Postgres function with a lot of duplicated logic. If I were writing this in, say, Ruby, I would extract the duplicated logic into a few private helper methods. But there doesn't seem to be an equivalent of "private methods" in Postgres.
Original Function
CREATE OR REPLACE FUNCTION drop_create_idx_constraint(in_operation varchar, in_table_name_or_all_option varchar) RETURNS integer AS $$
DECLARE
cur_drop_for_specific_tab CURSOR (tab_name varchar) IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
cur_drop_for_all_tab CURSOR IS SELECT drop_stmt FROM table_indexes;
cur_create_for_specific_tab CURSOR (tab_name varchar) IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
cur_create_for_all_tab CURSOR IS SELECT recreate_stmt FROM table_indexes;
BEGIN
IF upper(in_operation) = 'DROP' THEN
IF upper(in_table_name_or_all_option) ='ALL' THEN
FOR table_record IN cur_drop_for_all_tab LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
ELSE
FOR table_record IN cur_drop_for_specific_tab(in_table_name_or_all_option) LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
END IF;
ELSIF upper(in_operation) = 'CREATE' THEN
IF upper(in_table_name_or_all_option) ='ALL' THEN
FOR table_record IN cur_create_for_all_tab LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
ELSE
FOR table_record IN cur_create_for_specific_tab(in_table_name_or_all_option) LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
END IF;
END IF;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Refactored Function(s)
CREATE OR REPLACE FUNCTION execute_recreate_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
BEGIN
FOR table_record IN input_cursor LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION execute_drop_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
BEGIN
FOR table_record IN input_cursor LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION drop_indexes_and_constraints(table_name_to_drop varchar) RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION drop_all_indexes_and_constraints() RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes;
SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION recreate_indexes_and_constraints(table_name_to_recreate varchar) RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_recreate;
SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION recreate_all_indexes_and_constraints() RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes;
SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
I believe the underlying problem with my refactor is that the helper functions, execute_recreate_stmt_from_records and execute_drop_stmt_from_records, are way too powerful to be publicly accessible, especially since Heroku (which hosts this DB) only allows one DB user. Of course, if there are other problems with the above refactor, feel free to point them out.
You can reach separation by moving "private" procedures into a new schema, limiting access to it. Then use a SECURITY DEFINER to allow calls to "private" functions.
Although, this will be hard to achieve if you are limited to a single user by your hosting service.
Example:
CREATE USER app_user;
CREATE USER private_user;
GRANT ALL ON DATABASE my_database TO app_user;
GRANT CONNECT, CREATE ON DATABASE my_database TO private_user;
-- With private_user:
CREATE SCHEMA private;
CREATE OR REPLACE FUNCTION private.test_func1()
RETURNS integer AS
$BODY$
BEGIN
RETURN 123;
END
$BODY$
LANGUAGE plpgsql STABLE
COST 100;
CREATE OR REPLACE FUNCTION public.my_function_1()
RETURNS integer AS
$BODY$
DECLARE
BEGIN
RETURN private.test_func1();
END
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;
-- With app_user:
SELECT private.test_func1(); -- ERROR: permission denied for schema private
SELECT my_function_1(); -- Returns 123

I am trying to unnet an array in other to query the postgres DB

I am call the function but it is returning error that array value must start with "{" or dimension information using
Create or Replace Function get_post_process_info(IN v_esdt_pp character varying[])
Returns setof Record as
$$
Declare
post_processes RECORD;
esdt_value character varying;
v_sdsname character varying[];
v_dimension character varying[];
counter int := 1;
Begin
-- to loop through the array and get the values for the esdt_values
FOR esdt_value IN select * from unnest(v_esdt_pp)
LOOP
-- esdt_values as a key for the multi-dimensional arrays and also as the where clause value
SELECT distinct on ("SdsName") "SdsName" into v_sdsname from "Collection_ESDT_SDS_Def" where "ESDT" = esdt_values;
raise notice'esdt_value: %',esdt_value;
END LOOP;
Return ;
End
$$ Language plpgsql;
Select get_post_process_info(array['ab','bc]);
Your function sanitized:
CREATE OR REPLACE FUNCTION get_post_process_info(v_esdt_pp text[])
RETURNS SETOF record AS
$func$
DECLARE
esdt_value text;
v_sdsname text[];
v_dimension text[];
counter int := 1;
BEGIN
FOR esdt_value IN
SELECT * FROM unnest(v_esdt_pp) t
LOOP
SELECT distinct "SdsName" INTO v_sdsname
FROM "Collection_ESDT_SDS_Def"
WHERE "ESDT" = esdt_value;
RAISE NOTICE 'esdt_value: %', esdt_value;
END LOOP;
END
$func$ Language plpgsql;
Call:
Select get_post_process_info('{ab,bc}'::text[]);
DISTINCT instead of DISTINCT ON, missing table alias, formatting, some cruft, ...
Finally the immediate cause of the error: a missing quote in the call.
The whole shebang can possibly be replaced with a single SQL statement.
But, obviously, your function is incomplete. Nothing is returned yet. Information is missing.

How postgresql return a data set like the follows?

Just like the below function. I don't know how to return a set of inside parameter in postgresql?
create or replace function g_i(num int)
returns setof integer
as $$
declare
i int;
begin
while i < $1 loop
select i; -- How to write statements here?
end loop;
end;
$$ language plpgsql;
create or replace function g_i(num int)
returns setof integer
as $$
declare
i int;
begin
i := 0;
while i< $1 loop
i := i+1;
return query select i;
end loop;
end;
$$ language plpgsql;

Looping in PostgreSQL

I am new to PostgreSQL and have a simple task with a looping structure.
Trying to print 0 to 10. Here is my function:
CREATE OR REPLACE FUNCTION LOOPING()
RETURNS TABLE(asd text) AS
$BODY$declare
i integer;
Begin
i:=0;
WHILE i > 10 LOOP
select i;
i:=(i+1);
END LOOP;
end;$BODY$
LANGUAGE plpgsql IMMUTABLE
COST 100
ROWS 1000;
ALTER FUNCTION LOOPING()
OWNER TO postgres;
I have tried with while loop. If anybody can do this task with for loop it will be very helpful.
You have several hick-ups in there. One working way (of many):
CREATE OR REPLACE FUNCTION f_loop()
RETURNS TABLE(asd int) AS
$BODY$
BEGIN
asd := 0;
WHILE asd < 11 LOOP
RETURN NEXT;
asd := asd + 1;
END LOOP;
END
$BODY$ LANGUAGE plpgsql IMMUTABLE
Call:
SELECT * FROM f_loop();
FOR loop
CREATE OR REPLACE FUNCTION f_loop()
RETURNS TABLE(asd int) AS
$BODY$
BEGIN
FOR i IN 0..10 LOOP
asd := i;
RETURN NEXT;
END LOOP;
END
$BODY$ LANGUAGE plpgsql IMMUTABLE;
Or, for this simple case:
CREATE OR REPLACE FUNCTION f_loop()
RETURNS SETOF int AS
$BODY$
BEGIN
FOR i IN 0..10 LOOP
RETURN NEXT i;
END LOOP;
END
$BODY$ LANGUAGE plpgsql IMMUTABLE;
Details in the excellent manual.
Your problem as far as I can see is that you have to return the value properly and RETURN NEXT in my experiments (on Pg 9.1) did not work as I expected it to.
I tested this and it was working:
create or replace function loop() returns table (i int)
language plpgsql as
$$
declare i_array int[];
begin
for i in 0 .. 10 loop
i_array := i_array || i;
end loop;
return query select unnest(i_array);
end;
$$;

Iterating over integer[] in plpgsql

How can I iterate over integer[] if I have:
operators_ids = string_to_array(operators_ids_g,',')::integer[];
I want iterate over operators_ids.
I can't do it in this way:
FOR oid IN operators_ids LOOP
and this:
FOR oid IN SELECT operators_ids LOOP
oid is integer;
You can iterate over an array like
DO
$body$
DECLARE your_array integer[] := '{1, 2, 3}'::integer[];
BEGIN
FOR i IN array_lower(your_array, 1) .. array_upper(your_array, 1)
LOOP
-- do something with your value
raise notice '%', your_array[i];
END LOOP;
END;
$body$
LANGUAGE plpgsql;
But the main question in my view is: why do you need to do this? There are chances you can solve your problem in better ways, for example:
DO
$body$
DECLARE i record;
BEGIN
FOR i IN (SELECT operators_id FROM your_table)
LOOP
-- do something with your value
raise notice '%', i.operators_id;
END LOOP;
END;
$body$
LANGUAGE plpgsql;
I think Dezso is right. You do not need to use looping the array using an index.
If you make a select statement grouping by person_id in combination with limit 1, you have the result set you wanted:
create or replace function statement_example(p_data text[]) returns int as $$
declare
rw event_log%rowtype;
begin
for rw in select * from "PRD".events_log where (event_type_id = 100 or event_type_id = 101) and person_id = any(operators_id::int[]) and plc_time < begin_date_g order by plc_time desc group by person_id limit 1 loop
raise notice 'interesting log: %', rw.field;
end loop;
return 1;
end;
$$ language plpgsql volatile;
That should perform much better.
If you still prefer looping an integer array and there are a lot of person_ids to look after, then might you consider using the flyweight design pattern:
create or replace function flyweight_example(p_data text[]) returns int as $$
declare
i_id int;
i_min int;
i_max int;
begin
i_min := array_lower(p_data,1);
i_max := array_upper(p_data,1);
for i_id in i_min .. i_max loop
raise notice 'interesting log: %',p_data[i_id];
end loop;
return 1;
end;
$$ language plpgsql volatile;