SQL Trigger not working correctly - sql

here are the 2 tables i have, i want to implement an trigger that customer cannot have more than 5 accounts from a one bank, but can have more than 5 in total.
CREATE TABLE ACCOUNT(
ACCOUNT_NO VARCHAR(20) NOT NULL,
BALANCE REAL,
BANK_CODE VARCHAR(20),
BRANCH_NO VARCHAR(25),
ACCOUNT_CODE VARCHAR(20),
PRIMARY KEY(ACCOUNT_NO),
);
CREATE TABLE ACCOUNT_CUSTOMER(
CUS_NO VARCHAR(20) NOT NULL,
ACCOUNT_NO VARCHAR(20) NOT NULL,
PRIMARY KEY(CUS_NO,ACCOUNT_NO),
FOREIGN KEY(ACCOUNT_NO) REFERENCES ACCOUNT(ACCOUNT_NO),
);
heres the trigger i wrote but i can't create more than 5 accounts in total because it checks for all the accounts in all the banks rather than a single bank.
CREATE TRIGGER TRIGGER1
ON ACCOUNT_CUSTOMER
FOR INSERT,UPDATE
AS BEGIN
DECLARE #COUNT INT
DECLARE #CUS_NO VARCHAR(20)
SELECT #COUNT=COUNT(AC.ACCOUNT_NO)
FROM INSERTED I,ACCOUNT_CUSTOMER AC
WHERE I.CUS_NO=AC.CUS_NO
GROUP BY(AC.CUS_NO)
IF #COUNT>5
ROLLBACK TRANSACTION
END
THE PROBLEM IS WITHIN THE GROUPBY FUNCTION AS I GUESS.

this is easy to implement with constraints:
CREATE TABLE ACCOUNT(
ACCOUNT_NO VARCHAR(20) NOT NULL,
BALANCE REAL,
BANK_CODE VARCHAR(20),
BRANCH_NO VARCHAR(25),
ACCOUNT_CODE VARCHAR(20),
PRIMARY KEY(ACCOUNT_NO),
UNIQUE(ACCOUNT_NO,BANK_CODE)
);
CREATE TABLE ACCOUNT_CUSTOMER(
CUS_NO VARCHAR(20) NOT NULL,
ACCOUNT_NO VARCHAR(20) NOT NULL,
BANK_CODE VARCHAR(20),
NUMBER_FOR_BANK INT NOT NULL CHECK(NUMBER_FOR_BANK BETWEEN 1 AND 5),
PRIMARY KEY(CUS_NO,ACCOUNT_NO),
UNIQUE(CUS_NO,BANK_CODE,NUMBER_FOR_BANK),
FOREIGN KEY(ACCOUNT_NO, BANK_CODE) REFERENCES ACCOUNT(ACCOUNT_NO, BANK_CODE),
);
Edit: sometimes triggers do not fire. Only trusted constraints 100% guarantee data integrity.
To insert, I would use Numbers table:
INSERT INTO ACCOUNT_CUSTOMER(
CUS_NO,
ACCOUNT_NO,
BANK_CODE,
NUMBER_FOR_BANK
)
SELECT TOP 1 #CUS_NO,
#ACCOUNT_NO,
#BANK_CODE,
NUMBER
FROM dbo.Numbers WHERE NUMBER BETWEEN 1 AND 5
AND NOT EXISTS(SELECT * FROM ACCOUNT_CUSTOMER WHERE CUS_NO=#CUS_NO AND BANK_CODE=#BANK_CODE)
I would use a trigger to prohibit modifications of BANK_CODE.

I would try something like this:
Replace this part of your trigger
SELECT #COUNT=COUNT(AC.ACCOUNT_NO)
FROM INSERTED I,ACCOUNT_CUSTOMER AC
WHERE I.CUS_NO=AC.CUS_NO
GROUP BY(AC.CUS_NO)
IF #COUNT>5
ROLLBACK TRANSACTION
with this:
IF EXISTS (
SELECT COUNT(a.ACCOUNT_NO)
FROM INSERTED i
JOIN ACCOUNT a ON i.ACCOUNT_NO = a.ACCOUNT_NO
JOIN ACCOUNT_CUSTOMER c ON i.CUS_NO = c.CUS_NO
GROUP BY c.CUS_NO, a.BANK_CODE
HAVING COUNT(a.ACCOUNT_NO) >= 5
)
ROLLBACK TRANSACTION
Also consider that the INSERTED table may have multiple records in it. If those records are for more than one customer and any of the customers causes this trigger to rollback the transaction, then the updates for those customers that did not violate your rule will not be applied. This may never happen (if your application never updates records for more than one customer at a time), or may be the intended behavior.

Try this instead of the current query in your trigger. I think that this might work.
My syntax might be a bit off but you get the general idea.
SELECT #COUNT=MAX(COUNT(AC.ACCOUNT_NO))
FROM INSERTED I
INNER JOIN ACCOUNT_CUSTOMER AC ON I.CUS_NO=AC.CUS_NO
INNER JOIN ACCOUNT A ON AC.ACCOUNT_NO = A.ACCOUNT_NO
GROUP BY(AC.CUS_NO, A.BANK_CODE)

The trouble with your query is that you are only searching by unique customer identifier.
Your query must search a count of a unique customer AND bank identifier together. I'll leave the exact query to you, but here's what you want in pseudocode:
SELECT COUNT(customer_id)
FROM table_name
WHERE customer_id = customer_id_to_validate
AND bank_id = bank_id_to_validate
This will return how many times a customer + bank combination exist. That's the limit you want.

Thanks for the answers, after going through all, i came up with this solution. I inserted a nested query taht will give me the bankcode and by that code i get the count
CREATE TRIGGER TRIGGER1
ON ACCOUNT_CUSTOMER
FOR INSERT,UPDATE
AS BEGIN
DECLARE #COUNT INT
DECLARE #CUS_NO VARCHAR(20)
SELECT #COUNT=COUNT(*)
FROM ACCOUNT_CUSTOMER AC, ACCOUNT A
WHERE A.ACCOUNT_NO=AC.ACCOUNT_NO AND A.BANK_CODE=
(SELECT A.BANK_CODE
FROM DIT09C_0293_ACCOUNT A, INSERTED I
WHERE A.ACCOUNT_NO=I.ACCOUNT_NO
)
IF #COUNT>5
ROLLBACK TRANSACTION
END

Related

How do I insert a subquery with multiple results into multiple rows?

I want to add the results of a query into a table. There are multiple values, being inserted into multiple rows that are currently NULLS.
I have the following tables:
CREATE TABLE CAR (
CarID INT NOT NULL PRIMARY KEY,
Make VARCHAR (30) NOT NULL,
Model VARCHAR (30) NOT NULL,
Type VARCHAR (30) NOT NULL,
YearModel INT NOT NULL,
Price VARCHAR (100) NOT NULL,
);
CREATE TABLE [TRANSACTION] (
tID INT NOT NULL PRIMARY KEY,
cID INT NOT NULL FOREIGN KEY REFERENCES CUSTOMER(CID),
CarID INT NOT NULL FOREIGN KEY REFERENCES CAR(CARID),
eID INT NOT NULL FOREIGN KEY REFERENCES EMPLOYEE(EID),
tDate DATE,
PickupDate DATE NOT NULL,
ReturnDate DATE NOT NULL
);
I then had to add a new column:
ALTER TABLE [TRANSACTION]
ADD Amount_Due int;
The following code gives me the results I need:
SELECT Price*DATEDIFF(DAY,PickupDate, ReturnDate)
FROM [TRANSACTION], CAR
WHERE [TRANSACTION].CarID = CAR.CarID
But I don't know how to insert all of the data into my Amount_Due column.
I have tried to use INSERT INTO, but it's telling me I have a syntax error near Amount_Due.
INSERT INTO [TRANSACTION] Amount_Due
SELECT Price*DATEDIFF(DAY,PickupDate, ReturnDate)
FROM CAR, [TRANSACTION]
WHERE CAR.CarID = [TRANSACTION].CarID
I have played around with INSERT INTO and UPDATE and I cannot wrap my head around what I'm doing wrong, or what I am missing.
I'm using SQL SMS 2018
Thank you for any help.
You are not inserting data you are updating existing rows, so you need to update:
update t set
t.Amount_Due = c.Price * DateDiff(day, c.PickupDate, c.ReturnDate)
from [transaction] t
join car c on c.carId=t.carId
Notes
Always use proper join syntax, avoid adding tables separated by
commas.
Also always alias your tables with meaningful short
aliases for readability.
Avoid using reserved words for objects eg
transaction - if your table contains more than 1 row then call it
transactions and avoid the need to always have to use [] to avoid
ambiguity.
SSMS is not SQL Server, SSMS is just an application used to access SQL Server. Use select ##version if you ever need to know your SQL Server version.

Trigger to check if the combination of two field exist

I want to create a trigger which will check if my showtime with Cinema Hall Id exist or not. This will prevent me from booking one Cinema hall at two same Showtimes.
Secondly in this trigger I am also checking if the showtime I am assigning to movie lies in movie release and last date range or not.
But I don't know why none of the statement is working.
Create table Movie([Movie_ID] int primary key not null,[Movie_Name] varchar(50) Unique not null,[Realease_Date] date not Null,[Last_Date] date,Runtime time(0) not null ,Status varchar(20) not null,Rating float)
Create table [Showtime]([Showtime_ID] int primary key not null,Date date not null,Time time(0) not Null)
Create table [Cinema Halls]([Cinema_Halls_ID] int primary key not null,[Total_Seats] int not Null)
Create table [Movie Schedule] (
[Movie_Schedule_ID] int primary key not null,
[Movie_ID] int NOT null,
[Showtime_ID] int not null,
Cinema_Halls_ID int not null
Constraint fk_M_ID FOREIGN KEY ([Movie_ID]) REFERENCES Movie([Movie_ID]),
Constraint fk_Sh_ID FOREIGN KEY ([Showtime_ID]) REFERENCES Showtime([Showtime_ID]),
Constraint fk_C_ID FOREIGN KEY ([Cinema_Halls_ID]) REFERENCES [Cinema Halls] ([Cinema_Halls_ID])
)
/*Trigger Stops duplicate booking of Cinema halls and invalid showtime of movie*/
Create Trigger Trigger_Movie_Shedule
On "Movie Schedule"
After Insert,Update
As
declare #Cinema_Halls_ID int ,#Showtime_ID int,#Movie_ID int,#Release_Date Date,#Last_Date Date, #Showtime_Date date;
Select #Cinema_Halls_ID =Cinema_Halls_ID from inserted ;
Select #Showtime_ID=Showtime_ID from inserted;
Select #Movie_ID=Movie_ID from inserted;
Select Showtime_Date=Date from Showtime where Showtime_ID=#Showtime_ID
Select #Release_Date= Release_Date from Movie where Movie_ID=#Movie_ID;
Select #Last_Date=Last_Date from Movie where Movie_ID=#Movie_ID;
IF EXISTS (select count (Showtime_ID) from "Movie Schedule"
where Showtime_ID = #Showtime_ID and Cinema_Halls_ID = #Cinema_Halls_ID )
BEGIN
PRINT'This Cinema Hall is Already Booked'
Rollback Transaction;
return
END
ELSE IF (#Showtime_DATE >= #Release_Date and #Showtime_Date<= #Last_Date)
BEGIN
PRINT'Movie Showtime not in Range'
Rollback Transaction;
return
END
I think this is what you are looking for. The changes/improvements/best practices are:
Uses set-based logic, which you should always aim to do in a relational database.
Uses Inserted as a table rather than a single row
Semi-colon line terminators
set nocount on
Uses throw
Uses [] instead of ""
Fixed "in range" logic and detection of duplicate logic
CREATE TRIGGER Trigger_Movie_Shedule
ON [Movie Schedule]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM [Movie Schedule] S
-- Restrict the check to the inserted/updated records
INNER JOIN Inserted I on I.Showtime_ID = S.Showtime_ID and I.Cinema_Halls_ID = S.Cinema_Halls_ID
GROUP BY S.Showtime_ID, S.Cinema_Halls_ID
-- If more than one row exists we have a problem Houston
HAVING COUNT(*) > 1
) BEGIN
-- Rolls back, returns and provides an error message all in one.
THROW 51000, 'This Cinema Hall is Already Booked',1;
END; ELSE IF EXISTS (
SELECT 1
FROM Inserted I
INNER JOIN Movie M ON M.Movie_ID = I.Movie_ID
INNER JOIN ShowTime S ON S.ShowTime_ID = I.ShowTime_ID
-- WHERE S.Showtime_DATE >= M.Release_Date and S.Showtime_Date < M.Last_Date
-- Think you logic detects when it *is* in range, whereas the error is when its out of range
WHERE S.Showtime_DATE < M.Release_Date or S.Showtime_Date > M.Last_Date
) BEGIN
-- Rolls back, returns and provides an error message all in one.
THROW 51000, 'Movie Showtime not in Range',1;
END;
END;
Note: I highly recommend reading Using Inserted and Deleted as it explains very clearly how to write triggers.

Delete a record based on multiple table choices SQL

I'm trying to wrap my head around how to accomplish this Delete query. The goal is I'm trying to delete a client record (main table) based on if they don't have an insurance policy (another table) and if their needs description is "transportation" and importance values is LESS than 5. The needs is another table. They are all connected with foreign keys and SSN as the connector and Delete cascade is working properly. The query is partially working as is. If there is no insurance policy, the Client is being deleted correctly. However, the need description and importance value factors are not currently working. It will still delete if I have no insurance policy, but my importance description is another value other than transportation.
It's almost like I need 2 subqueries compare both Needs table and Insurance_Policy table for deletion, but I don't know how to do that.
The database I'm using is Azure Data Studio
Here is my current Procedure code:
DROP PROCEDURE IF EXISTS Option17;
GO
CREATE PROCEDURE Option17
AS
BEGIN
DELETE FROM Client
WHERE Client.SSN NOT IN (SELECT I.SSN
FROM Insurance_Policy I, Needs N
WHERE Client.SSN = I.SSN
AND Client.SSN = N.SSN
AND N.need_description = 'transportation'
AND N.importance_value < 5)
END
Also, here are my table structures:
CREATE TABLE Client
(
SSN VARCHAR(9),
doctor_name VARCHAR(60),
doctor_phone_no VARCHAR(10),
lawyer_name VARCHAR(60),
lawyer_phone_no VARCHAR(10),
date_assigned DATE,
PRIMARY KEY (SSN),
FOREIGN KEY (SSN) REFERENCES Person
ON DELETE CASCADE
);
CREATE TABLE Insurance_Policy
(
policy_id VARCHAR(10),
provider_id VARCHAR(10),
provider_address VARCHAR(100),
insurance_type VARCHAR(10),
SSN VARCHAR(9),
PRIMARY KEY (policy_id),
FOREIGN KEY (SSN) REFERENCES Client,
);
CREATE TABLE Needs
(
SSN VARCHAR(9),
need_description VARCHAR(60),
importance_value INT CHECK(importance_value > 0 and importance_value <11),
PRIMARY KEY(SSN,need_description),
FOREIGN KEY(SSN) REFERENCES Client
ON DELETE CASCADE
);
Here is a screenshot if the formatting didn't hold up on procedure.
enter image description here
Based on your answers, I believe this is the code you are looking for. If this is not working, let me know.
To explain a little, using an INNER join will eliminate the need for a couple of those WHERE conditions. INNER JOIN only returns records where it exists in both tables. Also there is no need to link to the Client table from within the subquery.
Also you want where it does not have a description of transportation with an importance of less than 5. Since you are pulling a list to leave alone, you do not want to include these records.
DROP PROC IF EXISTS Option17;
GO
Create proc Option17
AS
BEGIN
DELETE FROM Client
WHERE SSN NOT IN (
SELECT
N.SSN
FROM Needs N
INNER JOIN Insurance_Policy I ON N.SSN = I.SSN
WHERE NOT (N.need_description = 'transportation' AND N.importance_value < 5)
);
END
GO
I think you want separate conditions on Needs and Insurance_Policy. And I recommend NOT EXISTS, because it better handles NULL values:
DELETE c
FROM Client c
WHERE NOT EXISTS (SELECT 1
FROM Insurance i
WHERE c.SSN = i.SSN
) AND
EXISTS (SELECT 1
FROM Needs n
WHERE c.SSN = n.SSN AND
n.need_description = 'transportation' AND
n.importance_value < 5
);

Putting pre-update values into another table with a trigger

I've created a table called ProductsAudit that is meant to hold values from the MyGuitarShop Products table after they have been updated with a trigger, but I can't seem to get it to work. I'm doing this in SQL Server.
So what I need to happen is when Products is updated, it stores the old value in to the ProductsAudit table.
CREATE TABLE ProductsAudit
(
AuditID int NOT NULL,
CategoryID int,
ProductCode varchar(10) NOT NULL,
ProductName varchar(255) NOT NULL,
ListPrice money NOT NULL,
DiscountPercent money NOT NULL,
DateUpdated datetime
PRIMARY KEY (AuditID)
FOREIGN KEY (CategoryID) REFERENCES Categories(CategoryID)
)
CREATE TRIGGER Products_UPDATE2
ON Products
AFTER UPDATE
AS
BEGIN;
ROLLBACK TRAN
INSERT INTO ProductsAudit
SELECT CategoryID, ProductCode, ProductName, ListPrice, DiscountPercent
FROM Deleted
WHERE CategoryID = (SELECT CategoryID FROM Inserted);
PRINT 'Old data sent to ProductsAudit'
END;
UPDATE Products
SET ListPrice = 79.43
WHERE ProductID = 3;
CREATE TABLE ProductsAudit
(
AuditID int NOT NULL IDENTITY(1,1),
CategoryID int,
ProductCode varchar(10) NOT NULL,
ProductName varchar(255) NOT NULL,
ListPrice money NOT NULL,
DiscountPercent money NOT NULL,
DateUpdated datetime DEFAULT (GETDATE()),
PRIMARY KEY (AuditID),
FOREIGN KEY (CategoryID) REFERENCES Categories(CategoryID)
)
CREATE TRIGGER Products_UPDATE2
ON Products
AFTER UPDATE
AS
BEGIN;
INSERT INTO ProductsAudit (CategoryID, ProductCode, ProductName, ListPrice, DiscountPercent, DateUpdated)
SELECT
d.CategoryID
,d.ProductCode
,d.ProductName
,d.ListPrice
,d.DiscountPercent
,DateUpdated = GETDATE() -- may consider adding default to the table to handle this part
FROM
deleted d
LEFT JOIN inserted i
ON d.CategoryID= i.CategoryID --is this the Product PrimaryKey Column?
WHERE
ISNULL(d.CategoryID,-1) <> ISNULL(i.CategoryID,-1)
OR ISNULL(d.ProductCode,'') <> ISNULL(i.ProductCode,'')
OR ISNULL(d.ProductName,'') <> ISNULL(i.ProductName,'')
OR ISNULL(d.ListPrice,-1) <> ISNULL(i.ListPrice,-1)
OR ISNULL(d.DiscountPercent,-1000) <> ISNULL(i.DiscountPercent,-1000)
PRINT 'Old data sent to ProductsAudit'
END;
UPDATE Products
SET ListPrice = 79.43
WHERE ProductID = 3;
So you seem to have a few issues going on, and I will see if I can walk you through them and 1 way of fixing. First, you say you want ProductAudit to hold values "AFTER" they have been updated but your code and the term Audit suggests you want to store the OLD values (deleted) into an Audit Table not the new values so that is the assumption I worked from.
2 main issues with the code you showed.
ROLLBACK TRAN - this is sort of like an undo button that would roll back all statements in the transactions which could be more than just the update you are wanting to track. This would mean your data wouldn't actually get updated.
(SELECT CategoryId FROM Inserted) - Triggers are evaluated on a set based operation NOT a scalar value so the table inserted would have more than 1 CateogryId if more than 1 row is updated. So it would have picked the last row in the data set and only that row would have been inserted into your Audit Table.
Note you can use the deleted and the inserted tables to get to the information you want. technically you only need the deleted if you want to store a record no matter what. I am showing how to use both to detect if there was actually a change to the record and then only log if there was one.

Using sql function on CHECK constraint for newly inserted row

First of all i need help with this for my bachelor thesis. I'm doing the whole database on sql server 2008 Release 2.
The problem is with check constraint that is using a function that is working on her own but not with the use in the constraint. The result of the constraint should be something like this: An employee could go only on one bussines trip per day.
Table Bussines trips:
CREATE TABLE SluzebniCesta(
idSluzCesty int PRIMARY KEY NOT NULL,
DatumCesty DATE NOT NULL,
CasOdjezdu TIME(0) NOT NULL,
CasPrijezdu TIME(0),
CONSTRAINT Odjezd_prijezd CHECK(CasPrijezdu > DATEADD(hour,2,CasOdjezdu))
);
Table that contains the employs that goes on bussines trip:
CREATE TABLE ZamNaCeste(
idZamNaCeste int PRIMARY KEY NOT NULL,
SluzebCestaID int NOT NULL,
ZamestnanecID int NOT NULL,
FOREIGN KEY (ZamestnanecID) REFERENCES Zamestnanec(idZamestnance),
FOREIGN KEY (SluzebCestaID) REFERENCES SluzebniCesta(idSluzCesty)
);
Foreign key ZamestnanecID is an employee's id and SluzebCestaID is the bussines trip id.
Now the function :
CREATE FUNCTION myCheckZamNaCeste(#SluzebCestaID int, #ZamestnanecID int)
RETURNS int
AS
BEGIN
DECLARE #retVal int;
DECLARE #Zamestnanec int;
DECLARE #SluzebniCesta int;
SET #Zamestnanec = (SELECT idZamestnance FROM Zamestnanec WHERE idZamestnance=#ZamestnanecID);
SET #SluzebniCesta = (SELECT idSluzCesty FROM SluzebniCesta WHERE idSluzCesty=#SluzebCestaID);
IF EXISTS ( SELECT DatumCesty FROM SluzebniCesta
WHERE idSluzCesty = #SluzebniCesta
AND DatumCesty IN (SELECT DatumCesty FROM ZamNaCeste
LEFT JOIN SluzebniCesta
ON ZamNaCeste.SluzebCestaID = SluzebniCesta.idSluzCesty
WHERE ZamestnanecID=#Zamestnanec))
BEGIN
SET #retVal=0;
END
ELSE
BEGIN
SET #retVal=1;
END
return #retVal
END
GO
And the alter table for the table that contains evidence of employee and their bussines trips:
ALTER TABLE ZamNaCeste
ADD CONSTRAINT check_cesty_zamestnance CHECK(dbo.myCheckZamNaCeste(SluzebCestaID,ZamestnanecID)=1);
And when I try to enter any new row the constraint is broken even if the function gives the right data. return 1 is the good result ....
In the first place, I'm not sure but it looks like the two set statements in the function are going out to retrieve from tables exactly the same values they already have from being passed in as parameters.
In the second place, I don't see anything limiting trips in the same day. Anywhere.
If you wanted to limit a trip by an employee to one per day, that is easy.
CREATE TABLE ZamNaCeste(
idZamNaCeste int PRIMARY KEY NOT NULL,
SluzebCestaID int NOT NULL,
ZamestnanecID int NOT NULL,
TripDate date not null,
FOREIGN KEY (ZamestnanecID) REFERENCES Zamestnanec(idZamestnance),
FOREIGN KEY (SluzebCestaID) REFERENCES SluzebniCesta(idSluzCesty),
constraint UQ_OneTripPerDay unique( ZamestnanecID, TripDate )
);
The unique constraint ensures the same employee cannot log more than one trip on the same day.
Well in the end i solved with a more sophisticated and better looking solution. The employ is limited with the times of arrival and departure. And i solved it with a function that returns number of incorrect occurences, if its zero than its all right and it works:
SELECT COUNT(*) FROM(SELECT * FROM SluzebniCesta JOIN ZamNaCeste
ON SluzebniCesta.idSluzCesty = ZamNaCeste.SluzebCestaID) AS a
JOIN (SELECT * FROM SluzebniCesta2 JOIN ZamNaCeste
ON SluzebniCesta.idSluzCesty = ZamNaCeste.SluzebCestaID)AS b
ON a.SluzebCestaID b.SluzebCestaID
AND a.CasOdjezdu b.CasOdjezdu
AND a.ZamestnanecID = b.ZamestnanecID
AND (SELECT SluzebniCesta.DatumCesty FROM SluzebniCesta
WHERE SluzebniCesta.idSluzCesty = a.SluzebCestaID) = (SELECT SluzebniCesta.DatumCesty
FROM SluzebniCesta WHERE SluzebniCesta.idSluzCesty = b.SluzebCestaID)