passing cursor values into another function - sql

*i have a function which will delete the rows in the table for the given input id, the input is given to the function by another cursor_function.*
select * from t1;
id | col1
----+-------
1 | user1
2 | user2
3 | user3
4 | user4
5 | user5
(5 rows)
create or replace function del_t1(int) returns void as $$
declare
a alias for $1;
begin
delete from t1 where id = a;
return;
end;
$$language plpgsql;
create or replace function del_cur(ref refcursor) returns void as $$
declare
a int;
begin
open ref scroll for select id from t1 where id > 3 order by id desc;
fetch first from ref into a;
perform del_t1(a);
loop
a := 0;
fetch next from ref into a;
perform del_t1(a);
if (a=0) then
exit;
end if;
end loop;
end;
$$ language plpgsql;
when the function is executed,
postgres# select del_cur('t1');
it shows no error, no outputs, cursor just blinks, plz help me to solve this, this is small description of my big database tables, i need a cursor function which will pass its values one by one to another function.

In your example the function del_cur(refcursor) is declared to take a cursor. But you never actually use it inside the function - the construct makes no sense at all.
What you are trying to do should be much easier with the implicit cursor of a FOR loop:
CREATE OR REPLACE FUNCTION del_stuff()
RETURNS void AS
$BODY$
DECLARE
_a int;
BEGIN
FOR _a IN
SELECT id FROM t1 WHERE id > 3 ORDER BY id DESC
LOOP
DELETE FROM t1 WHERE id = _a;
-- do other stuff?
END LOOP;
END;
$BODY$ LANGUAGE plpgsql;
I don't understand the need to delete the rows one by one in descending order. Are triggers involved? Maybe the whole thing can be as simple as:
DELETE FROM t1 WHERE id > 3;

Related

How to create sequence based on other column value

I have below data table where I wants to create a unique id for data_id which will be unique per group_id (group_id and data_id should be unique) and for each group data_id should start from 1. How to create a DB sequence to achieve this ? Or any other better approach to achieve this ?
group_id data_id
UUID-1 1
UUID-1 2
UUID-1 3
UUID-1 4
UUID-2 1
UUID-2 2
DB Sequence will not help in this case. You can use trigger with some caveats:
Try this:
Create trigger function like below:
create or replace function trig_fun()
returns trigger AS
$$
begin
select coalesce(max(data_id),0)+1 into new.data_id from my_table where group_id=new.group_id;
return new;
end;
$$
language plpgsql
and attach above function on before insert event
create trigger trig_on_insert
before insert on
my_table
for each row
execute procedure trig_fun()
Limitations:
If you delete the row of any group having max value for that group, it will reassign same number on next insert of same group.
If you update any ID which is greater than others in that group then next value will be incremented from it.
create function tgf_mytable_bi()
returns trigger
language plpgsql
as $f$
declare
seq_name text;
begin
seq_name := 'seq_'||new.group_id;
-- Check is sequence already exists
if (select count(*) = 0 from pg_class where relkind = 'S' and relname = seq_name) then
-- New group detected
-- Create new sequence for it
execute format('create sequence %I', seq_name);
-- Sequence exists, get next value from it
end if;
-- Generate next value for group
new.data_id := nextval(seq_name);
end $f$;
create trigger trg_mytable_bi
before insert on mytable
for each row
execute procedure tgf_mytable_bi();

Create stored procedure (int[] as param) which deletes existing records in table when there is no match in int array

ex: if i have sent 1,2,3 params to stored procedure with idxyz, then table has 1,2,3,4,5 ids then 4,5 should be deleted from table.
CREATE OR REPLACE FUNCTION example_array_input(INT[]) RETURNS SETOF ids AS
$BODY$
DECLARE
in_clause ALIAS FOR $1;
clause TEXT;
rec RECORD;
BEGIN
FOR rec IN SELECT id FROM ids WHERE id = ANY(in_clause)
LOOP
RETURN NEXT rec;
END LOOP;
-- final return
RETURN;
END
$BODY$ language plpgsql;
ex: SELECT * FROM example_array_input('{1,2,4,5,6}'::INT[]);
if existing table has 1,2,3,4,5,6,7,8,9. then it should delete 7,8,9 from that table since these are not there in the input array
You can use a DELETE statement like this for your purpose.
DELETE FROM ids
where id NOT IN ( select UNNEST('{1,2,4,5,6}'::INT[]) ) ;
DEMO
You can use a sql function that returns the deleted ids:
CREATE OR REPLACE FUNCTION example_array_input(in_clause INT[]) RETURNS SETOF ids
language sql
AS
$SQL$
DELETE
FROM ids
WHERE id NOT IN ( SELECT unnest(in_clause) )
RETURNING id;
$SQL$;
You can see a running example in http://rextester.com/PFG55537
In a 1 to 10 table running
SELECT * FROM example_array_input('{1,2,4,5,6}'::INT[]);
you obtain:

Postgres trigger creation - ERROR: no language specified SQL state: 42P13

I am new to triggers. I am trying to create a trigger by following this link - http://www.postgresqltutorial.com/creating-first-trigger-postgresql/. But it gives some error. The code block and the error is given below.
Code block:
CREATE OR REPLACE FUNCTION log_last_name_changes()
RETURNS trigger AS
$BODY$
BEGIN
IF NEW.last_name <> OLD.last_name THEN
INSERT INTO employee_audits(employee_id,last_name,changed_on)
VALUES(OLD.id,OLD.last_name,now());
END IF;
RETURN NEW;
END;
$BODY$
And the error:
ERROR: no language specified
SQL state: 42P13
What can I try next?
Try this way:
CREATE OR REPLACE FUNCTION log_last_name_changes()
RETURNS trigger AS
$BODY$
BEGIN
IF NEW.last_name <> OLD.last_name THEN
INSERT INTO employee_audits(employee_id,last_name,changed_on)
VALUES(OLD.id,OLD.last_name,now());
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE -- Says the function is implemented in the plpgsql language; VOLATILE says the function has side effects.
COST 100; -- Estimated execution cost of the function.
If arriving here because your function gave you the same error (like mine did).. here's an over-baked example of using a function to update the tweet on the bottom right "blurb" with "hello world".
Important note: language goes BEFORE the last semicolon (see below). This is easy to miss in the accepted solution.
id (serial)
pub_id (text)
tweet (text)
1
abc
hello world
2
def
blurb
-- Optional drop if replace fails below.
drop function if exists sync_tweets(text, text);
create or replace function sync_tweets(
src_pub_id text, -- function arguments
dst_pub_id text
) returns setof tweets as -- i.e. rows. int, text work too
$$
declare
src_id int; -- temp function variables (not args)
dest_id int;
src_tweet text;
begin
-- query result into a temp variable
src_id := (select id from tweets where pub_id = src_pub_id);
-- query result into a temp variable (another way)
select tweet into src_tweet from tweets where id = src_id;
dest_id := (select id from tweets where pub_id = dst_pub_id);
update tweets set tweet=src_tweet where id = dest_id;
return query -- i.e. rows, return 0 with return int above works too
select * from tweets where pub_id in (src_pub_id, dst_pub_id);
end
$$ language plpgsql; -- need the language to avoid ERROR 42P13
-- Run it!
select * from sync_tweets('abc', 'def');
/*
Outputs
__________________________________________________
| id (serial) | pub_id (text) | tweet (text) |
|---------------|-----------------|----------------|
| 1 | abc | hello world |
| 2 | def | blurb |
--------------------------------------------------
*/

Declare a variable of a table type in postgres

I need to write a stored procudere like the following:
CREATE OR REPLACE FUNCTION foo() RETURNS TABLE(user_id integer, count bigint) AS $$
some_array integer[];
ret_val __WHAT_TYPE_;
BEGIN
FOR i IN 1 .. array_upper(some_array, 1)
LOOP
//modify the ret_val
END LOOP;
RETURN ret_val;
END $$
LANGUAGE plpgsql;
But I don't know what type of ret_val I should declare?
In a function returning a table you do not need a variable for returned value.
The columns of the table are treated as OUT parameters.
You can assign values to them and use RETURN NEXT, e.g.:
CREATE OR REPLACE FUNCTION foo()
RETURNS TABLE(user_id integer, counts bigint) AS $$
DECLARE
i integer;
BEGIN
FOR i IN 1..4
LOOP
user_id = i;
counts = i* i;
RETURN NEXT;
END LOOP;
END $$
LANGUAGE plpgsql;
SELECT * FROM foo();
user_id | counts
---------+--------
1 | 1
2 | 4
3 | 9
4 | 16
(4 rows)

query has no destination for result data - PostgreSQL

I have created the following stored procedure in PostgreSQL:
CREATE OR REPLACE function incomingdel(IN del_ID varchar) RETURNS VOID AS $$
DECLARE xyz integer;
BEGIN
SELECT * INTO xyz FROM incomingcheck(cast(del_ID as integer));
IF xyz <> 1
THEN
SELECT * INTO xyz FROM perfdel(cast(del_ID as integer));
END IF;
END
$$ LANGUAGE plpgsql
even though the function returns VOID, I am still getting
Error: query has no destination for result data
when i execute the function. Can someone please help?
Thanks
When you do SELECT * INTO ... in a PL/pgSQL function you need to supply a variable of type record or of the row type of the row source, even if the function only returns an integer. The functions incomingcheck(int) and perfdel(int) are obviously returning a single integer in which case you can simplify your function:
CREATE FUNCTION incomingdel(del_ID integer) RETURNS VOID AS $$
DECLARE
xyz integer;
BEGIN
xyz := incomingcheck(del_ID);
IF xyz <> 1 THEN
xyz := perfdel(del_ID);
END IF;
-- Do something with xyz
END; $$ LANGUAGE plpgsql;
If the functionality is actually in the called functions (so you do not process the xyz value), then it becomes simpler still:
CREATE FUNCTION incomingdel(del_ID integer) RETURNS VOID AS $$
BEGIN
IF incomingcheck(del_ID) <> 1 THEN
perfdel(del_ID);
END IF;
END; $$ LANGUAGE plpgsql;