Assigning query output to variable in postgres stored proc - postgresql

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.

Related

Save dynamic query to variable in postgres stored procedure

I have the following postgres stored procedure:
CREATE OR REPLACE PROCEDURE
schema.MyProcedure()
AS $$
DECLARE
RowCount int;
BEGIN
SELECT cnt INTO RowCount
FROM (
SELECT COUNT(*) AS cnt
FROM MySchema.MyTable
) AS sub;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;
which "prints" out the row count of the static table MySchema.MyTable. How can it make it so I pass the Table and Schema name as an input.
eg:
CREATE OR REPLACE PROCEDURE
schema.MyProcedure(MySchema_In varchar, MyTable_In varchar)
AS $$
DECLARE
RowCount int;
BEGIN
SELECT cnt INTO RowCount
FROM (
SELECT COUNT(*) AS cnt
FROM || **MySchema_In** || . || **MyTable_In** ||
) AS sub;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;
You should use format() instead of concatenating the strings with || and then EXECUTE ... INTO to get the query's result, e.g.
CREATE OR REPLACE PROCEDURE MyProcedure(MySchema_In varchar, MyTable_In varchar)
AS $$
DECLARE RowCount int;
BEGIN
EXECUTE FORMAT('SELECT count(*) FROM %I.%I',$1,$2) INTO RowCount;
RAISE NOTICE 'RowCount: %', RowCount;
END;
$$
LANGUAGE plpgsql;

Procedure to check count, store result and delete records

I want to create stored procedure to check count of query result. Then if count is > 0 to execute some query to delete records in other table. Below see what i got so far.
CREATE OR REPLACE PROCEDURE myprocedure(tableName VARCHAR, age INT, secondTable VARCHAR)
AS
$$
declare cnt := SELECT COUNT(*) FROM %tableName% WHERE ageID =%id%;
declare result;
BEGIN
EXECUTE cnt;
IF cnt >= 1 THEN
result := SELECT ID FROM %tableName% WHERE ageID =%id%
--remove records from secondTable
EXECUTE DELETE FROM %secondTable% WHERE ID IN (result)
END IF;
COMMIT;
END;
As documented in the manual you can't "reference" a variable with %tableName% and you certainly can not use a variable within a SQL statement for an identifier. You will need to use dynamic SQL.
You also got the DECLARE part completely wrong. You only write the keyword once, and you have to define a data type for the variables.
To create SQL strings that contain identifier, use format() and the %I placeholder to properly deal with identifiers that need quoting.
CREATE OR REPLACE PROCEDURE myprocedure(p_tablename VARCHAR, p_age INT, p_secondtable VARCHAR)
AS
$$
declare
l_sql text;
cnt integer;
BEGIN
l_sql := format('select count(*) from %I where ageid = :1', p_tablename);
EXECUTE l_sql
using p_age
into cnt;
IF cnt >= 1 THEN
l_sql := format('DELETE FROM %I WHERE ID IN (SELECT id FROM %I where ageid = :1)', p_secondtable, p_tablename);
EXECUTE l_sql using p_age;
END IF;
$$
language plpgsql;
But checking for the count before doing the delete is pretty pointless, you can simply that to a single DELETE statement:
CREATE OR REPLACE PROCEDURE myprocedure(p_tablename VARCHAR, p_age INT, p_secondtable VARCHAR)
AS
$$
declare
l_sql text;
cnt integer;
BEGIN
l_sql := format('DELETE FROM %I WHERE id IN (SELECT t.id FROM %I as t where t.ageid = :1)', p_secondtable, p_tablename);
EXECUTE l_sql using p_age;
END IF;
$$
language plpgsql;
Because the DELETE statement won't delete anything if the sub-select doesn't return any rows (which would be the case for cnt = 0). And you only need to query the first table once.

Postgresql: UPDATE before INSERT function

I have problem when create function for trigger. I want to UPDATE inserted value BEFORE INSERT data to DB.
My code look like this:
CREATE OR REPLACE FUNCTION test_func()
RETURNS TRIGGER AS
$$
DECLARE cnt INTEGER;
BEGIN
cnt := COUNT(*) FROM sample_tbl WHERE id = NEW.id AND created_date = NEW.created_date;
NEW.current_order := cnt + 1; // I want to set value of sample_tbl.current_order automatically
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_trigger
BEFORE INSERT
ON test_tbl
FOR EACH ROW
EXECUTE PROCEDURE test_func();
I inserted data then IDE said:
control reached end of trigger procedure without RETURN
Where: PL/pgSQL function test_func()
The error says that you must return something from the Trigger ( either NEW or NULL )
There's no Trigger needed for this. A simple View using this select query will give you the required result
--create or replace view sample_view as
select t.id, t.created_date,
row_number() OVER ( partition by id,created_date order by id ) as current_order
FROM sample_tbl t;
This will exactly match the records if updated using a Trigger
CREATE OR REPLACE FUNCTION test_func()
RETURNS TRIGGER AS
$$
DECLARE cnt INTEGER;
BEGIN
select COUNT(*) INTO cnt FROM sample_tbl WHERE id = NEW.id
AND created_date = NEW.created_date;
NEW.current_order := cnt + 1;
RETURN NEW; --required
END
$$ LANGUAGE plpgsql;
Demo
Your trigger function is just missing RETURN NEW; statement:
CREATE OR REPLACE FUNCTION test_func()
RETURNS TRIGGER AS
$$
DECLARE cnt INTEGER;
BEGIN
cnt := COUNT(*) FROM sample_tbl WHERE id = NEW.id AND created_date = NEW.created_date;
NEW.current_order := cnt + 1;
RETURN NEW;
END
$$ LANGUAGE plpgsql;

How to select into multiple variables inside a trigger function?

This is what I'd like to achieve:
CREATE FUNCTION f() RETURNS trigger AS $$
BEGIN
SELECT COUNT(*) AS total_num, SUM(width) AS total_width
FROM some_table WHERE foo = NEW.foo;
IF total_num > 0 AND total_width > 100
THEN
RAISE EXCEPTION 'this is bad';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
But it's not yet syntactically correct.
I've read I first need to DECLARE the variables (in this case total_num and total_width) so I can use those and use SELECT INTO but I've seen examples with a single variable / SELECT statement only. What if I have more of them?
You can list multiple variables in the into part. And the declare section needs to come before the first begin:
CREATE FUNCTION f() RETURNS trigger
AS $$
declare
total_num bigint;
total_width bigint;
BEGIN
SELECT COUNT(*), SUM(width)
into total_num, total_width
FROM some_table
WHERE foo = NEW.foo;
IF total_num > 0 AND total_width > 100 THEN
RAISE EXCEPTION 'this is bad';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Edit: I'm not sure whether the emphasis here is on the use of variables or the actual IF. This is meant as an answer on the latter:
You can do this without variables using HAVING and EXISTS.
IF EXISTS (SELECT ''
FROM some_table
WHERE foo = new.foo
HAVING count(*) > 0
AND sum(width) > 100) THEN
RAISE EXCEPTION 'this is bad';
END IF;

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;