How to prevent insert when condition is met - sql

I have a two tables
CREATE TABLE table1 (
ID int not null,
Status int not null,
TimeStamp datetime2(3)
CONSTRAINT pkId PRIMARY KEY (ID)
);
and
CREATE TABLE Status (
StatusID int not null,
StatusName varchar(64) not null
CONSTRAINT pStatus PRIMARY KEY (StatusID)
);
with inserted values
INSERT INTO table1 (ID,StatusID,TimeStamp) VALUES
(1,1,'2021-06-15 07:30:31'),
(2,2,'2021-07-15 07:30:31'),
(3,3,'2021-08-15 07:30:31'),
(4,4,'2021-09-15 08:30:31'),
(5,5,'2021-09-15 07:30:31'),
(6,5,'2021-09-15 07:30:31'),
(7,4,'2021-09-15 07:30:31'),
(8,2,'2021-09-15 07:30:31'),
(9,1,'2021-09-15 07:30:31');
and
INSERT INTO dbo.Status (StatusID,StatusName) VALUES
(1,'wants to enroll'),
(2,'granted enrollment'),
(3,'declined enrollment'),
(4,'attending the course'),
(5,'finished the course');
I wrote a trigger but it prevent all insert when the StatusID < 4 and what I want is to prevent insert into TimeStamp only when the condition is StatusID < 4. I use SQL Server.
CREATE TRIGGER dbo.Prevent_Insert4
ON dbo.table1
FOR INSERT
AS
SET NOCOUNT ON
IF EXISTS(SELECT *
FROM dbo.table1 t
JOIN inserted i ON t.ID=i.ID
WHERE t.StatusID<4)
BEGIN
RAISERROR('You can not update when StatusId value < 4', 16, 1)
ROLLBACK TRAN
SET NOCOUNT OFF
RETURN
END
SET NOCOUNT OFF
Thank you in advance.

Sounds like you just need a CHECK constraint
ALTER TABLE table1
ADD CONSTRAINT CHK_Status
CHECK(Status >= 4 OR TimeStamp IS NULL);
If you really, really wanted to use triggers for this (not recommended) then it should look like this
CREATE OR ALTER TRIGGER dbo.Prevent_Insert4
ON dbo.table1
FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
IF EXISTS(SELECT 1
FROM inserted i
WHERE i.StatusID < 4
AND i.TimeStamp IS NOT NULL)
BEGIN
THROW 50001, 'You can not set TimeStamp when StatusId value < 4', 1;
-- no need for rollback, will happen automatically if you use THROW
END;
go

With your code you are doing rollback of inserted row when condition is met. If I understood you correctly you just to want override timestamp with null value if StatusId < 4.
If so, you should write that instead of rollback
IF EXISTS(SELECT *
FROM dbo.table1 t
JOIN inserted i ON t.ID=i.ID
WHERE t.StatusID<4)
BEGIN
UPDATE t SET t.TimeStamp = NULL
FROM inserted i
JOIN dbo.table1 t ON i.ID = t.ID
WHERE t.StatusID<4
END

Related

if condition, then upsert else do nothing

I am trying to upsert a value only if a condition is true, otherwise I dont want to do anything to the record. My code seems to generate primary key errors when I run it again (indicating the insert is firing, but the whole statement should be skipped)
Error on second run: Violation of PRIMARY KEY constraint 'PK__chicken__A2D9E564407C90CD'. Cannot insert duplicate key in object 'dbo.chicken'. The duplicate key value is (10118-78843).
Query:
IF((select status from chicken where [PrimaryKey]='10118-78843')!='paid')
update [eComm]..[chicken] set [Status]='Overdue' where [PrimaryKey]='10118-78843'
IF ##ROWCOUNT=0 insert into [eComm]..[chicken]([PrimaryKey],[Status]) values('10118-78843','Paid');
create example table code:
create table chicken(
PrimaryKey nvarchar(50) not null,
Status nvarchar(10),
PRIMARY KEY (PrimaryKey)
)
Goal (this is invalid syntax):
IF((select status from chicken where [PrimaryKey]='10118-78843')!='paid')
{
update [eComm]..[chicken] set [Status]='Overdue' where [PrimaryKey]='10118-78843'
IF ##ROWCOUNT=0 insert into [eComm]..[chicken]([PrimaryKey],[Status]) values('10118-78843','Paid');
}
else, do nothing
The problem is that you are not taking into account what happens if there is an existing row but the status is paid.
You need to tweak the logic a bit:
declare #status varchar(20) = (
select c.status
from chicken c
where c.[PrimaryKey] = '10118-78843'
);
if (#status != 'paid')
update [chicken]
set [Status] = 'Overdue'
where [PrimaryKey] = '10118-78843';
else if (#status is null)
insert into [chicken] ([PrimaryKey], [Status])
values ('10118-78843', 'Paid');
Alternatively:
insert into [chicken] ([PrimaryKey], [Status])
select '10118-78843', 'Paid'
where not exists (select 1
from chicken c
where c.[PrimaryKey] = '10118-78843'
);
if (##ROWCOUNT = 0)
update [chicken]
set [Status] = 'Overdue'
where [PrimaryKey] = '10118-78843'
and status != 'Paid';

INSERT statement conflicting with FOREIGN KEY constraint, but there's no conflict apparent

I'm not sure why I'm getting this error, because no data is being inserted into the column mentioned in the error. It's a PK field set to IDENTITY, so the value is auto-filled with each record added. The function is supposed to insert a record into an audit table, called by a trigger on the source table. Here's the error and my code.
Msg 547, Level 16, State 0, Procedure tblTriggerAuditRecord_TTeamPlayers, Line 33 [Batch Start Line 344]
The INSERT statement conflicted with the FOREIGN KEY constraint "Z_TTeamPlayers_TTeams_FK". The conflict occurred in database "dbSQL1", table "dbo.Z_TTeams", column 'intTeamAuditID'.
--Problematic Code
DELETE FROM TTeamPlayers
DELETE FROM TTeams
WHERE strTeam = 'Reds'
SELECT * FROM TTeams
SELECT * FROM Z_TTeams
=========================
-- both these tables are updated when a DELETE is run. I have the FK constraint set to CASCADE so that when it's deleted out of the child table Z_TTeamPlayers, the parent record is deleted.
CREATE TABLE Z_TTeamPlayers
(
intTeamPlayerAuditID INTEGER IDENTITY NOT NULL
,intTeamAuditID INTEGER NOT NULL
,intPlayerAuditID INTEGER NOT NULL
,UpdatedBy VARCHAR(50) NOT NULL
,UpdatedOn DATETIME NOT NULL
,strAction VARCHAR(10) NOT NULL
,strModified_Reason VARCHAR(1000)
--,CONSTRAINT PlayerTeam_UQ UNIQUE ( intTeamID, intPlayerID )
,CONSTRAINT Z_TTeamPlayers_PK PRIMARY KEY ( intTeamPlayerAuditID )
)
CREATE TABLE Z_TTeams
(
intTeamAuditID INTEGER IDENTITY NOT NULL
,intTeamID INTEGER NOT NULL
,strTeam VARCHAR(50) NOT NULL
,strMascot VARCHAR(50) NOT NULL
,UpdatedBy VARCHAR(50) NOT NULL
,UpdatedOn DATETIME NOT NULL
,strAction VARCHAR(10) NOT NULL
,strModified_Reason VARCHAR(1000)
,CONSTRAINT Z_TTeams_PK PRIMARY KEY ( intTeamAuditID )
)
==============================
ALTER TABLE Z_TTeamPlayers ADD CONSTRAINT Z_TTeamPlayers_TTeams_FK
FOREIGN KEY ( intTeamAuditID ) REFERENCES Z_TTeams ( intTeamAuditID ) ON DELETE CASCADE
==============================
-- --------------------------------------------------------------------------------
-- Create Trigger for Z_TTeamPlayers
-- --------------------------------------------------------------------------------
GO
CREATE TRIGGER tblTriggerAuditRecord_TTeamPlayers on TTeamPlayers
AFTER UPDATE, INSERT, DELETE
AS
DECLARE #Now DATETIME
DECLARE #Modified_Reason VARCHAR(1000)
DECLARE #Action VARCHAR(10)
SET #Action = ''
-- Defining if it's an UPDATE, INSERT, or DELETE
BEGIN
IF (SELECT COUNT(*) FROM INSERTED) > 0
IF (SELECT COUNT(*) FROM DELETED) > 0
SET #Action = 'UPDATE'
ELSE
SET #Action = 'INSERT'
ELSE
SET #Action = 'DELETE'
END
SET #Now = GETDATE() -- Gets current date/time
IF (#Action = 'INSERT')
BEGIN -- Begin INSERT info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT I.intTeamID, I.intPlayerID, SUSER_NAME(), GETDATE(), #Action, I.strModified_Reason
FROM INSERTED as I
INNER JOIN TTeamPlayers as T ON T.intTeamPlayerID = I.intTeamPlayerID
END -- End Insert Info
ELSE
IF (#Action = 'DELETE')
BEGIN -- Begin INSERT info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT D.intTeamID, D.intPlayerID, SUSER_SNAME(), GETDATE(), #Action, ''
FROM DELETED as D
END -- End Delete Info
ELSE -- #Action = 'UPDATE'
BEGIN --begin UPDATE info get modified reason
IF EXISTS (SELECT TOP 1 I.strModified_Reason FROM INSERTED as I, TPlayers as T WHERE I.intPlayerID = T.intPlayerID
AND I.strModified_Reason <> '')
BEGIN -- beging insert of UPDATE info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT I.intTeamID, I.intPlayerID, SUSER_SNAME(), GETDATE(), #Action, I.strModified_Reason
FROM TTeamPlayers as T
INNER JOIN INSERTED as I ON T.intPlayerID = I.intPlayerID
-- set modified reason column back to empty string
UPDATE TPlayers SET strModified_Reason = NULL
WHERE intPlayerID IN (SELECT TOP 1 intPlayerID FROM INSERTED)
END
ELSE
BEGIN -- begin if no modified reasson supplied
PRINT 'Error and rolled back, please enter modified reason'
ROLLBACK
END
END
z_TTeamPlayers.intTeamAuditID references your audit table's primary key. In your code you are inserting that value into z_TTeamPlayers... INSERT INTO Z_TTeamPlayers(intTeamAuditID... while it doesn't exist (as a primary key) in your audit table yet... thus it fails.
Here is a demo.
I get that you are trying to audit, but i'm not sure your business logic on teams and players. You seem to have your design a bit backwards. You could always use versioning in SQL Server.
As a guess you probably want a design similar to this instead

Triggers execution after group update with "in (...)" statement in query

I have AFTER UPDATE trigger for in table:
ALTER TRIGGER [dbo].[table1]
ON [dbo].[table]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #primaryKey bigint
SELECT #PrimaryKey = PK FROM Inserted
if EXISTS(select * from [dbo].[table1] where PK=#PrimaryKey)
begin
update [dbo].[table1] set [Action] = 'U' where PK=#PrimaryKey
end
else
begin
insert into [dbo].[table1] ([PK], [Action], StampIn)
values (#PrimaryKey, 'U', GETDATE())
end
END
When I do "update SOME_DB.dbo.TABLE set FIELD='NEW VALUE' where PK in (3,4,5)", I found that only one row was added to the table1 with PK "3". Which means that trigger was executed only once in table.
But I need to have all the rows in table1 with PKs that were updated.
Could you please help me to solve my problem?
Thank you.
SQL triggers use the inserted view to identify all the rows being inserted. Your logic is only looking at one of the rows; hence it does not do what you expect. So:
BEGIN
SET NOCOUNT ON;
update t1
set [Action] = 'U'
from table1 t1 join
inserted i
on i.primarykey = t1.pk ;
insert into [dbo].[table1] ([PK], [Action], StampIn)
select i.primarykey, 'U', getdate()
from inserted i
where not exists (select 1 from dbo.table1 t1 where t1.pk = i.primarykey);
END;
You don't actually need the conditional logic, because the join and where clauses take care of that.

Trigger After Update SQL

I have Customer table. To simplify lets say i have two columns
Id
Name
I have a second table (Log) that I want to update ONLY when the Id column of my customer changes. Yes you heard me right that the primary key (Id) will change!
I took a stab but the NewId that gets pulled is the first record in the Customer table not the updated record
ALTER TRIGGER [dbo].[tr_ID_Modified]
ON [dbo].[customer]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE (Id)
BEGIN
UPDATE [log]
SET NewId = Id
FROM customer
END
END
Many would make the argument that if you are changing PK values, you need to rethink the database/table design. However, if you need a quick & dirty fix, add a column to the customer table that is unique (and not null). Use this column to join between the [inserted] and [deleted] tables in your update trigger. Here's a sample script:
CREATE TABLE dbo.Customer (
Id INT CONSTRAINT PK_Customer PRIMARY KEY,
Name VARCHAR(128),
UQColumn INT IDENTITY NOT NULL CONSTRAINT UQ_Customer_UQColumn UNIQUE
)
CREATE TABLE dbo.[Log] (
CustomerId INT NOT NULL,
LogMsg VARCHAR(MAX)
)
INSERT INTO dbo.Customer
(Id, Name)
VALUES
(1, 'Larry'),
(2, 'Curley'),
(3, 'Moe')
INSERT INTO dbo.[Log]
(CustomerId, LogMsg)
VALUES
(1, 'Larry is cool'),
(1, 'Larry rocks'),
(2, 'Curley cracks me up'),
(3, 'Moe is mean')
CREATE TRIGGER [dbo].[tr_Customer_Upd]
ON [dbo].[customer]
FOR UPDATE
AS
BEGIN
UPDATE l
SET CustomerId = i.Id
FROM inserted i
JOIN deleted d
ON i.UQColumn = d.UQColumn
JOIN [Log] l
ON l.CustomerId = d.Id
END
SELECT *
FROM dbo.[Log]
UPDATE dbo.Customer
SET Id = 4
WHERE Id = 1
SELECT *
FROM dbo.[Log]

Create trigger prevent insert

I'm trying to execute the following trigger:
create trigger t23
on studies
after insert, update, delete
as
begin
REFERENCING NEW ROW NewStudent
FOR EACH ROW
WHEN (30 <= (SELECT SUM(credits) FROM Studies)
DELETE FROM NewStudent N
WHERE N.spnr = NewStudent.spnr
end
I'm trying to create a trigger which only inserts a student if the credits is < or == to '30'. The "Credits" is a type int.
I'm getting numerous errors trying to implement this trigger. I really have tried everything and i m out of options. Could someone who is expert in the field point me in the right direction?
The example "Using a DML AFTER trigger to enforce a business rule between the PurchaseOrderHeader and Vendor tables" in the CREATE TRIGGER MSDN documentation does exaclty what you're looking for:
USE AdventureWorks2008R2;
GO
IF OBJECT_ID ('Purchasing.LowCredit','TR') IS NOT NULL
DROP TRIGGER Purchasing.LowCredit;
GO
-- This trigger prevents a row from being inserted in the Purchasing.PurchaseOrderHeader table
-- when the credit rating of the specified vendor is set to 5 (below average).
CREATE TRIGGER Purchasing.LowCredit ON Purchasing.PurchaseOrderHeader
AFTER INSERT
AS
DECLARE #creditrating tinyint, #vendorid int;
IF EXISTS (SELECT *
FROM Purchasing.PurchaseOrderHeader p
JOIN inserted AS i
ON p.PurchaseOrderID = i.PurchaseOrderID
JOIN Purchasing.Vendor AS v
ON v.BusinessEntityID = p.VendorID
WHERE v.CreditRating = 5
)
BEGIN
RAISERROR ('This vendor''s credit rating is too low to accept new purchase orders.', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
The key here is ROLLBACK TRANSACTION, just adapt the example to suit your need and you're done.
Edit: This should accomplish what you're looking for, but I have not tested it so your mileage may vary.
create trigger dbo.something after insert as
begin
if exists ( select * from inserted where sum(credits) > 30 )
begin
rollback transaction
raiserror ('some message', 16, 1)
end
end
Another edit, based on some assumptions (please note I wrote this script on the fly since I can't test it right now):
create table dbo.students
(
student_id int not null,
name varchar (50) not null
)
create table dbo.courses
(
course_id int not null,
name varchar (50) not null,
required_credits int not null
)
create table dbo.results
(
student_id int not null,
course_id int not null,
course_result int not null
)
create trigger dbo.check_student_results on dbo.results after insert as
(
declare #check int
select #check = count(*)
from inserted as a
join dbo.courses as b on b.course_id = a.course_id
where b.required_credits > a.course.result
if #check <> 0
begin
rollback transaction
raiserror('The student did not pass the course.', 16, 1)
end
)
This way when you insert records in the dbo.results table the constraint checks if the student has passed the course, and cancels the insertion if appropriate. However, it's better to check this things in the application layer.