Trigger with insert and calculations - sql

I Have two tables, TableA which has a primary key (A_ID) and a salary column.
Table B has a primary key (B_ID) and a paymentAmount column.
I need to create a trigger so that once TableB B_ID is inserted the trigger will go to TableA, find A_ID which matches B_ID, grab the salary on the relating column then divide it by 12 and finally add the result of that calculation to TableB paymentAmount column.
Here is my attempt but it does not compile;
CREATE TRIGGER test AFTER INSERT ON TableB
FOR EACH ROW
BEGIN
UPDATE TableB
SET TableB.paymentamount = TableA.salary / 12 WHERE TableA.staffid = TableB.staffid
END;
I've never used triggers before so apologies if this I'm going about this the wrong way.

I think that this does what you want:
create trigger test before insert on tableb
for each row
declare
v_salary number;
begin
select salary into v_salary from tablea a where a.a_id= :new.b_id;
:new.paymentamount := v_salary / 12;
end;
/
In a nutshell: this is a before trigger that modifies the paymentamount in the row that is about to be inserted. To get the relevant value, we query tablea for the row whose a_id matches the newly inserted b_id and recover the corresponding salary, that we assign to a variable.

Related

how to add trigger to count number of rows automatically after inserting in oracle sql developer

I want to add trigger to count number of movies after inserting!
This is the table to store the count value:
CREATE TABLE mov_count
(mcount NUMBER);
and the movie table:
create table movie
(mov_id number primary key,
mov_title varchar(20),
mov_lang varchar(20));
This is the trigger I have created:
create trigger count_movie_trg
after insert on movie
for each row
BEGIN
UPDATE mov_count
SET mcount = (SELECT COUNT(*) FROM movie);
END;
/
After creating this i tried to add movie but its showing mutating trigger/function may not see it error.
It is the FOR EACH ROW that bothers you. It is a table-level trigger, so:
Enter a dummy value for beginning (as you'll update it later):
SQL> insert into mov_count values (0);
1 row created.
Trigger:
SQL> create or replace trigger count_movie_trg
2 after insert on movie
3 begin
4 update mov_count c set
5 c.mcount = (select count(*) from movie m);
6 end;
7 /
Trigger created.
Testing:
SQL> insert into movie
2 select 1, 'Titanic' from dual union all
3 select 2, 'Superman' from dual;
2 rows created.
SQL> select count(*) from mov_count;
COUNT(*)
----------
1
SQL>
Why not just maintain the value without referring to the original table?
create trigger count_movie_trg after insert on movie for each row
begin
update mov_count set mcount = mcount + 1;
end;
To keep the count up-to-date, you'll need a delete trigger as well.
Don't use the table at all; use a view instead.
CREATE VIEW mov_count ( mcount ) AS
SELECT COUNT(*) FROM movie;
db<>fiddle

For each inserted row create row in other table with foreign key constrain

I have 2 tables with foreign key constraint:
Table A:
[id] int identity(1, 1) PK,
[b_id] INT
and
Table B:
[id] int identity(1, 1) PK
where [b_id] refers to [id] column of Table B.
The task is:
On each insert into table A, and new record into table B and update [b_id].
Sql Server 2008 r2 is used.
Any help is appreciated.
Having misread this the first time, I am posting a totally different answer.
First if table B is the parent table, you insert into it first. Then you grab the id value and insert into table A.
It is best to do this is one transaction. Depending on what the other fields are, you can populate table A with a trigger from table B or you might need to write straight SQL code or a stored procedure to do the work.
It would be easier to describe what to do if you have a table schema for both tables. However, assuming table B only has one column and table A only has ID and B_id, this is the way the code could work (you would want to add explicit transactions for production code). The example is for a single record insert which would not happen from a trigger. Triggers should always handle multiple record inserts and it would have to be written differently then. But without knowing what the columns in the tables are it is hard to provide a good example of this.
create table #temp (id int identity)
create table #temp2 (Id int identity, b_id int)
declare #b_id int
insert into #temp default values
select #B_id = scope_identity()
insert into #temp2 (B_id)
values(#B_id)
select * from #temp2
Now the problem gets more complex if there are other columns, as you would have to provide values for them as well.
Without removing identity specification you can use the following option:
SET IDENTITY_INSERT B ON
Try this:
CREATE TRIGGER trgAfterInsert ON [dbo].[A]
FOR INSERT
AS
IF ##ROWCOUNT = 0 RETURN;
SET NOCOUNT ON;
SET IDENTITY_INSERT B ON
DECLARE #B_Id INT
SELECT #B_Id = ISNULL(MAX(Id), 0) FROM B;
WITH RES (ID, BIDS)
AS
(SELECT Id, #B_Id + ROW_NUMBER() OVER (ORDER BY Id) FROM INSERTED)
UPDATE A SET [b_Id] = BIDS
FROM A
INNER JOIN RES ON A.ID = RES.ID
INSERT INTO B (Id)
SELECT #B_Id + ROW_NUMBER() OVER (ORDER BY Id) FROM INSERTED
SET IDENTITY_INSERT B OFF
GO
Though Nadeem's answer is on the right track, his trigger for some reason takes max.id instead of NEW.id and doesn't update A accordingly.
For what you ask to be usable by trigger, you need the FK in table A to be nulleable, else you have a race condition between the tables.
EDIT: As SpectralGhost pointed out, my original code didn't support multiple rows, this one will do:
CREATE TRIGGER trgAfterInsertA ON TableA
FOR INSERT
AS
DECLARE db_cursor CURSOR FOR
SELECT id FROM INSERTED
DECLARE #an_id int
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #an_id
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO TableB VALUES(VALUE_PLACEHOLDER)
UPDATE TableA
SET b_id = SCOPE_IDENTITY()
WHERE id = #an_id
FETCH NEXT FROM db_cursor INTO #an_id
END
GO
The VALUE_PLACEHOLDER are the values you initialize TableB with.

How can I "group" multiple rows into a collection in my PL/SQL cursor?

I have a cursor where I need to get a whole bunch of information for an interface, but one of the tables that I need to get information from has one row for each information that I need in columns.
I looked into PIVOT and at first it seemed like it would be a mess (especially considering that I don't need an aggregate for this) but I managed to get it to work nicely; but I'm a serial learner so I still want to find out if it's possible like this:
I thought of doing this by fetching a table as a column: "type table of (object)" so I'd have an array of an array in my PL/SQL code... and it worked fine! When my properties table had only one row, but I got ORA-01427 when it had more than one.
Here's a short example code (I'm using just the IDs and addresses tables for simplicity, this is an actual little PL I created just to test this functionality):
CREATE OR REPLACE TYPE CAIB_FIELDS AS OBJECT (
ID_QUALIFIER VARCHAR2(3),
ID_NUMBER VARCHAR2(20)
)
/
CREATE TYPE CAIB_TBL AS TABLE OF CAIB_FIELDS
/
DECLARE
CURSOR MYCUR(CID IN VARCHAR2) IS
SELECT CUST_ID,CUST_ADDR,UPD_DATE
(SELECT CAIB_TBL(CAIB_FIELDS(ID_QUALIFIER,ID_NUMBER)) FROM CUSTOMER_IDS B
WHERE B.CUST_ID = A.CUST_ID
AND B.CUST_ADDR = A.CUST_ADDR) CAIB
FROM CUSTOMER_ADDR A
WHERE A.CUST_ID = CID
;
TYPE MYCUR_TYPE IS TABLE OF MYCUR%ROWTYPE;
REC_MYCUR MYCUR_TYPE;
BEGIN
OPEN MYCUR('918888'); --This customer has only one ID row -> OK! IT WORKS!
--For customer ID '002632', he has several ID ROWS -> ERROR ORA-01427
LOOP
FETCH MYCUR BULK COLLECT INTO REC_MYCUR LIMIT 100;
FOR I IN 1..REC_MYCUR.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).CAIB(1).ID_QUALIFIER);
--DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).CAIB(2).ID_QUALIFIER); --this would be OK if Oracle would allow me to fetch multiple rows on my CAIB_TBL type, of course I'll just loop here if it works, but for the sake of this test, I just used fixed values..
END LOOP;
EXIT WHEN MYCUR%NOTFOUND;
END LOOP;
END;
Thanks in advance!!
--- EDIT, tbone's answer is exactly what I was looking for, but it doesn't reflect the exact scenario as it only deals with a single column table; for multiple columns the solution changes just slightly, here's my final test:
create table testA
(
col1 number,
col2 varchar2(50)
);
create table testB
(
col1 number,
col2 varchar2(50),
col3 varchar2(50)
);
insert into testA values (1,'A');
insert into testA values (2,'B');
insert into testA values (3,'C');
insert into testB values (1,'X','x');
insert into testB values (1,'Y','y');
insert into testB values (1,'Z','z');
insert into testB values (2,'BA','ba');
insert into testB values (2,'BB','bb');
commit;
CREATE OR REPLACE TYPE t_test_rec AS object
(col2 varchar2(50),
col3 varchar2(50)
)
/
create or replace type t_vchar_tab as table of t_test_rec;
DECLARE
CURSOR MYCUR IS
SELECT A.COL1,
CAST(MULTISET(SELECT B.COL2,B.COL3 FROM TESTB B WHERE B.COL1 = A.COL1 ORDER BY B.COL2) AS T_VCHAR_TAB) AS TESTB_VALS
FROM TESTA A
;
TYPE MYCUR_TYPE IS TABLE OF MYCUR%ROWTYPE;
REC_MYCUR MYCUR_TYPE;
BEGIN
OPEN MYCUR;
LOOP
FETCH MYCUR BULK COLLECT INTO REC_MYCUR LIMIT 100;
FOR I IN 1..REC_MYCUR.COUNT
LOOP
IF REC_MYCUR(I).TESTB_VALS.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).COL1 || '->(NULL)');
ELSE
FOR J IN 1..REC_MYCUR(I).TESTB_VALS.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(REC_MYCUR(I).COL1 || '->' || REC_MYCUR(I).TESTB_VALS(J).COL2 || ',' || REC_MYCUR(I).TESTB_VALS(J).COL3);
NULL;
END LOOP;
END IF;
END LOOP;
EXIT WHEN MYCUR%NOTFOUND;
END LOOP;
END;
/
Its a bit unclear what you're trying to achieve, but based on the title (grouping multiple rows into a collection from a cursor), you can do something like this:
set echo on;
set display on;
set linesize 200;
create table testA
(
col1 number,
col2 varchar2(50)
);
create table testB
(
col1 number,
col2 varchar2(50)
);
create or replace type t_vchar_tab as table of varchar2(50);
insert into testA values (1,'A');
insert into testA values (2,'B');
insert into testB values (1,'X');
insert into testB values (1,'Y');
insert into testB values (1,'Z');
commit;
-- select all related testB.col2 values in a nested table for each testA.col1 value
select a.col1,
cast(multiset(select b.col2 from testB b where b.col1 = a.col1 order by b.col2) as t_vchar_tab) as testB_vals
from testA a;
So the output will be only the 2 rows from tableA, but have a nested table column containing all the matching rows from tableB

PL/SQL trigger to insert next value

I created a trigger which works like when I update/insert a row in one table, an insert of a row will a done in another table which contains a primary key.
Now when I insert a row in the first table I want the trigger to check the last value of primary key of another table and if that is null or '-' then I've to insert 1 into that primary key column so as to insert the remaining values.
I've written the code as follows:
create or replace trigger "T1"
AFTER
insert or update on "buses"
for each row
begin
-- Here I want to check the V_id on vehicles table, if that is null or '-' then insert V_id as 1 along with the below insert statement.
if :NEW."b_key" is not null then
INSERT INTO vehicles (b_KEY,B_NAME,ADDRESS_1,CITY,STATE,ZIP,PHONE,WEBSITE) VALUES (:new.b_KEY,:new.b_NAME,:new.ADDRESS_1,:new.CITY,:new.STATE,:new.ZIP,:new.PHONE,:new.WEBSITE);
end if;
end;
How to find the last b_id in the vehicles table, so that if that value is null or '-' insert b_id as 1, followed by the above insert statement in the same row.
By adding another trigger we can do that as follows:
create or replace TRIGGER "B_VEHICLES"
before insert on "buses"
for each row
declare b_number number;
begin
select max(B_ID) into b_number from Vehicles;
if :OLD."B_ID" is null and b_number is null then
select 1 into :new."B_ID" from dual;
else select b_number + 1 into :new."B_ID" from dual;
end if;
end;​

Insert/Update in PL/SQL

I have made a procedure in PL/SQL which inserts data from one table to another on basis of primary key. My procedure is working fine but i can't figure out how will i update column CODE_NUMBER of my table MAIN if primary key already exists.
Actually i want rows of MAIN table to get UPDATED when its has primary key and insert data from REGIONS when primary key does not exists.
DECLARE
variable number;
id number;
description varchar2 (100);
CURSOR C1 IS
select regions.REGION_ID variable
from regions;
BEGIN
FOR R_C1 IN C1 LOOP
BEGIN
select regions.REGION_ID,regions.REGION_NAME
into id,description
from regions
where regions.REGION_ID = R_C1.variable;
----If exists then update otherwise insert
INSERT INTO MAIN( ID, CODE_NUMBER) VALUES( id,description);
dbms_output.put_line( id ||' '|| 'Already Exists');
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
dbms_output.put_line( R_C1.variable);
END;
END LOOP;
END;
There's no need to do this with PL/SQL and cursors. What you really want to do is something like this:
MERGE INTO MAIN dst
USING (
SELECT regions.REGION_ID id,
regions.REGION_NAME description
FROM regions
) src
ON src.id = dst.id
WHEN MATCHED THEN UPDATE
SET dst.code_number = src.description
WHEN NOT MATCHED THEN INSERT (id, code_number)
VALUES (src.id, src.description)
Read more about the SQL MERGE statement in the documentation
I can not really see a point in doing a cursor in this case. Why can't you just do it like this:
--Update the rows
UPDATE MAIN
SET ID=regions.REGION_ID,
CODE_NUMBER=regions.[description]
FROM MAIN
JOIN regions
ON MAIN.ID=regions.REGION_ID;
--Insert the new ones
INSERT INTO MAIN(ID,CODE_NUMBER)
SELECT
regions.REGION_ID,
regions.[description]
FROM
regions
WHERE NOT EXISTS
(
SELECT
NULL
FROM
MAIN.ID=regions.REGION_ID
)