How to make increment if same name occurs using triggers? - sql

I have a table:
id
name
amount
If the same name was inserted, then amount should be incremented.
Else, insert with 0 amount.
How to create a trigger with a given condition?

You shouldn't really be doing that. AMOUNT column effectively counts number of NAME appearances in that table. You can always count it, can't you? So, what's the purpose of doing that?
If you want to know which name was inserted prior (or after) some other name (equal to the previous one), sort them by ID (if it is incremental, such as an identity column or if it gets its value from a sequence).
If ID isn't incremental, add DATE_INSERTED column (and sort by it; or apply ROW_NUMBER analytic function which orders values by DATE_INSERTED).
Also, what happens if you delete one of those duplicate names? Will you retroactively decrement AMOUNT column for all previous instances of that NAME?
But, if you insist, here's one option. As you can't just select from a table you're inserting into (because of the mutating table error), I'm using a compound trigger. The part of "insert amount 0" is done by setting the column's default value (you don't need any code for that).
SQL> create table test
2 (id number primary key,
3 name varchar2(20),
4 amount number default 0);
Table created.
SQL> create or replace trigger trg_ai_test
2 for insert on test
3 compound trigger
4
5 type test_rt is record (id test.id%type,
6 name test.name%type);
7 type rli_t is table of test_rt index by pls_integer;
8 g_rli rli_t;
9
10 after each row is
11 begin
12 g_rli(g_rli.count + 1).id := :new.id;
13 g_rli(g_rli.count).name := :new.name;
14 end after each row;
15
16 after statement is
17 l_cnt number;
18 begin
19 for i in 1 .. g_rli.count loop
20 dbms_output.put_Line('x');
21 dbms_output.put_line(i ||' '|| g_rli(i).id ||' '||g_rli(i).name);
22 select count(*) into l_cnt
23 from test
24 where name = g_rli(i).name;
25
26 update test set
27 amount = l_cnt
28 where id = g_rli(i).id;
29 end loop;
30
31 end after statement;
32 end trg_ai_test;
33 /
Trigger created.
SQL>
Testing:
SQL> insert into test (id, name) values (1, 'little');
1 row created.
SQL> insert into test (id, name) values (2, 'foot');
1 row created.
SQL> insert into test (id, name) values (3, 'little');
1 row created.
SQL> insert into test (id, name) values (9, 'little');
1 row created.
SQL> select * from test;
ID NAME AMOUNT
---------- -------------------- ----------
1 little 1
2 foot 1
3 little 2
9 little 3
SQL>
Or, a simpler solution (described above), without using that much code:
SQL> select id, name,
2 row_number() over (partition by name order by id) as amount
3 from test;
ID NAME AMOUNT
---------- -------------------- ----------
2 foot 1
1 little 1
3 little 2
9 little 3
SQL>

Related

Creating multiple updates on database with procedure. Problem with incrementation

I'm trying to create a procedure in oracle sql in order to make multiple updates for one table. I have around 500 updates for different values but I'm only changing one column which is unique and it's task is to number rows. So i want to change number 20 to 21 and so on until number 120. So in the end number 20 will be empty and I will have 121 rows.
I created this loop but I keep getting error "unique constraint violated" so i figured that my iteration is not working.
declare
n integer := 20;
BEGIN
FOR x in 20..120 LOOP
Update my_table
SET number_of_field = n WHERE number_of_field > 20;
n:= n + 1;
END LOOP;
END;
I would really appreciate help because it's my first time using procedures in SQL and I will feel like I'm close to the solution.
Thank you!
Oracle is one of the few databases (PostgreSQL being the other one afaik) that allows you to defer constraint checks until the end of the transaction.
If you place the whole block of code into a single transaction, and also defer the check of the UNIQUE constraint to DEFERRABLE INITIALLY DEFERRED, then you can perform all updates in it, even if you temporarily store duplicate values in the column. However, once you perform the COMMIT, the constraint (for all rows/updates) will be validated, and if it fails the transaction will be rolled back. So you need to be on a good stance once you issue the commit.
For example you can change your table to something like:
create table my_table (
... other_columns here
number_of_field,
constraint uq1 unique(number_of_field) deferrable initially deferred
);
This is how I understood the question:
SQL> -- Create table
SQL> create table my_table (number_of_field number, somecol varchar2(10));
Table created.
SQL> -- Insert sample data; my end with 30, not 120 (that's too much for an example)
SQL> insert into my_table
2 select 20 + level - 1, 'X'
3 from dual
4 connect by level <= 11;
11 rows created.
SQL> -- Table contents
SQL> select number_of_field, somecol from my_table order by number_of_field nulls first;
NUMBER_OF_FIELD SOMECOL
--------------- ----------
20 X
21 X
22 X
23 X
24 X
25 X
26 X
27 X
28 X
29 X
30 X
11 rows selected.
Two-step job: update existing rows (by adding 1) and insert an empty one (somecol is here just to show that there's a row with empty number_of_Field column).
SQL> -- Update existing rows; insert an "empty" one
SQL> begin
2 update my_table set
3 number_of_field = number_of_field + 1;
4 insert into my_table (number_of_field, somecol)
5 values (null, 'X');
6 end;
7 /
PL/SQL procedure successfully completed.
SQL> -- The result
SQL> select number_of_field, somecol from my_table order by number_of_field nulls first;
NUMBER_OF_FIELD SOMECOL
--------------- ----------
X
21 X
22 X
23 X
24 X
25 X
26 X
27 X
28 X
29 X
30 X
31 X
12 rows selected.
SQL>

How to pass string data type to number datatype in plsql

I have procedure like this...
declare
v_psg varchar2(10);
id_no number;
begin
select value into v_psg from settings_am where key = 'PSG';
select id into id_no from product where to_char(psg) in (v_psg);
end;`
The value returned from select value into v_psg from settings_am where key = 'PSG'; would be
'1','2','3'
when i run this procedure i am returned with ora error - ORA-01403.
please advise how i should pass the v_psg value to psg column of product table?
EDIT - Tried with test case suggested
If you got ORA-01403, you were kind of lucky. It is the NO_DATA_FOUND error, which means that one (probably the first) query didn't return anything.
Those two statements could be combined into
select id
from product
where to_char(psg) in (select value
from settings_am
where key = 'PSG'
);
Why would you select value first, and then use it in another query? Besides, it just wouldn't work. v_psg is declared as VARCHAR2 variable. The way you described it, it contains the following string: '1','2','3', as if this is what you have:
SQL> create table settings_am (key varchar2(10),
2 value varchar2(20)); --> note size here
Table created.
SQL> insert into settings_am (key, value)
2 values ('PSG', q'['1','2','3']');
1 row created.
SQL> select * From settings_am;
KEY VALUE
---------- --------------------
PSG '1','2','3'
SQL>
As you can see, I enlarged the value column size, although variable you declared says 10. Why? Because of
SQL> select length(value) from settings_am where key = 'PSG';
LENGTH(VALUE)
-------------
11
i.e. you can't put something that is long 11 into something that accepts length 10.
Or, if your data actually contains 3 rows for the PSG key, are those values already enclosed into single quotes? If so, that's strange; people usually don't do that. Anyway, suppose that you managed to get string '1,2,3' (which is what I presume you actually have) into a VARCHAR2 variable, then you have to split it into rows in order to be able to use it in the IN clause:
SQL> create table product (id number, psg varchar2(10));
Table created.
SQL> insert into product (id, psg) values (100, '1');
1 row created.
SQL> insert into product (id, psg) values (200, '2');
1 row created.
SQL>
Query is then (where lines #3 - 5 represent splitting a string into rows):
SQL> select p.id
2 from product p
3 where p.psg in (select regexp_substr('&&v_psg', '[^,]+', 1, level)
4 from dual
5 connect by level <= regexp_count('&&v_psg', ',') + 1
6 );
Enter value for v_psg: 1,2,3
ID
----------
100
200
So, wouldn't it be simpler to use
SQL> select id
2 from product
3 where to_char(psg) in (select value
4 from settings_am
5 where key = 'PSG'
6 );
ID
----------
100
200
SQL>
Note that both options also show why your query is wrong: you can't put two values (rows) into a variable declared as id_no number; as you'd get TOO_MANY_ROWS error.
Finally, what is it that you'd want to do? What problem are you trying to solve? Apparently, except for special cases (only one row for each value) your query can't work. If you could provide test case (create table & insert into sample data), as well as expected output, it would be easier to help you.

Update column value by 1 in Oracle table conditionally

I have an Oracle table with 4 columns (Name, Phone, Email, Count).
If the user is updating value of Name column, then I need to increment Count column value by 1.
If the user is updating values other than Name column, then I don't need to increment Count column value by 1.
Initially, when the record is inserted, Count should be 0. And every time when Name column is updated, the Count should be incremented by 1 (like 1, 2, 3 .....).
How we can achieve this? I am very new to databases.
Thanks a lot for your help.
You can do that in an update and insert trigger or in your program. The later however requires all possible programs to cooperate. The former (triggers) is black art.
A program can do it like this:
UPDATE Person SET Count=Count+1, Phone='123' WHERE name=`csr` and Phone <> '123';
This will update one or no record (i.e. if phone was already 123 it will do nothing).
BTW: there is no nice solution to insert it if it was missing.
Hmm, you changed your question, updating the Name is problematic if you do not have another primary key, is that really what you want?
If you are looking at plsql procedure, then you can use this method,
I am using 3 input variables,
1.) column name to edit
2.) old value
3.) New value to be updated
SQL> create or replace procedure updateval (colname varchar2,oldval varchar2,newval varchar2) is
2 l_prop varchar2(10);
3 l_newval varchar2(10);
4 l_old_val varchar2(10);
5 begin
6 l_prop:=colname;
7 l_newval:=newval;
8 l_old_val:=oldval;
9 IF (upper(l_prop)='NAME') THEN
10 update TESTING123 set name=l_newval,count=count+1 where name=l_old_val;
11 elsif (upper(l_prop)='PHONE') THEN
12 update TESTING123 set PHONE=l_newval ,count=count+1where PHONE=l_old_val;
13 elsif (upper(l_prop)='EMAIL') THEN
14 update TESTING123 set EMAIL=l_newval ,count=count+1 where EMAIL=l_old_val;
15 END IF;
16 end;
17 /
Procedure created.
SQL>
SQL> select * from testing123;
NAME PHONE EMAIL COUNT
---------- ---------- ---------- ----------
abc1 12345 ABC#a.COM 1
xyz 3435 xyz#a.COM 0
SQL> exec updateval ('NAME','abc1','newabc1');
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> select * from testing123;
NAME PHONE EMAIL COUNT
---------- ---------- ---------- ----------
newabc1 12345 ABC#a.COM 2
xyz 3435 xyz#a.COM 0

Returning the value of identity column after insertion in Oracle

How do I return the value of an identity column (id) in Oracle 12c after insertion? Seems like most of the approaches out there uses sequence to get back the id of the inserted item.
Simply use the RETURNING clause.
For example -
RETURNING identity_id INTO variable_id;
Test case -
SQL> set serveroutput on
SQL> CREATE TABLE t
2 (ID NUMBER GENERATED ALWAYS AS IDENTITY, text VARCHAR2(50)
3 );
Table created.
SQL>
SQL> DECLARE
2 var_id NUMBER;
3 BEGIN
4 INSERT INTO t
5 (text
6 ) VALUES
7 ('test'
8 ) RETURNING ID INTO var_id;
9 DBMS_OUTPUT.PUT_LINE('ID returned is = '||var_id);
10 END;
11 /
ID returned is = 1
PL/SQL procedure successfully completed.
SQL>
SQL> select * from t;
ID TEXT
---------- --------------------------------------------
1 test
SQL>

Oracle Constraints

I am working on a small school project using oracle database. I have created some tables and two of them are Mobile (Mobile_Number,Status_Flag) Status_Flag shows if a number is active or not and there is another table Owner_Mobile(Owner_Id FK,Mobile_ID FK). Now I should write a Constraint that prohibits the insert operation if the corresponding Status_Flag is N for the specified number. I tried to make it using sub query but this is not possible.
the constrain should be applied to OWNER_MOBILE table of course. For example if I say: INSERT INTO OWNER_MOBILE(25541,042536) the constrain should check the Mobile table and see if the Mobile 042536 is active or not . If the number is not active the insert statement should generate a error
You can use the trigger or another PL/SQL API for this but you should take into account ACID transaction principles. Let's consider the case when flag value = 0 should prevent insertion:
SQL> create table mobile (mobile_id int primary key, flag int)
2 /
SQL> create table owner_mobile(owner_id int,
2 mobile_id int references mobile(mobile_id))
3 /
SQL> insert into mobile values (1,1)
2 /
SQL> commit
2 /
SQL> create or replace trigger
2 tr_owner_mobile
3 before insert on owner_mobile
4 for each row
5 declare
6 l_flag mobile.flag%type;
7 begin
8 select flag into l_flag
9 from mobile where mobile_id = :new.mobile_id;
10
11 if l_flag = 0 then
12 raise_application_error(-20000, 'Unavalable mobile');
13 end if;
14 end;
15 /
In the code above I simply select flag and rely on the retrieved value - I don't care of ACID.
In the first transaction I update flag value but don't commit:
SQL> update mobile set flag = 0 where mobile_id = 1;
In the second transaction I insert into owner_mobile and get the success:
SQL> insert into owner_mobile values(1,1);
1 row inserted.
Next, I commit the first transaction and later - the second one. What I get then:
SQL> select * from mobile;
MOBILE_ID FLAG
---------- ----------
1 0
SQL> select * from owner_mobile;
OWNER_ID MOBILE_ID
---------- ----------
1 1
Seems this is not what I expect.
I can use select for update to prevent inconsistent behavoiur:
SQL> update mobile set flag = 1;
1 row updated.
SQL> delete from owner_mobile;
1 row deleted.
SQL> commit;
SQL> create or replace trigger
2 tr_owner_mobile
3 before insert on owner_mobile
4 for each row
5 declare
6 l_flag mobile.flag%type;
7 begin
8 select flag into l_flag
9 from mobile where mobile_id = :new.mobile_id
10 for update;
11
12 if l_flag = 0 then
13 raise_application_error(-20000, 'Unavalable mobile');
14 end if;
15 end;
16 /
Now do the same:
SQL> update mobile set flag = 0 where mobile_id = 1;
1 row updated.
Second transaction is waiting because parent row is locked:
SQL> insert into owner_mobile values(1,1);
After commit in first transaction I get in the second one:
SQL> insert into owner_mobile values(1,1);
insert into owner_mobile values(1,1)
*
error in line 1:
ORA-20000: Unavalable mobile
ORA-06512: at "SCOTT.TR_OWNER_MOBILE", line 9
ORA-04088: error in trigger 'SCOTT.TR_OWNER_MOBILE'
So whatever you do to achieve requirements you will have to consider transaction isolation.