How to create sequence based on other column value - sql

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();

Related

Create a trigger function in Postgres that doesn't let you have two entries with the same id

I have two tables in postgres,
I want to create a function that doesn’t have more than 2 loans in the lending table with the same person id.
example: in the loan table I cannot have 3 loans that are from the same person, that is, we loan with the same person's id.
I need to do this using a function, I put what I was trying to do but it didn't work
CREATE TABLE person (
name_person varchar (100) ,
id_person varchar(14) primary key
)
CREATE TABLE lending(
id_lending primary key (100) ,
id_publication (14) FK,
id_person fk REFERENCES id_person (person)
CREATE OR REPLACE FUNCTION check_numlending()
RETURNS trigger AS
$BODY$
BEGIN
IF( select * from lending
inner join person
on person.id_person = lending.id_person > 2 ) THEN
RAISE EXCEPTION 'ERROR';
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- Trigger
CREATE TRIGGER
trg_check_num_lending
BEFORE INSERT OR UPDATE ON
lendingFOR EACH ROW EXECUTE PROCEDURE check_numlending();
Write your trigger Function like below:
-- Function
CREATE OR REPLACE FUNCTION check_numlending()
RETURNS trigger AS
$BODY$
declare counter int;
BEGIN
select count(*) into counter from lending where id_person =new.id_person;
IF( counter>=2 ) THEN
RAISE EXCEPTION 'ERROR';
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- Trigger
CREATE TRIGGER
trg_check_num_lending
BEFORE INSERT OR UPDATE ON
lending FOR EACH ROW EXECUTE PROCEDURE check_numlending();

Creating specific value sequence in oracle

I need to create an oracle sequence with specific values
FOUR0001, FOUR0002, FOUR0003.....
the increment must be in order.
First create a simple sequence
create sequence my_seq ; --start with 1 increment by 1
In your application code / table where you use the sequence to store the data, use something like this
INSERT INTO yourtab (col1) VALUES( 'FOUR'||lpad(my_seq.nextval,4,'0'));
You could create a sequence:
create sequence SEQ_NAME ...;
and then to create a trigger to feed the field automatically:
CREATE OR REPLACE TRIGGER INS_TABLENAME
before insert on TABLENAME
for each row
BEGIN
if :new.FIELD_NAME is null then
:new.FIELD_NAME := 'FOUR'||lpad(SEQ_NAME.nextval,4,'0');
end if;
END;
I've created a sequence starting with 210 ( because i already ahve 209 records) then created the trigger bellow
CREATE OR REPLACE trigger BIU_FRS
before insert or update on FOURNISSEUR
for each row
begin
if :NEW.FRS_NUM is null then
select ('FOUR'||lpad(four_seq.nextval,4,'0')) into :NEW.FRS_NUM from dual;
end if;
if inserting then
:new.created := localtimestamp;
:new.created_by := nvl(wwv_flow.g_user,user);
end if;
:new.updated := localtimestamp;
:new.updated_by := nvl(wwv_flow.g_user,user);
end;
thank you #Kaushik Nayak and #Diego Souza

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:

Column values as factorial in Oracle SQL

I have created a trigger, that will autoincrement the id, according to the sequence, every time a new record is inserted. Like this:
create sequence test_seq
start with 1
increment by 1
nomaxvalue;
--drop trigger test_trigger;
create or replace trigger test_trigger
before insert on myTable
for each row
begin
select test_seq.nextval into :new.tab_id from dual;
end;
However, I'd like to insert a factorial of the row index instead. How could I achieve this?
Edit:
create or replace trigger test_trigger
after insert on myT
for each row
begin
select fac(test_seq.nextval) into :new.tab_id from dual;
end;
Added fac function which works fine:
create or replace function fac(n in number)
return number
is
v number :=1;
begin
for i in 1..n
loop
v :=v * i;
end loop;
return v;
end;
But I still only see 1,2,3,4 in the table instead of 1,2,6,24...
From Oracle's documentation. You want to use a BEFORE trigger in this instance, an AFTER trigger won't actually change the table's data just from setting it in NEW:
Because the trigger uses the BEFORE keyword, it can access the new
values before they go into the table, and can change the values if
there is an easily-corrected error by assigning to :NEW.column_name.
My guess is that you are still seeing the same old values from the sequence because your BEFORE trigger still exists; the AFTER trigger won't change those values.
So what you want is the following:
CREATE OR REPLACE TRIGGER test_trigger
BEFORE INSERT ON myt
FOR EACH ROW
BEGIN
SELECT FAC(test_seq.nextval) INTO :new.tab_id FROM dual;
END;
/
I think as of Oracle 11g (or maybe it's 10g; can't remember) you can also do the following:
CREATE OR REPLACE TRIGGER test_trigger
BEFORE INSERT ON myt
FOR EACH ROW
BEGIN
:new.tab_id := FAC(test_seq.nextval);
END;
/
Do something like
create function factorial (n integer) return integer as
...
create or replace trigger test_trigger
after insert on mytable
-- don't do this for each row
begin
update mytable set
tab_id = factorial((select count(*) from mytable))
where tab_id is null;
end;
/

passing cursor values into another function

*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;