Looping in PostgreSQL - 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;
$$;

Related

How to pass table name to plpgsql function

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;

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

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;

how do I return a bit in postgres

I am new to postgres and trying to setup a function that returns a bit.
I keep getting the error
Function's final statement must be SELECT or INSERT/UPDATE/DELETE
RETURNING.
I understand that
Unless the function is declared to return void, the last statement must be a SELECT, or an INSERT, UPDATE, or DELETE that has a RETURNING clause.
here is the code
CREATE OR REPLACE FUNCTION "f"(...)
RETURNS bit AS
DO $$
Begin
IF someStuff
THEN
0; //also tried select 0 //also tried return 0
ELSE
1; //also tried select 1 //also tried return 0
END IF;
0; //also tried select 0 //also tried return 0
END $$
Where am I going wrong with the syntax?
There are several errors:
the DO is wrong in a function definition
you are missing the specification of the language
in PL/pgSQL you use return to return the function's result
So your function becomes:
CREATE OR REPLACE FUNCTION f(some_value integer)
RETURNS bit AS
$$
Begin
IF (some_value = 1)
THEN
return 0;
ELSE
return 1;
END IF;
END $$
language plpgsql
But you should use boolean instead of bit to return true/false flags:
CREATE OR REPLACE FUNCTION f(some_value integer)
RETURNS boolean AS
$$
Begin
IF (some_value = 1)
THEN
return false;
ELSE
return true;
END IF;
END $$
language plpgsql
If you want to use plpgsql then do as in the a_horse's answer but if you don't need plpgsql do in sql:
create or replace function f(some_value integer)
returns boolean as $$
select some_value = 1;
$$
language sql;
If the function is the one from this question then this will do it:
create or replace function isPersonQualifiedForJob(pid integer, jid)
returns boolean as $$
select exists (
select 1
from
getskillsforjob(jid) j
inner join
getskillsforperson(pid) p on j.skillid = p.skillid
)
$$
language sql;
Checking for exists is much faster then counting since it is enough to find the first match.

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;