SQL Server triggers to check credit card balance - sql-server

I've been trying to find a solution to a very simple problem, but I just cant find out how to do it. I have two tables Transactions and Credit_Card.
Transactions
transid (PK), ccid (FK: to credit_card > ccid), amount, timestamp
Credit_Card
ccid (PK), Balance, creditlimit
I want to create a trigger so before someone inserts a transaction it checks that the transaction amount + the balance of the credit card does not go over the creditlimit and if it is, it rejects the insert.
"EDIT" The following code fixed my issue, big thanks to Dan Guzman for his contribution!
CREATE TRIGGER TR_transactions
ON transactions FOR INSERT, UPDATE
AS
IF EXISTS(
SELECT 1
FROM (
SELECT t.ccid, SUM(t.amount) AS amount
FROM inserted AS t
GROUP BY t.ccid) AS t
JOIN Credit_Card AS cc ON
cc.ccid = t.ccid
WHERE cc.creditlimit <= (t.amount + cc.balance)
)
BEGIN
RAISERROR('Credit limit exceeded', 16, 1);
ROLLBACK;
END;

If I understand correctly, you just need to check the credit limit against newly inserted/updated transactions. Keep in mind that a SQL Server trigger fires once per statement and a statement may affect multiple rows. The virtual inserted will have images of the affected rows. You can use this to limit the credit check to only the credit cards affected by the related transactions.
CREATE TRIGGER TR_transactions
ON transactions FOR INSERT, UPDATE
AS
IF EXISTS(
SELECT 1
FROM (
SELECT inserted.ccid, SUM(inserted.amount) AS amount
FROM inserted
GROUP BY inserted.ccid) AS t
JOIN Credit_Card AS cc ON
cc.ccid = t.ccid
WHERE cc.creditlimit <= (t.amount + cc.balance)
)
BEGIN
RAISERROR('Credit limit exceeded', 16, 1);
ROLLBACK;
END;
EDIT
I removed the t alias from the inserted table and qualified the columns with inserted instead to better indicate the source of the data. It's generally a good practice to qualify column names with the table name or alias in multi-table queries to avoid ambiguity.
The integers 16 and 1 in the RAISERROR statement specify the severity and state of the raised error. See the SQL Server Books Online reference for details. Severity 11 and greater raise an error, with severities in the 11 through 16 range indicating a user-correctable error.

you can try this.
ALTER trigger [dbo].[TrigerOnInsertPonches]
On [dbo].[CHECKINOUT]
After Insert
As
BEGIN
DECLARE #ccid int
,#amount money
you have to tell sql that the trigger is ofter insert,
then you can declare the using variable. declare
select #ccid=o.ccid from inserted o;
I think is the correct whey to catch the id.
then you can make the select filtiering from this value.
I hope this can be usefull

Related

From MySQL to SQL Server trigger

Can someone please help me converting this into SQL server trigger.
Create a trigger that will cause an error when an update occurs that would result in a salary increase greater than ten percent of the current salary.
CREATE TRIGGER RAISE_LIMIT
AFTER UPDATE OF SALARY ON EMPLOYEE
REFERENCING NEW AS N OLD AS O
FOR EACH ROW
WHEN (N.SALARY > 1.1 * O.SALARY)
SIGNAL SQLSTATE '75000' SET MESSAGE_TEXT='Salary increase>10%'
In SQL Server, every trigger only runs once per statement, not per row, and the pseudo-tables inserted and deleted contain, respectively, the new and old versions of the rows. You must join these on the primary key to match up the versions.
Remember that these tables may contain multiple or no rows. The UPDATE function can tell you if a column was present in the UPDATE command, it will not tell you whether any rows changed.
The trigger runs within the same transaction as the UPDATE, and an error will cause an immediate rollback of all rows. Do not attempt to rollback yourself, this will cause spurious transactional errors. Instead use THROW.
The docs are located here, you should consult them for further syntax.
CREATE TRIGGER RAISE_LIMIT ON EMPLOYEE
AFTER UPDATE
AS
IF (UPDATE(SALARY)
AND EXISTS (SELECT 1
FROM inserted i
JOIN deleted d ON d.ID = i.ID
WHERE i.SALARY > 1.1 * d.SALARY))
BEGIN
THROW 75000, N'Salary increase > 10%', 0;
END;
GO

SQL Server trigger: limit record insert

I'm doing a database for a university work. At certain point we have to create a trigger/function that can limit insertion of records on my SQL Server Management Studio 17, taking into account different foreign keys. For example we can have 100 records, but we can only have 50 records for the same foreign key.
We have this constraint:
CREATE TABLE [dbo].[DiaFerias] WITH CHECK
ADD CONSTRAINT [Verificar22DiasFerias]
CHECK (([dbo].[verificarDiasFerias]((22)) ='True'))
With the help of this function:
ALTER FUNCTION [dbo].[verificarDiasFerias] (#contagem INT)
RETURNS VARCHAR(5)
AS
BEGIN
IF EXISTS (SELECT DISTINCT idFuncionario, idDiaFerias
FROM DiaFerias
GROUP BY idFuncionario, idDiaFerias
HAVING COUNT(*) <= #contagem)
RETURN 'True'
RETURN 'False'
END
Would I use a trigger here? Reluctantly, yes (See Enforce maximum number of child rows for how to do it without triggers, in a way I'd never recommend). Would I use that function? No.
I'd create an indexed view:
CREATE VIEW dbo.DiaFerias_Counts
WITH SCHEMABINDING
AS
SELECT idFuncionario, idDiaFerias, COUNT_BIG(*) as Cnt
FROM dbo.DiaFerias
GROUP BY idFuncionario, idDiaFerias
GO
CREATE UNIQUE CLUSTERED INDEX PK_DiaFerias_Counts on
dbo.DiaFerias_Counts (idFuncionario, idDiaFerias)
Why do this? So that SQL Server maintains these counts for us automatically, so we don't have to write a wide-ranging query in the triggers. We can now write the trigger, something like:
CREATE TRIGGER T_DiaFerias
ON dbo.DiaFerias
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
IF EXISTS (
SELECT
*
FROM dbo.DiaFerias_Counts dfc
WHERE
dfc.Cnt > 22
AND
(EXISTS (select * from inserted i
where i.idFuncionario = dfc.idFuncionario AND i.idDiaFerias = dfc.idDiaFerias)
OR EXISTS (select * from deleted d
where d.idFuncionario = dfc.idFuncionario AND d.idDiaFerias = dfc.idDiaFerias)
)
)
BEGIN
RAISERROR('Constraint violation',16,1)
END
Hopefully you can see how it's meant to work - we only want to query counts for items that may have been affected by whatever caused us to trigger - so we use inserted and deleted to limit our search.
And we reject the change if any count is greater than 22, unlike your function which only starts rejecting rows if every count is greater than 22.

Archiving data from Table1 to Table2 upon condition | Sql Server 2017 Express

I have a database with two tables:
Table Student with the following
columns:
StudentID int identity,
StudentFN,
StudentLN,
Active bit,
EnrollmentDate
Table ArchivedStudent with the following columns:
ArvchivedStudentID int identity,
StudentID int,
StudentFN,
StudentLN,
WithdrawalDate getdate(),
ReasonDropped
In the long run, I'd like to schedule automatic updates for the table AcrchivedStudent and move the data from columns StudentID, StudentFN and StudentLN from table Student to table ArchnivedStudent when column Active changes from 1 (true) to 0 (false).
Here's my start up script that is not working:
update [as]
set [as].StudentID = s.StudentID,
[as].StudentFN = s.StudentFN,
[as].StudentLN = s.StudentLN
from ArchivedStudent [as]
inner join Student s
on [as].StudentID = s.StudentID
where s.Active = 0
go
The issue is that it does not return any results.
Once I'll be able to update table ArchivedStudent, I'd like to delete data of the students whose Active status changed to 0 in the Student table.
Your question still isn't very clear on the process. For example, do you want to allow the student to be deactivated for a certain period of time before they are moved to the archive table or do you want the student to be immediately moved to the archived table once the student is deactivated?
If the latter, this is much easier:
INSERT INTO ArchivedStudent (StudentId, StudentFn, StudentLn, WithdrawalDate)
SELECT S.StudentId, S.StudentFn, S.StudentLn, GETDATE()
FROM Student S
WHERE StudentId = ?
DELETE FROM Student WHERE StudentId = ?
If the former, then that is more challenging and we will require more detail.
Update 1:
To set the Withdrawal date based off a calculated value, use the following:
INSERT INTO ArchivedStudent (StudentId, StudentFn, StudentLn, WithdrawalDate)
SELECT S.StudentId, S.StudentFn, S.StudentLn, CAST(DATEADD(D,14,GETDATE()) AS DATE)
FROM Student S
WHERE StudentId = ?
Note 1: In DATEADD(), use a positive value for future dates and use a negative value for past dates. You can remove the DATE CAST if you need the actual time in addition to the date.
Note 2: The DELETE script posted in the original answer still stands.
You need a trigger to do it :
CREATE TRIGGER ArchiveStudent ON Student
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO ArchivedStudent (StudentID, StudentFN, StudentLN)
SELECT
StudentID
, StudentFN
, StudentLN
FROM
Student
WHERE
Active = 0
DELETE FROM Student
WHERE
Active = 0
END
However, your approach it's simple, and risky at the same time. For instance, if someone made a student inactive by mistake, then the trigger will immediately insert that student into archive table then deleted. Surely you can retrieve it by many ways such as deleted, inserted tables or even get the max id of archive table, but why you put yourself in this situation in the first place?. This is one of many general issues could be experienced by the current approach. A better approach is to actually add more versioning or historian methods for the tables, and make the archives run either from SQL Job or a store procedure on a fixed dates rather than triggers. this would give you a scheduled and controlled data archiving.
You can even add a historian columns which will store the value of active column and the date of the change. Then, use trigger or store procedure to do it for you (or even a computed column with a generic scalar function that will be reused on multiple tables). for instance, if the student is inactive for 5 business days, then archive it and delete it from the table.
You could use a TRIGGER AFTER UPDATE on the Student table.
This trigger would:
- react only on UPDATE,
- transfer Student to ArchiveStudent, when Active is set to 0
- and set WithdrawalDate to 2 weeks from today.
Trigger:
CREATE TRIGGER [Student_Changed]
ON Student
AFTER UPDATE
AS
BEGIN
-- Archive
INSERT INTO ArchiveStudent
(StudentID, StudentFN, StudentLN, WithdrawalDate)
SELECT
DELETED.StudentID
,DELETED.[StudentFN]
,DELETED.[StudentLN]
,DATEADD(day, 14, GETDATE()) -- 2 Weeks from today
FROM DELETED
WHERE DELETED.Active = 1
-- Delete archived
DELETE FROM Student
WHERE StudentID = (SELECT DELETED.[StudentID] FROM DELETED)
AND Active = 0
END;
DEMO:
You can take a look at the SQL Fiddle solution here.
There are a number of solutions here that all appear partially correct, but with some issues. Your initial update of your archive table will not insert into the archive table, only update an existing row. And since you are trying to join between the live table and the archive table, you will get no results - well, no updates since an update statement doesn't produce "results" as such anyway.
So as other have said you would use two statements - one an insert statement and one a delete. I would tend to be on the careful side and make sure that I a) dont get duplicates in my archive table and b) dont delete from live before I am sure its made it into the archive. So the two statements would be:
insert archivestudent(...fieldlist...)
select * from student
where active=0
and not exists(select * from archivestudent where archivestudent.studentid=student.studentid)
delete student
where active=0
and exists(select * from archivestudent where archivedstudent.studentid=student.studentid)
You can then run this code whenever you wish, schedule it as a job to run each night, whatever makes sense in your app.
If, on the other hand you want to immediately run then a trigger is the way to go. Be aware though that triggers are set-based operations, meaning that the trigger runs once for all rows affected by an update. This means that the solution proposed by #Milan will fail if the triggering update affects more than one row, because the clause WHERE StudentID = (SELECT DELETED.[StudentID] FROM DELETED) will return more one value. An example might be update student set active=0 where enrolmentdate<'2017-01-01'
You should always join to the internal tables exposed inside a trigger, in this case the DELETED table
delete student
from deleted
join student on student.studentid=deleted.studentid
where active=0
I'd still be tempted to add the where exists/not exists clauses inside the trigger as well just to make it more error-proof.
You need two queries:
Insert
Insert into archivedstudent (studentid, student, studentln) select studentid, studentfn, studentln from student where active=0 and studentid not in (select studentid from archivedstudent);
And the delete
Delete from student where studentid in (select studentid from archivedstudent);
What you need is a trigger.SQL Server Trigger After Update for a Specific Value However you should be careful with triggers on large amounts of data, they can hurt performance.

Writing a SQL Server trigger - error

I have three tables:
Bus_Driver (drNo,drName,salary,StationNo,experience)
Station (StationNo,address,district,Salary_Commission)
Cleaner (Cleaner_No, Cname, StationNo)
The question is to write a trigger. It states that if a bus driver's salary is increased by 20% more than the original salary then 0.05% of the increased value will be transferred to his Station as Salary commission.
I managed to write the trigger halfway, but got stuck when I have to transfer the amount to the other table.
My code is:
CREATE TRIGGER tr_1
ON Bus_Driver
AFTER INSERT
AS
BEGIN
DECLARE #salary MONEY
SET #Salary = 0
SELECT #Salary= salary
FROM Inserted
WHERE #Salary > (120 / 100 * #Salary)
Can anyone help me how to write the next steps please
The trigger you wrote is wrong.
First, it's a trigger for insert, while the question states that the salary is raised, meaning it should be a trigger for update.
Second, your trigger assumes only a single row will be in the inserted table. However, this assumption is wrong. Triggers in SQL server are fired per statement, not per row, meaning that the inserted (and deleted) tables might contain zero, one, or many rows.
A solution for this question will be to write a trigger for update, that will, in turn, update the station table. Something like this:
CREATE TRIGGER tr_Bus_Driver_Update ON Bus_Driver
FOR UPDATE
AS
UPDATE s
SET Salary_Commission = Salary_Commission -
(0.0005 * D.Salary) + -- Remove old salary of the driver(s) from salary_commition.
(0.0005 * I.Salary) -- add new salary of the driver(s) to salary_commition
FROM Station s
INNER JOIN Inserted I ON s.StationNo = I.StationNo
INNER JOIN Deleted D ON I.drNo = D.drNo -- assuming drNo is unique in Bus_Driver table
WHERE I.Salary >= D.Salady * 1.2 -- You might need to cast to a floating point data type if the Salary is an integer data type
Note you might need to cast the salary to a floating point data type if the Salary is an integer data type whenever it's used in this trigger.
You need to stop and start again from scratch.
First of all, you need to catch the AFTER UPDATE event - not the insert - since you want to do something when the salary is updated (an existing value is replaced with a higher one).
Secondly, the trigger will be called once per UPDATE statement and if that UPDATE affects more than one row, the Deleted and Inserted pseudo tables will contain multiple rows of data - so your SELECT #Salary = salary FROM Inserted statement is doomed - it will fetch one arbitrary row and ignore all others that might be also affected.
In an UPDATE case, Inserted will have the new values (after the update), while Deleted has the old values (before the update) - so the difference between these two pseudo tables can be used to figure out if the salary increase was more than 20%:
CREATE TRIGGER trBusDriverSalaryIncrease
ON dbo.Bus_Driver
AFTER UPDATE
AS
BEGIN
-- declare a table variable to hold all revelant values
DECLARE #RelevantIncreases TABLE (drNo INT, StationNo INT, SalaryIncrease DECIMAL(18,2))
-- find those bus drivers who have had a more than 20% increase in their salary
INSERT INTO #relevantIncreases (drNo, StationNo, SalaryIncrease)
SELECT
i.drNo, i.StationNo, -- Driver and Station No
(i.Salary - d.Salary) -- Salary increase in absolute numbers
FROM
Deleted d
INNER JOIN
Inserted i ON d.drNo = i.drNo
WHERE
-- Salary increased by more than 20%
i.Salary > 1.2 * d.Salary
-- now that we have all the relevant bus drivers and their salary increase
-- insert this into the Station.Salary_Commission column
UPDATE s
SET Salary_Commission = s.Salary_Commission + ri.SalaryIncrease * 0.0005
FROM dbo.Station s
INNER JOIN #RelevantIncreases ri ON ri.StationNo = s.StationNo
END

SQL Server decrementing inventory

I am using SQL Server 2008. I have a table where orders with SKU are recorded, a table for inventory that has counts and a table where the relationship between the SKU sold and inventory items is recorded.
In the end, I got the report like this
Inventory CurrentQuantity OpenedOrder
SKU1 300 50
SKU2 100 10
Each order will be processed individually. How can I have the database automatically update the inventory tablet after each order is processed?
i.e
If the order has 2 SKU1 in it got processed, the the inventory table will automatically show 298.
Thanks
I would use a Stored Procedure, and perform the order insert and quantity update in one hit:
CREATE PROC dbo.ProcessOrder
#Item int,
#Quantity int
AS
BEGIN
--Update order table here
INSERT INTO dbo.Orders(ItemID,Quantity)
VALUES (#ItemID, #Quantity)
--Update Inventory here
UPDATE dbo.Inventory
SET CurrentQuantity = CurrentQuantity - Quantity
WHERE ItemID = #ItemID
END
I think what you are looking for is a trigger
Basically, set up a trigger that will update the appropriate columns using the inserted/updated data given. Without a full schema set, that is the best answer I can give at this time
I wouldn't be looking at a trigger myself for this.
My check out process
Start a transaction
Check stock level.
If OK, (optional validation / authorisation)
Add a check out record
Reduce the stock
Possibly add some record to invoice teh recipent etc.
Commit the transaction
While you could do it with triggers, I simply fail to see the point, a nice simple clear and all in one place SP_CheckOut stored procedure is where I'd be going.
I would normally advise to use a trigger but stock manipulation is that kind of operation that's usually done a lot of times, sometimes on batches and this is not the best scenario for triggers to be honest.
I think PKG's idea is very good, but you should never forget to add transaction control to it, otherwise you can endup with non-matching stocks:
CREATE PROC dbo.ProcessOrder
#Item int,
#Quantity int
AS
BEGIN
begin transaction my_tran
begin try
--Update order table here
INSERT INTO dbo.Orders(ItemID,Quantity)
VALUES (#ItemID, #Quantity)
--Update Inventory here
UPDATE dbo.Inventory
SET CurrentQuantity = CurrentQuantity - Quantity
WHERE ItemID = #ItemID
commit transaction
end try
begin catch
rollback transaction
--raise error if necessary
end catch
END
you can use the trigger,also use the procedure,and the specific steps on the top,use the procedure need to open the atuo exec feature in mastaer DB.

Resources