Prevent duplicates with trigger SQL - sql

My objective is inserting the first insert, but not letting the second to pass, because NIC is duplicated. I don't know why, but it isn't letting the first one pass without having other NIC to compare if it already exists one equal.
I know I can prevent duplicates with "unique", but I was trying to do with a trigger :/
Create table Utentes
(
numUtente nchar(3),
Name nchar(40) not null,
NIC nchar(8) not null,
Constraint PK_Utente Primary Key(numUtente)
)
create trigger tr_Duplicate
on Utentes
after insert
as
declare #NIC nchar(8)
select #NIC = NIC from inserted
if exists(select * from Utentes where NIC = #NIC)
begin
print 'NIC already in database'
rollback
end
go
insert into Utentes (numUtente, Name, NIC)
values ('123', 'asd', '12345678')
insert into Utentes (numUtente, Name, NIC)
values ('124', 'asd', '12345678')
select * from Utentes
Result:
NIC already in database
Msg 3609, Level 16, State 1, Line 1392
The transaction ended in the trigger. The batch has been aborted.

You should really use a constraint. An "after insert" trigger will actually put the second row in the table . . . and hopefully no one is using NOLOCK for reading it.
In any case, you have to actually count the rows and look for multiple occurrences. It would be something like this:
Create trigger tr_Duplicate on Utentes after INSERT as
begin
if exists (select 1
from utentes u join
inserted i
on u.nic = i.nic
group by u.nic
having count(*) > 1
)
begin
print 'NIC already in database';
rollback;
end;
end;
With an instead of trigger, you would not add new rows into the table if one already exists:
create trigger tr_Duplicate on Utentes after INSERT as
begin
if exists (select 1
from utentes u join
inserted i
on u.nic = i.nic
)
begin
print 'NIC already in database';
rollback;
end;
else
begin
insert into utentes
select i.*
from inserted i;
end;
end;

I would second the sentiment against the use of triggers and would also suggest using UNIQUE constraints. In my humble opinion, I would rather search for a solution in the ETL layer, grouping records as they are inserted. With triggers you will get the aforementioned concurrency and consistency issues, as well as potentially swelling your tempdb or T-log if the table ever gets big enough to take some time to process.

Related

Stored procedure for referential integrity between two tables in different database?

For two tables A and B, I'd like to implement referential integrity such that in tables A a foreign key's value must present in table B, while in table B a primary key can only be deleted or modified if that value does not present in table A. My requirement is that I'd like to have table A, and B as variable, and apply the procedure to any arbitrary instances of tables. That is,
sp_referential_integrity_across_databases(A, B)
I have figured out how to do the referential integrity as triggers for a pair of particular tables. I wonder if it's feasible to write such stored procedure to save future effort?
My environment is Microsoft SQL Server 2017. The more portable the solution, the better.
Here are my crafted procedures:
The triggers on table "A" for insert and update:
USE DWPractice
IF OBJECT_ID ('dbo.trgCheckCustomer_Cat_Id_Customer_D', 'TR') IS NOT NULL
DROP Trigger trgCheckCustomer_Cat_Id_Customer_D;
GO
CREATE TRIGGER trgCheckCustomer_Cat_Id_Customer_D
ON Customer_D
AFTER INSERT, UPDATE
AS
IF NOT EXISTS
(
SELECT Customer_Cat_Id
FROM inserted
WHERE Customer_Cat_Id IN (SELECT Customer_Cat_Id FROM [OtherDW].[dbo].[Customer_Cat_D])
)
BEGIN
RAISERROR('Lookup Value Not Found -- Inerst Failed', 16, 1);
ROLLBACK TRANSACTION;
END;
The trigger on table "B" for delete and update:
USE OtherDW
IF OBJECT_ID ('dbo.trgCheckCustomer_Cat_Id_Customer_Cat_D', 'TR') IS NOT NULL
DROP Trigger trgCheckCustomer_Cat_Id_Customer_Cat_D;
GO
CREATE TRIGGER trgCheckCustomer_Cat_Id_Customer_Cat_D
ON Customer_Cat_D
AFTER DELETE, UPDATE
AS
Begin
IF EXISTS
(
SELECT Customer_Cat_Id
FROM deleted
WHERE Customer_Cat_Id IN (SELECT Customer_Cat_Id FROM [DWPractice].[dbo].[Customer_D])
)
BEGIN
RAISERROR('Lookup Value Found -- Delete Failed', 16, 1);
ROLLBACK TRANSACTION;
END;
-- It seems that the following for the case of update is not needed
-- The above clauses would get executed even for the case of update.
-- IF EXISTS
-- (
-- SELECT Customer_Cat_Id
-- FROM inserted
-- WHERE Customer_Cat_Id IN (SELECT Customer_Cat_Id FROM [DWPractice].[dbo].[Customer_D])
-- )
-- BEGIN
-- RAISERROR('Lookup Value Found -- Update Failed', 16, 1);
-- ROLLBACK TRANSACTION;
-- END;
End;
If stored procedure is not the best practice, then what is the best practice? It seems to me, there's much boiler-plate code, with only database name and table name are variables.
The logic in your (first) trigger is not correct. If you have multiple rows in inserted, then only one has to match. Instead, you want:
CREATE TRIGGER trgCheckCustomer_Cat_Id_Customer_D ON Customer_D AFTER INSERT, UPDATE
AS BEGIN
IF EXISTS (SELECT 1
FROM inserted i LEFT JOIN
[OtherDW].[dbo].[Customer_Cat_D] d
ON i.Customer_Cat_Id = d.Customer_Cat_Id
WHERE d.Customer_Cat_Id IS NULL
)
BEGIN
RAISERROR('Lookup Value Not Found -- Insert Failed', 16, 1);
ROLLBACK TRANSACTION;
END;
END; -- trigger

Record Counts in Stored Procedures

I have a SP that INSERT INTO TBL_DOMAIN from TBL_STAGING, but first I want check table TBL_STAGING to make sure the table is not empty before I truncate table TBL_DOMAIN, if table TBL_STAGING got more than one record then proceed the truncate table TBL_DOMAIN then run the INSERT, ELSE message say the table TBL_STAGING is EMPTY and exit the SP. My goal is to make sure the table TBL_DOMAIN still have the data even is old. I'm very new SQL please help.
CREATE PROCEDURE [dbo].[SP_INSERT_ALL_DOMAIN]
WITH EXECUTE AS CALLER
AS
BEGIN
BEGIN TRANSACTION NT_ALL_DOMAIN
INSERT INTO TBL_DOMAIN
(DOMAIN_NAME,
DISTINGUISHED_NAME,
EMAIL_ADDR_I)
SELECT DOMAIN_NAME,
DISTINGUISHED_NAME,
EMAIL_ADDR_I
FROM TBL_STAGING
First you need to check if TBL_STAGING has data:
IF EXISTS (SELECT TOP 1 1 FROM TBL_STAGING)
BEGIN
BEGIN TRANSACTION NT_ALL_DOMAIN
INSERT INTO TBL_DOMAIN
(DOMAIN_NAME,
DISTINGUISHED_NAME,
EMAIL_ADDR_I)
SELECT DOMAIN_NAME,
DISTINGUISHED_NAME,
EMAIL_ADDR_I
FROM TBL_STAGING
COMMIT
END
ELSE
BEGIN
RETURN 'no data on table'
END

need to write a trigger

I want to write a trigger for a table "TRANSACTION".When a new line is inserted, I want to trigger to update the field "TRANSACTIONID" to the maximum + 1 of all the previous records.
I on't know much about SQL. Can someone help me?
many thanks
This is a really bad idea for a multi-user environment, as it will serialise inserts into the table. The usual approach is to use an Oracle sequence:
create sequence transaction_seq;
create trigger transaction_bir before insert on transaction
for each row
begin
:new.id := transaction_seq.nextval;
end;
To write a trigger based solution that actually got the max current value plus 1, you would need to write a complex 3-trigger solution to avoid the "mutating table" issue. Or you could create a simpler solution using another table to hold the current maximum value like this:
create table transaction_max (current_max_id number);
insert into transaction_max values (0);
create trigger transaction_bir before insert on transaction
for each row
declare
l_current_max_id number;
begin
update transaction_max set current_max_id = current_max_id + 1
returning current_max_id into l_current_max_id;
:new.id := l_current_max_id;
end;
This will avoid the mutating table issue and will serialize (slow down) inserts, so I don't see any advantage of this over using a sequence.
CREATE TRIGGER trigger1 on TransactionTable
INSTEAD OF INSERT
AS
BEGIN
DECLARE #MaxTranId INT
SELECT
#MaxTranId = MAX(TransactionId)
FROM
TransactionTable
INSERT INTO TransactionTable
SELECT
#MaxTranId + 1 ,
RestOfYourInsertedColumnsHere ,
FROM
inserted
END
GO

How to insert a record into multiple tables using a trigger?

I have two Tables.
I want to insert the same record on both tables at the same time.
I.e., while I insert a record for the first table, this same record also is inserted in the second table using a trigger.
Do you have any experience/advice in this process ?
if you're using stored procedures you can easily manage this
CREATE PROCEDURE sp_Insert
#Value varchar(10)
AS
insert into table1 (...,...) values (#value,...)
insert into table2 (...,...) values (#value,...)
I would suggest using Erik's method over a trigger. Triggers tend to cause performance issues, and a lot of times, you forget that the trigger exists, and get unexpected behavior. If you do want to use a trigger however, it will work. here is an example:
CREATE TRIGGER trgTest ON Test
FOR INSERT
AS
INSERT Test2
(Id, value)
SELECT Id, Value
FROM Inserted
Can Use Cursor Concept!
CREATE OR REPLACE TRIGGER bi_order
BEFORE INSERT
ON ord
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
WHEN (NEW.payment_type = 'CREDIT')
DECLARE
CURSOR cur_check_customer IS
SELECT 'x'
FROM customer
WHERE customer_id = :NEW.customer_id
AND credit_rating = 'POOR';
lv_temp_txt VARCHAR2(1);
lv_poor_credit_excep EXCEPTION;
BEGIN
OPEN cur_check_customer;
FETCH cur_check_customer INTO lv_temp_txt;
IF (cur_check_customer%FOUND) THEN
CLOSE cur_check_customer;
RAISE lv_poor_credit_excep;
ELSE
CLOSE cur_check_customer;
END IF;
EXCEPTION
WHEN lv_poor_credit_excep THEN
RAISE_APPLICATION_ERROR(-20111, 'Cannot process CREDIT ' ||
'order for a customer with a POOR credit rating.');
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20122, 'Unhandled error occurred in' ||
' BI_ORDER trigger for order#:' || TO_CHAR(:NEW.ORDER_ID));
END bi_order;

What is the syntax to use a Select statement inside a PL/SQL Trigger?

This is what I currently have:
CREATE OR REPLACE TRIGGER MYTRIGGER
AFTER INSERT ON SOMETABLE
FOR EACH ROW
DECLARE
v_emplid varchar2(10);
BEGIN
SELECT
personnum into v_emplid
FROM PERSON
WHERE PERSONID = :new.EMPLOYEEID;
dbms_output.put(v_emplid);
/* INSERT INTO SOMEOTHERTABLE USING v_emplid and some of the other values from the trigger table*/
END MYTRIGGER;
DBA_ERRORS has this error:
PL/SQL: ORA-00923: FROM keyword not found where expected
1) There must be something else to your example because that sure seems to work for me
SQL> create table someTable( employeeid number );
Table created.
SQL> create table person( personid number, personnum varchar2(10) );
Table created.
SQL> ed
Wrote file afiedt.buf
1 CREATE OR REPLACE TRIGGER MYTRIGGER
2 AFTER INSERT ON SOMETABLE
3 FOR EACH ROW
4 DECLARE
5 v_emplid varchar2(10);
6 BEGIN
7 SELECT personnum
8 into v_emplid
9 FROM PERSON
10 WHERE PERSONID = :new.EMPLOYEEID;
11 dbms_output.put(v_emplid);
12 /* INSERT INTO SOMEOTHERTABLE USING v_emplid and some of the other values
from the trigger table*/
13* END MYTRIGGER;
14 /
Trigger created.
SQL> insert into person values( 1, '123' );
1 row created.
SQL> insert into sometable values( 1 );
1 row created.
2) You probably want to declare V_EMPLID as being of type Person.PersonNum%TYPE so that you can be certain that the data type is correct and so that if the data type of the table changes you won't need to change your code.
3) I assume that you know that your trigger cannot query or update the table on which the trigger is defined (so no queries or inserts into someTable).
You are playing with Lava (not just fire) in your trigger. DBMS_OUTPUT in a trigger is really, really bad. You can blow-out on a buffer overflow in your trigger and the whole transaction is shot. Good luck tracking that down. If you must do output-to-console like behavior, invoke an AUTONOMOUS TRANSACTION procedure that writes to a table.
Triggers are pretty evil. I used to like them, but they are too hard to remember about. They affect data often times leading to MUTATING data (scary and not just because Halloween is close).
We use triggers to change the value of columns like .new:LAST_MODIFIED := sysdate and .new:LAST_MODIFIED_BY := user. That's it.
Don't ever allow a TRIGGER to prevent a transaction from completing. Find another option.
I would not use a select statment in a trigger ever. Insert into the table rather than a select into. Once the table already exists select into does not work in most databases.