sql server trigger updates only one row - sql-server

I have a trigger after update an I want to update not only one row by one update, but also more rows. But when I try to update more rows, error Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. appears. Is there a way to update more rows?
ALTER TRIGGER ChangeActorWhileUpdate
ON Herec
AFTER UPDATE
AS
BEGIN
BEGIN TRANSACTION
BEGIN TRY
DECLARE #od DATE;
DECLARE #idHerce INT;
SELECT #idHerce = (SELECT id_herce FROM DELETED);
IF EXISTS(SELECT COUNT(*) FROM historie_hercu WHERE Herec_id_herce = #idHerce)
BEGIN
SELECT #od = (SELECT MAX(do) FROM historie_hercu WHERE Herec_id_herce=#idHerce)
END
IF(SELECT COUNT(*) FROM historie_hercu WHERE Herec_id_herce = #idHerce) = 0
BEGIN
SELECT #od = (SELECT datum_narozeni FROM DELETED);
END
INSERT INTO historie_hercu (jmeno_herce, prijmeni_herce, datum_narozeni, datum_umrti, mesto_narozeni, mesto_umrti, zeme, od, do, Herec_id_herce)
SELECT jmeno_herce, prijmeni_herce, datum_narozeni, datum_umrti, mesto_narozeni, mesto_umrti, zeme, #od, CAST(GETDATE() AS DATE), #idHerce FROM DELETED;
COMMIT;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
ROLLBACK;
END CATCH
END

Triggers are fired once for the batch of rows affected by the triggering action, they are not fired once for each row. You need to write your code in a way that it can handle more than 1 row.
Try something like.....
ALTER TRIGGER ChangeActorWhileUpdate
ON Herec
AFTER UPDATE
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
WITH X AS (
SELECT d.* , h.do AS H_Do
,ROW_NUMBER() OVER (PARTITION BY h.Herec_id_herce ORDER BY do DESC) rn
FROM deleted d
LEFT JOIN historie_hercu h ON d.id_herce = h.id_herce
)
INSERT INTO historie_hercu
(jmeno_herce, prijmeni_herce, datum_narozeni, datum_umrti
, mesto_narozeni, mesto_umrti, zeme, od, do, Herec_id_herce)
SELECT x.jmeno_herce, x.prijmeni_herce, x.datum_narozeni, x.datum_umrti
, x.mesto_narozeni , x.mesto_umrti, x.zeme
, ISNULL(x.H_Do ,x.datum_narozeni)
, CAST(GETDATE() AS DATE), x.id_herce
FROM x
WHERE x.rn = 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
ROLLBACK TRANSACTION;
END CATCH
END

Related

SQL Server trigger compare inserted event date to existings records date

I have a SQL Server trigger I am working on and can't seem to get the inserted date to compare against a date column I have setup in another table. Here is the trigger I am working on:
CREATE TRIGGER TRIGNAME
ON TABLE
FOR INSERT
AS
BEGIN
DECLARE #REC INT
SELECT #REC = COUNT(*)
FROM INSERTED I
WHERE I.ID IS NOT NULL
AND I.COL2 = 'TEST'
IF (#REC > 0)
BEGIN
SELECT #REC = COUNT(*)
FROM INSERTED I
WHERE I.DATE > (SELECT DISTINCT S.DATE FROM TABLE1 S
WHERE I.PARENT_ID = S.PARENT_ID)
IF (#REC > 0)
BEGIN
UPDATE STATEMENT HERE
END
END
Not sure where I am going wrong here but the date column from Table1 is the same for the group of records with the same parent id.
Try declaring a DATETIME variable to store the Table1.Date value.
Then do the compare. And it might be just me but I like to use the inserted table by all lowercase letters.
CREATE TRIGGER TRIGNAME
ON TABLE
FOR INSERT
AS
BEGIN
DECLARE #REC INT
DECLARE #MyDate DateTime
SET #MyDate = (SELECT DISTINCT S.DATE FROM TABLE1 S
INNER JOIN inserted ON inserted.PARENT_ID = S.PARENT_ID)
SELECT #REC = COUNT(*)
FROM INSERTED I
WHERE I.ID IS NOT NULL
AND I.COL2 = 'TEST'
IF (#REC > 0)
BEGIN
SELECT #REC = COUNT(*)
FROM INSERTED I
WHERE I.DATE > #MyDate
IF (#REC > 0)
BEGIN
UPDATE STATEMENT HERE
END
END

Increment of counter variable in SQL Transaction before commit

I am using SQL Transaction and inside transaction I need to insert multiple record in one go.
But problem is that my Id is not identity. So, I need to get max id and insert it.
But when I get the max and increment +1. It is always same.
Following is the my code.
BEGIN
DECLARE #maxId INT;
SET #maxId = (SELECT MAX(id) FROM [dbo].[tempUser]);
BEGIN TRAN
BEGIN TRY
INSERT INTO [dbo].[tempUser] (id, principal, first_name, last_name, email, isActive)
SELECT #maxId+1, A.User_Id, A.FirstName,A.LastName, 'ab#gmail.com', 1 FROM ADUser_Table A
LEFT JOIN [dbo].[tempUser] B on A.User_Id =B.principal where B.principal Is NULL
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
END
How can I insert max id +1 with every record in sql transaction?
Instead of #maxid+1 you can add the #maxId with the row_number() as below.
SELECT #maxId+ (row_number()over(order by A.User_Id)) , A.User_Id,..
from [yourtable]
Use Row_Number while inserting as next:-
BEGIN
DECLARE #maxId INT;
SET #maxId = (SELECT MAX(id) + 1 FROM [dbo].[tempUser]);
BEGIN TRAN
BEGIN TRY
INSERT INTO [dbo].[tempUser] (id, principal, first_name, last_name, email, isActive)
SELECT row_number() over(order by A.User_Id) + #i , A.User_Id, A.FirstName,A.LastName, 'ab#gmail.com', 1 FROM ADUser_Table A
LEFT JOIN [dbo].[tempUser] B on A.User_Id =B.principal where B.principal Is NULL
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
END

how to call function inside a trigger?

what is the problem with the #temp variable?
create function dbo.getNumOfReviews2 (#email varchar(40))
returns int
as begin
declare #numOfReviews int
select #numOfReviews = count(*)
from dbo.Reviews
where email = #email
group by Email
return #numOfReviews
end
CREATE TRIGGER setDiscount
ON dbo.[Contains]
FOR INSERT
AS
DECLARE #OrderID int
DECLARE #ProductID int
DECLARE #Size VarChar(15)
DECLARE #temp int
IF CURSOR_STATUS('global','C_CURSOR')>=-1
BEGIN
DEALLOCATE C_CURSOR
END
DECLARE C_CURSOR CURSOR
FOR SELECT ProductID,OrderID,Size
FROM INSERTED
BEGIN
OPEN C_CURSOR
FETCH NEXT FROM C_CURSOR INTO #ProductID,#OrderID,#Size
WHILE (##FETCH_STATUS=0)
BEGIN
#temp = dbo.getNumOfReviews2(select BillingEmail from dbo.Orders where OrderID=#OrderID)
IF (SELECT COUNT(*)
FROM dbo.[Contains]
WHERE OrderID = #OrderID) > 5 or (SELECT sum(Quantity) FROM dbo.[Contains] WHERE OrderID=#OrderID) > 10 or
( #temp )> 5
UPDATE [Contains]
SET [Savings%] = [Savings%] + 0.05
WHERE OrderID = #OrderID and ProductID = #ProductID and Size = #Size
FETCH NEXT FROM C_CURSOR INTO #ProductID,#OrderID,#Size
END
END
Use select to call scalar function
correct way to do this would be
select #temp = dbo.getNumOfReviews2(BillingEmail)
from dbo.Orders
where OrderID=#OrderID
Note: It is not advisable to write big logic inside a trigger. Triggers should be simple and fast otherwise your DML operations will be slow. Moreover you have used a CURSOR which should be avoided at any cost. Rewrite the code using SET based approach.
Here is a SET based approach code
;WITH cte
AS (SELECT c1.orderid
FROM dbo.[contains] c1
INNER JOIN inserted i1
ON i1.orderid = c1.orderid
GROUP BY orderid
HAVING Count(*) > 5
OR Sum(quantity) > 5
OR #temp > 5)
UPDATE C
SET [savings%] = [savings%] + 0.05
FROM [contains] C
INNER JOIN inserted I
ON I.orderid = C.orderid
AND I.productid = C.productid
AND I.size = C.size
AND EXISTS (SELECT 1
FROM cte c1
WHERE c1.orderid = c.orderid)
CREATE OR REPLACE COVID19VMS_VACCINESHOT_T1
BEFORE INSERT OR UPDATE ON VACCINENEXTSHOTDATE
FOR EACH ROW
BEGIN
IF :NEW.VACCINESHOTDATE := 1
:NEW.VACCINEXTSHOTDATE := to_date(VACCINESHOTDATE +28)
END IF;
IF :NEW.VACCINESHOTDATE :=2
:NEW.VACCINENEXTSHOTDATE IS NULL
END IF
END

After Insert Trigger not Working

I'm trying to run an after insert trigger but it does not work, is there any way to see a log of it?
Here is my code
ALTER TRIGGER [dbo].[SUBSID_INSERT] on [dbo].[STUDENT_MASTER] AFTER INSERT AS
BEGIN
SET NOCOUNT ON
DECLARE #numrows int, #hostname char(30), #sysuser char(30);
SELECT #numrows = ##rowcount;
IF #numrows = 0 RETURN;
SELECT #hostname = substring(hostname, 1, 30)
FROM master.dbo.sysprocesses
WHERE spid = ##spid;
SELECT #sysuser = substring(system_user, 1, 30);
INSERT INTO insert statement,
FROM STUDENT_MASTER JOIN INSERTED I ON STUDENT_MASTER.ID_NUM = I.ID_NUM
JOIN NAME_MASTER ON NAME_MASTER.ID_NUM = STUDENT_MASTER.ID_NUM
WHERE
STUDENT_MASTER.id_num not in ( select DISTINCT id_num from subsid_master where subsid_cde LIKE '0%')
END
GO
You are checking ##ROWCOUNT after declaring a variable. This will always show the result of the last statement (in this case 0), so stop doing that. Instead just use:
SELECT #numrows = COUNT(*) FROM inserted;
I see lots of other potential improvements but this is precisely why it currently appears to be "not working."

How to commit inside a CURSOR Loop?

I am trying to see if its possible to perform Update within a cursor loop and this updated data gets reflected during the second iteration in the loop.
DECLARE cur CURSOR
FOR SELECT [Product], [Customer], [Date], [Event] FROM MyTable
WHERE [Event] IS NULL
OPEN cur
FETCH NEXT INTO #Product, #Customer, #Date, #Event
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM MyTable WHERE [Event] = 'No Event' AND [Date] < #DATE
-- Now I update my Event value to 'No Event' for records whose date is less than #Date
UPDATE MyTable SET [Event] = 'No Event' WHERE [Product] = #Product AND [Customer] = #Customer AND [Date] < #DATE
FETCH NEXT INTO #Product, #Customer, #Date, #Event
END
CLOSE cur
DEALLOCATE cur
Assume when the sql executes the Event column is NULL for all records
In the above sql, I am doing a select inside the cursor loop to query MyTable where Event value is 'No Event' but the query returns no value even though I am doing an update in the next line.
So, I am thinking if it is even possible to update a table and the updated data get reflected in the next iteration of the cursor loop.
Thanks for any help,
Javid
Firstly You shouldn't need a cursor here. Something like the following would have the same semantics (from a starting position where all Event are NULL) and be more efficient.
WITH T
AS (SELECT [Event],
RANK() OVER (PARTITION BY [Product], [Customer]
ORDER BY [Date] DESC) AS Rnk
FROM MyTable)
UPDATE T
SET [Event] = 'No Event'
WHERE Rnk > 1
Secondly regarding the question in the title to commit inside a cursor loop is the same as anywhere else. You just need a COMMIT statement. However if you aren't running this inside a larger transaction the UPDATE statement will be auto committed anyway.
Thirdly Your real question doesn't seem to be about commit anyway. It is about the cursor reflecting updates to the data on subsequent iterations. For the case in the question you would need a DYNAMIC cursor
Defines a cursor that reflects all data changes made to the rows in
its result set as you scroll around the cursor. The data values,
order, and membership of the rows can change on each fetch.
Not all queries support dynamic cursors. The code in the question would but without an ORDER BY it is undeterministic what order the rows would be processed in and thus whether you would see visible results. I have added an ORDER BY and an index to support this to allow a dynamic cursor to be used.
If you try the following you will see the cursor only fetches one row as the dates are processed in descending order and when the first row is processed the table is updated such that no more rows qualify for the next fetch. If you comment out the UPDATE inside the cursor loop all three rows are fetched.
CREATE TABLE MyTable
(
[Product] INT,
[Customer] INT,
[Date] DATETIME,
[Event] VARCHAR(10) NULL,
PRIMARY KEY ([Date], [Product], [Customer])
)
INSERT INTO MyTable
VALUES (1,1,'20081201',NULL),
(1,1,'20081202',NULL),
(1,1,'20081203',NULL)
DECLARE #Product INT,
#Customer INT,
#Date DATETIME,
#Event VARCHAR(10)
DECLARE cur CURSOR DYNAMIC TYPE_WARNING FOR
SELECT [Product],
[Customer],
[Date],
[Event]
FROM MyTable
WHERE [Event] IS NULL
ORDER BY [Date] DESC
OPEN cur
FETCH NEXT FROM cur INTO #Product, #Customer, #Date, #Event
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Product,
#Customer,
#Date,
#Event
-- Now I update my Event value to 'No Event' for records whose date is less than #Date
UPDATE MyTable
SET [Event] = 'No Event'
WHERE [Product] = #Product
AND [Customer] = #Customer
AND [Date] < #Date
FETCH NEXT FROM cur INTO #Product, #Customer, #Date, #Event
END
CLOSE cur
DEALLOCATE cur
DROP TABLE MyTable
Even if this worked, this would not guarantee the correct result since you miss an ORDER BY clause in your query.
Depending on this, all records, no records or any random subset of records could be updated.
Could you please explain in plain English what your stored procedure should do?
Use Below template
DECLARE #CCount int = 100
DECLARE #Count int = 0
DECLARE #id AS BigInt
DECLARE cur Cursor fast_forward for
SELECT t1.Id
FROM Table1 t1 WITH (NOLOCK) WHERE <Some where clause>
OPEN cur
Begin Tran
While (1=1)
Begin
Fetch next from cur into #id
If ##Fetch_Status <> 0
break
-- do some DML actions
Delete From Table1 WITH (ROWLOCK) where Id = #id
Set #count = #count + ##Rowcount
if (#count % #CCount = 0)
Begin
if (#count % 100 = 0)
Print 'Table1: DML action ' + Cast(#count as Varchar(15)) + ' rows'
-- for every 100 rows commit tran will trigger , and starts a new one.
While ##Trancount > 0 Commit Tran
Begin Tran
End
End
While ##Trancount > 0 Commit Tran
Close cur
Deallocate cur

Resources