How to access FOUND while running a dynamic query in plpgsql - postgresql

I have stored proc in Redshift in plpgsql.
When I run call test3() I get exception
Amazon Invalid operation: Row Count: 1;
1 statement failed.
CREATE OR REPLACE PROCEDURE test3()
LANGUAGE plpgsql
AS $$
DECLARE
found_record RECORD;
integer_var integer;
BEGIN
SELECT * INTO found_record FROM tbl WHERE id='ABC';
IF FOUND THEN
GET DIAGNOSTICS integer_var = ROW_COUNT;
RAISE EXCEPTION 'Row Count: %', integer_var;
END IF;
END;
$$
But when I run call test4() I get
0 rows affected
CALL executed successfully
CREATE OR REPLACE PROCEDURE test4()
LANGUAGE plpgsql
AS $$
DECLARE
found_record RECORD;
integer_var integer;
BEGIN
EXECUTE 'SELECT * FROM tbl WHERE id=\'ABC\'' into found_record ;
IF FOUND THEN
GET DIAGNOSTICS integer_var = ROW_COUNT;
RAISE EXCEPTION 'Row Count: %', integer_var;
END IF;
END;
$$
How do I access FOUND while executing dynamic query

Dynamic SQL has not a impact on FOUND variable. But you can use a GET DIAGNOSTICS statement. You do it in your example:
CREATE OR REPLACE PROCEDURE test4()
LANGUAGE plpgsql
AS $$
DECLARE
found_record RECORD;
integer_var integer;
BEGIN
EXECUTE 'SELECT * FROM tbl WHERE id=\'ABC\'' into found_record ;
GET DIAGNOSTICS integer_var = ROW_COUNT;
RAISE EXCEPTION 'Row Count: %', integer_var;
END;
$$
You can use GET DIAGNOSTICS statement everywhere - not only when FOUND is true.

You can't, however you can check that found_record is not null.

Related

Assigning query output to variable in postgres stored proc

I am trying to assign a variable the result of a query in a postgres stored procedure.
Here is what I am trying to run:
CREATE OR Replace PROCEDURE schema.MyProcedure()
AS $$
DECLARE
RowCount int = 100;
BEGIN
select cnt into RowCount
from (
Select count(*) as cnt
From schema.MyTable
) ;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;
schema.MyTable is just some arbitrary table name but the script is not displaying anything, not even the random value I assigned RowCount to (100).
What am I doing wrong?
Thanks
You need an alias for the subquery, for example : as sub
CREATE OR Replace PROCEDURE schema.MyProcedure()
AS $$
DECLARE
RowCount int = 100;
BEGIN
select cnt into RowCount
from (
Select count(*) as cnt
From schema.MyTable
) as sub ;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;
You can also assign any variable with a query result in parenthesis.
CREATE OR REPLACE PROCEDURE schema.my_procedure()
AS
$$
DECLARE
row_count BIGINT;
BEGIN
row_count = (SELECT COUNT(*) FROM schema.my_table);
RAISE NOTICE 'RowCount: %', row_count;
END;
$$ LANGUAGE plpgsql;
You should use BIGINT instead of INT.
And it's far better to write your code and table definition with snake_case style as possible.

function not returning any value and not raising exception

The below function neither returning the value nor raising the exception.
create or replace function get_custid(p_customerNum varchar2)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', message_text;
select customer_num into cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', message_text;
raise;
end $$ language plpgsql;
select get_custid('Ab12345') from dual;
-- the customer number is existed but not returning any rows.
select get_custid('DDDDDDD') from dual;
-- the customer number is not existed but not going to exception block
I think that is you really use postgresql this code is more likely what you need (or at list it's running...)
create table customer (cust_id int, customer_num varchar);
insert into customer values (1, 'Ab12345');
drop function get_custid(varchar);
create or replace function get_custid(p_customerNum varchar)
RETURNS int AS $$
DECLARE
out_cust_id int;
begin
--raise notice '%', message_text;
select cust_id into out_cust_id
from customer
where customer_num = p_customerNum;
if out_cust_id is null
then raise exception 'your exception';
end if;
return out_cust_id;
end $$ language plpgsql;
select get_custid('Ab12345');
In PL/pgSQL, SELECT INTO only throws an exception on the wrong number of rows if STRICT is specified.
create or replace function get_custid(p_customerNum varchar)
RETURNS text AS $$
DECLARE
cust_id customer.customer_num%TYPE;
begin
raise notice '%', 'message_text';
select customer_num into strict cust_id
from customer
where customer_num = p_customerNum;
return cust_id;
exception
when OTHERS then
raise notice '%', 'message_text';
raise;
end $$ language plpgsql;

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;

For loop with dynamic table name in Postgresql 9.1?

I have a plpgslq function which does some data processing and would like to write a for loop, however my table name is not known at design time. Is there any possible way to achieve this? Here is sample code snippet of what I want to achieve:
-- Function: check_data()
-- DROP FUNCTION check_data();
CREATE OR REPLACE FUNCTION check_data()
RETURNS character varying AS
$BODY$declare
dyn_rec record;
tbl_name record;
begin
-- sample dynamic tables
tbl_name := 'cars';
tbl_name := 'trucks';
tbl_name := 'bicycles';
for dyn_rec in select * from format($$s%$$,tbl_name) loop
raise notice 'item is %',dyn_rec.item_no;
end loop;
return 'Processing Ok';
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION check_data()
OWNER TO postgres;
You cannot use a variable as table or column identifier in plpgsql embedded SQL ever. A solution is dynamic SQL - EXECUTE or FOR IN EXECUTE statements:
DO $$
DECLARE
tables text[] = ARRAY['table1','table2'];
table_name text;
rec record;
BEGIN
FOREACH table_name IN ARRAY tables
LOOP
FOR r IN EXECUTE format('SELECT * FROM %I', table_name)
LOOP
RAISE NOTICE '%', rec;
END LOOP;
END LOOP;
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;