Mutating trigger issue in oracle - sql

I am facing mutating trigger error,
I will describe the issue here
I am using tableA and tableB.
TableA holds a column called empChecked which can hold 2 values '-', '+'.
TableB holds a column called mgrChecked which can hold 2 values '-', '+'.
The current requirement is both fields in empChecked and mgrchecked must be in sync. i mean if empChecked is updated to '+' then mgr checked in tableB must be updated to '+' and vice versa. Updation is possible from front end for both fields.
I have created trigger on both the tables. but i am facing ora-04091 error.
Please suggest me any approach to achieve this?

The mutating table error is a code smell. It almost always points to a bad data model, usually insufficient normalisation.
Certainly a bad data model is in evidence here. You have a column on a table with two settings. That's fine. Now you want to add the same column to a second table and keep the two in sync. This new column is completely pointless. There is no information available in that new column than you cannot get from querying the first table.
And that's what ORA-04091 is telling you. You can spend an awful lot of time building a workaround but that would all be wasted effort.

Instead of using triggers for that kind of synchronization why don't you just create a view
named TableB that will "contain" all data from TableB (not mgrchecked) + empChecked column from TableA
Something like this
create or replace view TAbleB as
select t1.id
, t1.Column2
, ...
, t1.ColumnN
, t.empChecked
from TableA t
, TableB t1
where t.id = t1.id

You're a little unclear on detail, but I think this would help (assuming you have an ID column on both tables)
CREATE TRIGGER tri_table_a
AFTER UPDATE ON table_a
DECLARE
hits NUMBER :=0;
BEGIN
SELECT count(*) INTO hits FROM table_a a INNER JOIN table_b b ON (a.id=b.id) WHERE a.emp_checked<>b.mgr_checked;
IF hits>0 THEN
UPDATE table_b b SET mgr_checked=(SELECT emp_checked FROM table_a a WHERE a.id=b.id);
END IF;
END;
/
CREATE TRIGGER tri_table_b
AFTER UPDATE ON table_b
DECLARE
hits NUMBER :=0;
BEGIN
SELECT count(*) INTO hits FROM table_a a INNER JOIN table_b b ON (a.id=b.id) WHERE a.emp_checked<>b.mgr_checked;
IF hits>0 THEN
UPDATE table_a a SET emp_checked=(SELECT mgr_checked FROM table_b b WHERE a.id=b.id);
END IF;
END;
/
The key is don't use FOR EACH ROW, which will cause table lock.
Edit
This will NOT conflicts with table lock or ORA-04091, because there's no FOR EACH ROW in trigger condition (a.k.a. not a row trigger), which means the trigger body will only be executed AFTER the whole update action has been finished;
This will NOT cause an infinite loop, because when count(*) in trigger body returns 0, the UPDATE inside trigger body will not be executed;
Also, count(*) has to be used because this is a statement trigger and there's no :new.emp_checked available;
General scenario: Update table_a -> triggers tri_table_a -> [trigger]check count -> [trigger]update table_b ->triggers tri_table_b -> [trigger]check count -> done.
I tried this out myself before posting this answer.

Related

How to fill in values from table B into table A based on a condition

I have a matrix A that is composed of let's say one column A.ID. I added another column A.BIRTHDATE with the ALTER TABLE Command (see below).
Now, I would like to fill in A.BIRTHDATE with the matching birthdates in table B that is composed of B.ID and B.BIRTHDATE.
There are plenty of similar examples in this forum but none of them worked for me. I do not know whether it has something to do with Oracle-SQL or because of minor differences in the questions asked.
One of the suggested solutions utilizes UPDATE SET and a JOIN (see second code line).
I get an error message stating that a subquery got more than one result per record. How to fix this?
ALTER TABLE A ADD BIRTHDATE DATE NULL;
UPDATE A
SET A.BIRTHDATE = (
SELECT B.BIRTHDATE
FROM B
WHERE A.ID= B.ID
);
There is issue of multiple records with same ID in table B.
You need to decide which record from table B is of your interest.
You can use merge statement to update values in table A as following:
Merge into A
Using (select id,
min(birthdate) as birthdate -- you can also use max or any logic of your interest
From B)
On (a.id=b.id)
When matched then
Update set a.birthdate = b.birthdate;
Also, update statement should be something like this:
UPDATE A
SET A.BIRTHDATE = (
SELECT min(B.BIRTHDATE) -- or max or other logic
FROM B
WHERE A.ID= B.ID
);
Cheers!!

Checking if an update already took place in ACCESS

I am using the following code, to combine several tables into 1 single Table called DB_Total:
INSERT INTO DB_Total
SELECT *
FROM Tags_DI_DB;
But, if I (accidentally) execute this query twice or three times, the data just gets stacked. Is there a possibility to check wether or not a table is added in the new "mastertable", DB_Total?
INSERT INTO DB_TOTAL
SELECT a.*
FROM Tags_DI_DB a
LEFT JOIN DB_TOTAL b
ON a.colName = b.colName
WHERE b.colName IS NULL
where colName is the unique column

DELETE statement issues within a trigger definition

I have created an insert/update trigger that is designed to update information in a different table based on the data being inserted. The last thing the trigger does (or is supposed to do) is remove all data from a target table with conditions that may have changed during the insert portions of the trigger.
Everything appears to be working with the trigger except the final DELETE statement. It is executing the DELETE statement, but not following any of the conditions in the where clause. It simply deletes everything in the table.
I have even tried changing the NOT IN in the where clause to IN, and it still does the same. I have isolated the DELETE statement and tested outside the trigger and it works fine (using the same variables and subqueries).
Am I missing something with the behavior of a trigger?
Here comes the code:
ALTER TRIGGER [dbo].[cust_trgr_profile_attribute]
ON [dbo].[port_module_instance_setting]
AFTER INSERT, UPDATE
AS
DECLARE #ModuleId INT=449,
#MatchGroupModSetting VARCHAR(50) = 'AttributeGroup',
#FilterGroupModSetting VARCHAR(50) = 'FilterAttributeGroup',
#MatchAttributes TABLE (attribute_id INT),
#FilterAttributes TABLE (attribute_id INT)
INSERT INTO #MatchAttributes
SELECT DISTINCT camatch.attribute_id
FROM inserted I
JOIN core_attribute camatch ON I.value = CONVERT(VARCHAR(10), camatch.attribute_group_id)
JOIN port_module_instance pmi ON I.module_instance_id = pmi.module_instance_id
AND pmi.module_id=#ModuleId
WHERE I.name like #MatchGroupModSetting+'_'
INSERT INTO #FilterAttributes
SELECT DISTINCT cafilter.attribute_id
FROM inserted I
JOIN core_attribute cafilter ON I.value = CONVERT(VARCHAR(10), cafilter.attribute_group_id)
JOIN port_module_instance pmi ON I.module_instance_id = pmi.module_instance_id
AND pmi.module_id=#ModuleId
WHERE I.name=#FilterGroupModSetting
IF ((SELECT COUNT(*) FROM #MatchAttributes) > 0 OR (SELECT COUNT(*) FROM #FilterAttributes) > 0)
BEGIN
IF (SELECT COUNT(*) FROM #MatchAttributes) > 0
BEGIN
UPDATE cpa
SET cpa.[required]=0
FROM cust_profile_attribute cpa
JOIN #MatchAttributes ma ON cpa.attribute_id = ma.attribute_id
END
IF (SELECT COUNT(*) FROM #FilterAttributes) > 0
BEGIN
UPDATE cpa
SET cpa.[required]=0
FROM cust_profile_attribute cpa
JOIN #FilterAttributes fa ON cpa.attribute_id=fa.attribute_id
END
DELETE FROM cust_profile_attribute
WHERE attribute_id NOT IN (SELECT distinct ca.attribute_id
FROM core_attribute ca
JOIN port_module_instance_setting inst ON CONVERT(VARCHAR(10),ca.attribute_group_id) = inst.value
JOIN port_module_instance modinst ON inst.module_instance_id = modinst.module_instance_id
AND modinst.module_id = #ModuleId
WHERE inst.name like #MatchGroupModSetting + '_'
OR inst.name like #FilterGroupModSetting)
END
I have found a flaw in my fundamental logic of how the trigger works. I have now refined my understanding of what is going on and able to articulate more information for others to help. Rather than try to completely transform the original post, I thought it better to post the new info here.
The basic idea is that the port_module_instance_setting table stores a string in the that represents a setting (in this specific case, the conditions are set so that the value field will always be a number). What I am trying to accomplish is that when the value field is updated in one of these specific "settings", everything in the cust_profile_attribute table that is referenced by the old value gets deleted. In this context, the value field of port_module_instance_setting is a numeric varchar value that directly references the attribute_group_id of core_attribute. Please don't comment on best practices regarding referencing tables using different data types, as I have no control over the actual table structure :)
Everything in the trigger functions properly, except the DELETE statement at the end. It isn't doing anything. Any ideas on why it isn't deleting the attributes I want it to?
I have changed the DELETE statement as follows to reference the pre-updated valuefield.
DELETE FROM cust_profile_attribute
WHERE attribute_id IN(SELECT ISNULL(attribute_id,-1) FROM deleted d
JOIN core_attribute ca ON ca.attribute_group_id= CONVERT(INT,d.value))

How do you find if a unique pair value already exists in a sql table? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Finding duplicate values in a SQL table
I'm completely new to T-Sql and Sql programming in general so I'm hoping someone can steer me in the right direction. Here's my problem.. I have a table with only 2 columns, AppliedBandwidthSourceKey and AppliedBandwithSource for simplicity I'll call them columns A and B respectively.
Columns A and B compose the primary key. When I try to insert a record where the value for column A already exists I immediately get a Primary Key constraint violation and if column A doesn't exist but column B does I get Unique Key constraint violation. My question is how do I check if the 'value pair' already exists in the table? if does then do nothing otherwise insert.
I've seen several solutions to similar problems using tsql's merge and if not exists statements but I just can't grasp the concept. Any help would be greatly appreciated.
You don't actually have to do the work first . . . after all, that's what the constraints are doing.
Instead, learn about try/catch blocks:
begin try
insert into t(a, b) values('a', 'b')
end try
begin catch
print 'Oops! There was a problem, maybe a constraint violation for example'
end catch;
This statement attempts the insert. If there is a failure, then it goes to the "catch" portion, and you can do whatever you want (including ignoring the problem). The documentation is reasonably clear (http://msdn.microsoft.com/en-us/library/ms175976.aspx).
You can do this:
IF NOT EXISTS (select * from yourtable where yourfield1 = 'field1' and yourfield2 = 'field2')
BEGIN
INSERT INTO ...
END
This simply insert data into yourtable if the data is not found.
use a count statment where (A and B) If count returns 0, then the pair doesn't exist. If it returns a number not 0 (X), then the entry pair exists (X) times.
In some cases you can use left/right join, where you join on your PK's:
insert into tableA (Id,AnotherId)
select b.Id, b.AnotherId
from tableB b
left join tableA a
on b.Id = a.id
and b.AnotherId = a.AnotherId
where a.Id is null
Another left join version:
insert into tableA (Id,AnotherId)
select b.Id, b.AnotherId
from (select Id = 5, AnotherId = 5) b
left join tableA a
on b.Id = a.id
and b.AnotherId = a.AnotherId
where a.Id is null
and a.Id is null

SQL server 2008 trigger not working correct with multiple inserts

I've got the following trigger;
CREATE TRIGGER trFLightAndDestination
ON checkin_flight
AFTER INSERT,UPDATE
AS
BEGIN
IF NOT EXISTS
(
SELECT 1
FROM Flight v
INNER JOIN Inserted AS i ON i.flightnumber = v.flightnumber
INNER JOIN checkin_destination AS ib ON ib.airport = v.airport
INNER JOIN checkin_company AS im ON im.company = v.company
WHERE i.desk = ib.desk AND i.desk = im.desk
)
BEGIN
RAISERROR('This combination of of flight and check-in desk is not possible',16,1)
ROLLBACK TRAN
END
END
What i want the trigger to do is to check the tables Flight, checkin_destination and checkin_company when a new record for checkin_flight is added. Every record of checkin_flight contains a flightnumber and desknumber where passengers need to check in for this destination.
The tables checkin_destination and checkin_company contain information about companies and destinations restricted to certain checkin desks. When adding a record to checkin_flight i need information from the flight table to get the destination and flightcompany with the inserted flightnumber. This information needs to be checked against the available checkin combinations for flights, destinations and companies.
I'm using the trigger as stated above, but when i try to insert a wrong combination the trigger allows it. What am i missing here?
EDIT 1:
I'm using the following multiple insert statement
INSERT INTO checkin_flight VALUES (5315,3),(5316,3),(5316,2)
//5315 is the flightnumber, 3 is the desknumber to checkin for that flight
EDIT 2:
Tested a single row insert which isn't possible, then the error is being thrown correct. So it's the multiple insert which seems to give the problem.
The problem is that your logic is allowing any insert that includes at least one valid set of values through. It will only fail if all of the inserted records are invalid, instead of if any of the inserted records are invalid.
Change your "IF NOT EXISTS(...)" to a statement "IF EXISTS(...)" and change your SELECT statement to return invalid flights.
eg:
IF EXISTS
(
SELECT 1
FROM Flight v
INNER JOIN Inserted AS i ON i.flightnumber = v.flightnumber
LEFT JOIN checkin_destination AS ib ON ib.airport = v.airport
AND i.desk = ib.desk
LEFT JOIN checkin_company AS im ON im.company = v.company
AND i.desk = im.desk
WHERE (im.desk IS NULL OR ib.desk IS NULL)
)
BEGIN
RAISERROR('This combination of of flight and check-in desk is not possible',16,1)
ROLLBACK TRAN
END
I'm not sure of your business logic, but you need to check that the query does the proper thing.
Your problem is the IF NOT EXISTS, if the condition is true for 1 of the 3 rows in INSERTED it does not exist. You need to convert it to find a problems row and use IF EXISTS then error out.
However, when in a trigger the best way to error out is:
RAISERROR()
ROLLBACK TRANSACTION
RETURN
I kind of doubt that the lack of a RETURN is your problem, but it is always best to include the three Rs when erroring out in a trigger.
The problem is that the condition will be true if only one of the inserted records are correct. You have to check that all records are correct, e.g.:
if (
(
select count(*) from inserted
) = (
select count(*) from flight v
inner join inserted i ...
)
) ...
The inserted table can contain multiple rows and therefore all logic within a trigger MUST be able to apply to all rows. The idea triggers must fire once per row effect is a common misunderstanding WRT triggers. SQL Server will tend to coalesce calls to a trigger to increase performance when they occur within the same transaction.
To fix you might start with a COUNT() of inserted and compare that with a COUNT() of the matching conditions and raise an error if there is a mismatch.