Invalid for loop in a function - postgresql

I am trying to make the following function working:
CREATE OR REPLACE FUNCTION validate_count(devices TEXT[], campaign_id INTEGER) RETURNS void AS $$
DECLARE
devices_array TEXT[] := devices;
devices_count INTEGER := array_length(devices, 1);
row_id INTEGER := campaign_id;
BEGIN
FOR device IN unnest(devices_array) LOOP
IF my_count('my_table', device, row_id) != 1;
RAISE EXCEPTION 'invalid_count %', row_id
ENDIF
END LOOP;
END
$$ LANGUAGE plpgsql;
my_count is a working function which returns INTEGER.
The definition fails with the error:
ERROR: syntax error at or near "unnest"
LINE 7: FOR device IN unnest(devices_array) LOOP
Could you spot the issue? Thanks!
I am planning to call the function as follows:
select validate_count('{foo, bar}', 1)

Use a FOREACH loop:
CREATE OR REPLACE FUNCTION validate_count(devices TEXT[], campaign_id INTEGER)
RETURNS void
AS
$$
DECLARE
device text;
devices_count INTEGER := array_length(devices, 1);
BEGIN
FOREACH device IN ARRAY devices LOOP
IF my_count('my_table', device, campaign_id) <> 1 then
RAISE EXCEPTION 'invalid_count %', campaign_id;
END IF;
END LOOP;
END
$$
LANGUAGE plpgsql;

A few things need to be fixed to make this valid.
The FOR loop needs SELECT before the unnest, i.e.:
FOR device IN SELECT unnest(devices_array) LOOP
You need to declare device up top, for example:
DECLARE device RECORD;
The IF statement needs a THEN instead of a ;, i.e.:
IF my_count('my_table', device, row_id) != 1 THEN
The RAISE EXCEPTION statement needs a semicolon at the end of the line, i.e.:
RAISE EXCEPTION 'invalid_count %', row_id;
The END for the IF statement should be END IF;.
The END for the LOOP statement should be END LOOP;.
Here's the net result:
CREATE OR REPLACE FUNCTION validate_count(devices TEXT[], campaign_id INTEGER) RETURNS void AS $$
DECLARE
devices_array TEXT[] := devices;
devices_count INTEGER := array_length(devices, 1);
row_id INTEGER := campaign_id;
device RECORD;
BEGIN
FOR device IN SELECT unnest(devices_array) LOOP
IF my_count('my_table', device, row_id) != 1 THEN
RAISE EXCEPTION 'invalid_count %', row_id;
END IF;
END LOOP;
END
$$ LANGUAGE plpgsql;

Related

postgreSQL : oracle sqlerrm equivalent of postgres

I'm new to PostgreSQL. I have experience in oracle. In oracle , to find the exact error, I use code 'dbms_output.put_line(sqlerrm)' . Here I have a postgresql function returning an integer value
CREATE OR REPLACE FUNCTION public.fn_sqltest(
p_id integer)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
Declare
n integer;
begin
select off_id into n from office
where per_id=p_id;
return n ;
exception when others then
return -1;
end;
$BODY$;
ALTER FUNCTION public.fn_sqltest(character varying)
OWNER TO postgres;
I call this function as below
DO $$
DECLARE
ae integer;
BEGIN
ae:=fn_sqltest(10);
RAISE NOTICE 'exception: % % ', sqlstate , sqlerrm ;
RAISE NOTICE 'Return value is: % ', ae;
END $$;
and I get the error
ERROR: column "sqlstate" does not exist
How can I show the exact error message like sqlerrm in oracle.
I updated the function and wrote a code in function exception section
CREATE OR REPLACE FUNCTION public.fn_sqltest(
p_id integer)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
Declare
n integer;
text_var1 text;
text_var2 text;
text_var3 text;
begin
select off_id into n from office
where per_id=p_id;
return n ;
exception when others then
GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
text_var2 = PG_EXCEPTION_DETAIL,
text_var3 = PG_EXCEPTION_HINT;
RAISE NOTICE 'Return value is: % % %',text_var1 , text_var2, text_var3;
return -1;
end;
$BODY$;
ALTER FUNCTION public.fn_sqltest(character varying)
OWNER TO postgres;
Another method is by changing the return type. I changed the return type to text and rewrite the exception section code as below
return sqlerrm;
Unlike PL/SQL, in PL/pgSQL SQLSTATE and SQLERRM are not defined outside an exception handler. See the documentation, section "Obtaining Information About An Error".
This also means that you can't get SQLSTATE and SQLERRM of a successful operation, unlike PL/SQL.
So, if you want to use these special variables outside an exception handler, you have to store them in variables.
I don't know how would you like to return them from the function. I can demonstrate this idea inside your function code:
CREATE OR REPLACE FUNCTION public.fn_sqltest(
p_id integer)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
Declare
n integer;
v_sqlerrm text;
v_sqlstate text;
begin
begin
select off_id into n from office
where per_id=p_id;
return n ;
exception when others then
v_sqlerrm := sqlerrm;
v_sqlstate := sqlstate;
end;
RAISE NOTICE 'exception: % % ', v_sqlstate , v_sqlerrm ;
return -1;
end;
$BODY$;
I know this is old but just for the record: The easiest way to output the state and message of error is to raise them from within the exception clause. You don't need to declare extra variables.
CREATE OR REPLACE FUNCTION fn_sqltest(p_id integer)
RETURNS integer
AS $$
DECLARE
n integer;
BEGIN
SELECT off_id
INTO n
FROM office
WHERE per_id = p_id;
RETURN n;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'exception: % - %', SQLSTATE, SQLERRM;
RETURN -1;
END;
$$ LANGUAGE plpgsql;

How to keep looping even error happend?

I wrote a PL/pgsql to batch create index on tables
CREATE OR REPLACE FUNCTION create_index() RETURNS void AS
$BODY$
DECLARE
r INTEGER;
BEGIN
FOR r IN 1..1000
LOOP
EXECUTE format(
' CREATE INDEX idx_abc_id_' || r::text ||
' ON abc_id_' || r::text ||
' USING btree
(key);');
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql;
it has one problem, if partition abc_500 doesn't exist, then the how create index function will fail and do nothing.
How to make loop keep going through even if create_index made an error on one of the table in between?
I think a better approach would be to not hardcode the number for the loop, but iterate over the existing tables:
CREATE OR REPLACE FUNCTION create_index() RETURNS void AS
$BODY$
DECLARE
r record;
BEGIN
FOR r IN select tablename, regexp_replace(tablename, '[^0-9]+','') as idx_nr
from pg_tables
where tablename ~ 'abc_id_[0-9]+'
LOOP
EXECUTE format('CREATE INDEX %I ON %I USING btree (key)',
'idx_abc_id_'||r.idx_nr,
r.tablename);
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql;
When you use the format() function is better to use the proper place holders for identifiers.
If you also want to ignore any error when creating the index on an existing table, you need to catch the exception and ignore it:
CREATE OR REPLACE FUNCTION create_index() RETURNS void AS
$BODY$
DECLARE
r record;
msg text;
BEGIN
FOR r IN select tablename, regexp_replace(tablename, '[^0-9]+','') as idx_nr
from pg_tables
where tablename ~ 'abc_id_[0-9]+'
LOOP
BEGIN
EXECUTE format('CREATE INDEX %I ON %I USING btree (key)',
'idx_abc_id_'||r.idx_nr,
r.tablename);
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS msg = MESSAGE_TEXT;
RAISE NOTICE 'Could not create index for: %, %', r.idx_nr, msg;
END;
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql;

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.

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;

the cursor for loop in postgresql

We have a function written in pl/sql(oracle) as below:
CREATE OR REPLACE PROCEDURE folder_cycle_check (folder_key IN NUMBER, new_parent_folder_key IN NUMBER) IS
parent_of_parent NUMBER;
ILLEGAL_CYCLE EXCEPTION;
CURSOR parent_c IS
SELECT parent_folder_key FROM folder
WHERE folder_key = new_parent_folder_key;
BEGIN
IF folder_key = new_parent_folder_key THEN
RAISE ILLEGAL_CYCLE;
END IF;
FOR parent_rec IN parent_c LOOP
BEGIN folder_cycle_check(folder_key, parent_rec.parent_folder_key); END;
END LOOP;
END;
Now, i have to rewrite this same procedure in pl/pgsql(PostgreSQL) to achieve similar functionality. Please help me and send that pl/pgsql function.
Edit (formatted code from the comments)
CREATE OR REPLACE FUNCTION folder_cycle_check(IN folder_key INTEGER, IN new_parent_folder_key INTEGER)
RETURNS VOID
AS $procedure$
DECLARE parent_of_parent INTEGER;
PARENT_C CURSOR FOR
SELECT parent_folder_key
FROM folder
WHERE folder_key = new_parent_folder_key;
BEGIN
IF folder_key = new_parent_folder_key THEN
RAISE EXCEPTION 'ILLEGAL_CYCLE';
END IF
FOR parent_rec IN (SELECT parent_folder_key FROM folder WHERE folder_key = new_parent_folder_key) LOOP
PERFORM folder_cycle_check(folder_key,parent_rec.parent_folder_key);
END LOOP;
RETURN;
END;
$procedure$
LANGUAGE plpgsql;
This should work:
CREATE OR REPLACE FUNCTION folder_cycle_check (p_folder_key INT4, p_new_parent_folder_key INT4) RETURNS VOID AS $$
DECLARE
v_parent_rec RECORD;
BEGIN
IF folder_key = new_parent_folder_key THEN
RAISE EXCEPTION 'ILLEGAL_CYCLE';
END IF;
FOR v_parent_rec IN SELECT parent_folder_key FROM folder WHERE folder_key = p_new_parent_folder_key LOOP
PERFORM folder_cycle_check(folder_key, v_parent_rec.parent_folder_key)
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;