SQL Server Trigger on Specified Columns - c#

Hey,
I am trying to update some columns in my table in SQL Server 2014 and I wrote some code on the trigger and to consequently insert the new values into a new table, using the following code procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Atrin Noori
-- =============================================
ALTER TRIGGER [dbo].[UsersOnUPDATE]
ON [dbo].[Users]
After UPDATE
AS
BEGIN
SET NOCOUNT ON;
--
DECLARE #user_key int
SELECT #user_key = i.User_key FROM inserted i;
--
IF UPDATE (User_fullname)
BEGIN
DECLARE #newfullname nvarchar(MAX);
DECLARE #oldfullname nvarchar(MAX);
--
SELECT #oldfullname = i.User_fullname FROM deleted i;
SELECT #newfullname = i.User_fullname FROM inserted i;
--
INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldname, UL_newname, UL_IsDeleted)
VALUES (#user_key, GETDATE(), #oldfullname, #newfullname, 0)
END
--
ELSE IF UPDATE (User_password)
BEGIN
DECLARE #newpassword nvarchar(10);
DECLARE #oldpassword nvarchar(10);
--
SELECT #oldpassword = i.User_password FROM deleted i;
SELECT #newpassword = i.User_password FROM inserted i;
--
INSERT INTO UsersLogs (UL_user_key_r,UL_date,UL_oldpass,UL_newpass,UL_IsDeleted)
VALUES (#user_key, GETDATE(), #oldpassword, #newpassword, 0)
END
--
ELSE IF UPDATE (User_username)
BEGIN
DECLARE #newusername nvarchar(10);
DECLARE #oldusername nvarchar(10);
--
SELECT #oldusername = i.User_username FROM deleted i;
SELECT #newusername = i.User_username FROM inserted i;
--
INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldusername, UL_newusername, UL_IsDeleted)
VALUES (#user_key, GETDATE(), #oldusername, #newusername, 0)
END
--
ELSE IF UPDATE (User_position_id_r)
BEGIN
DECLARE #newposid tinyint;
DECLARE #oldposid tinyint;
--
SELECT #oldposid = i.User_position_id_r FROM deleted i;
SELECT #newposid = i.User_position_id_r FROM inserted i;
--
INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldPosid, UL_newPosid, UL_IsDeleted)
VALUES (#user_key, GETDATE(), #oldposid, #newposid, 0)
END
END
This trigger works fine when I manually change the values in each column and inserts into a new table called UsersLogs.
However it does not work fine when I use the c# application I am developing to update the values...
CONSIDER:
I am trying to change the password of a user through my application
and using a Stored Procedure.
The Stored Procedure works fine and updates the values, BUT the trigger of UserOnUpdate inserts the old and new value of User_Fullnme (not oldpassword and newpassword) into the new table (UsersLgos) and set others to null (which is OK to be null).
I mean only this part of code will run:
IF UPDATE (User_fullname)
BEGIN
DECLARE #newfullname nvarchar(MAX);
DECLARE #oldfullname nvarchar(MAX);
--
SELECT #oldfullname = i.User_fullname FROM deleted i;
SELECT #newfullname = i.User_fullname FROM inserted i;
--
INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldname, UL_newname, UL_IsDeleted)
VALUES (#user_key, GETDATE(), #oldfullname, #newfullname, 0)
END
And by the things I just said... I change the password and SET a condition to check for the updated column.
But the question is why the trigger cannot realize which column was updated through the application ?
NOTE:
I say it again: IT WORKS FINE WITH THE MANUAL CHANGES AND UPDATES

The UPDATE() function only tells you if a column was present in the UPDATE statement, not if the value actually changed. Furthermore, te inserted and deleted tables may contain multiple rows, or none.
So, your trigger should really look like this:
ALTER TRIGGER [dbo].[UsersOnUPDATE]
ON [dbo].[Users]
After UPDATE
AS
SET NOCOUNT ON;
IF NOT (UPDATE(User_fullname) OR UPDATE(User_password) OR UPDATE(User_username) OR UPDATE(User_position_id_r))
RETURN; -- this only tells you if the column was present
INSERT INTO UsersLogs
(UL_user_key_r, UL_date, UL_IsDeleted,
UL_oldname, UL_newname,
UL_oldpass, UL_newpass,
UL_oldusername, UL_newusername,
UL_oldPosid, UL_newPosid)
SELECT
i.user_key, GETDATE(), 0,
d.fullname, i.fullname,
d.User_password, i.User_password,
d.User_username, i.User_username,
d.User_position_id_r, i.User_position_id_r
FROM inserted i
JOIN deleted d ON i.user_key = d.user_key
WHERE EXISTS ( -- this checks for any differences
SELECT i.fullname, i.User_password, i.User_username, i.User_position_id_r
EXCEPT -- this will deal with nulls correctly
SELECT d.fullname, d.User_password, d.User_username, d.User_position_id_r
);
In case of only some columns being updated, this does leave you with the other columns having the same before and after values. But some CASE expressions should sort that out.
ALTER TRIGGER [dbo].[UsersOnUPDATE]
ON [dbo].[Users]
After UPDATE
AS
SET NOCOUNT ON;
IF NOT (UPDATE(User_fullname) OR UPDATE(User_password) OR UPDATE(User_username) OR UPDATE(User_position_id_r))
RETURN; -- this only tells you if the column was present
INSERT INTO UsersLogs
(UL_user_key_r, UL_date, UL_IsDeleted,
UL_oldname, UL_newname,
UL_oldpass, UL_newpass,
UL_oldusername, UL_newusername,
UL_oldPosid, UL_newPosid)
SELECT
i.user_key, GETDATE(), 0,
CASE WHEN d.fullname <> i.fullname THEN d.fullname END, CASE WHEN d.fullname <> i.fullname THEN i.fullname END
CASE WHEN d.User_password <> i.User_password THEN d.User_password END, CASE WHEN d.User_password <> i.User_password THEN i.User_password END,
CASE WHEN d.User_username <> i.User_username THEN d.User_username END, CASE WHEN d.User_username <> i.User_username THEN i.User_username END,
CASE WHEN d.User_position_id_r <> i.User_position_id_r THEN d.User_position_id_r END, CASE WHEN d.User_position_id_r <> i.User_position_id_r THEN i.User_position_id_r END
FROM inserted i
JOIN deleted d ON i.user_key = d.user_key
WHERE EXISTS ( -- this checks for any differences
SELECT i.fullname, i.User_password, i.User_username, i.User_position_id_r
EXCEPT -- this will deal with nulls correctly
SELECT d.fullname, d.User_password, d.User_username, d.User_position_id_r
);
If you want separate rows for every value changed, you could unpivot with a CROSS APPLY

update() trigger function , returns true if the column is updated even if with the same value.
so seems like when you update password, you are updating User_fullname column and other columns probably (with the same value as before of course) . so UPDATE (User_fullname) returns true .
But, also the way you have've written your code , the trigger works for the situation only one column is updated and in that order in your code , for example if UPDATE (User_fullname) is true , your code doesn't check other conditions , because of ELSE IF. you might wanna remove else and check for each column , or totally change your strategy to log data inside trigger.
then based on your comment , get rid of all if else and have one single insert statement like so for all columns:
INSERT INTO UsersLogs (UL_user_key_r,UL_date,UL_oldpass,UL_newpass,<all columns>)
select (#user_key, GETDATE(), case when deleted.password <> new.password then deleted.password else null end , case when deleted.password <> new.password then inserted.password else null end , ....)
from inserted i
join deleted d on i.userkey = d.userkey

Related

Duplicate Entry Insertion while insertion of DataTable in Stored Procedure

I'm inserting DataTable in Database using StoredProcedure but the issue is, its inserting twice the actual number of entries of DataTable to be inserted, the procedure is below, kindly guide me, if I'm using wrong approach, why its duplicating the rows? The return which is required is working fine.
Thanks In Advance
ALTER PROCEDURE [dbo].[proc_InsertStore_Recvry]
(#dt_Recovery Recovery_Store READONLY)
AS
Declare #RecoveryIDs as Table (IDs int, ClientIds int)
declare #StoreID int
declare #ClientID int
declare #Arrears decimal(18, 2)
declare #NetDues decimal(18, 2)
declare #Received decimal(18, 2)
Declare #RecoveryRecID int
begin
select * into #tempTable from #dt_Recovery
declare #Count int
set #Count= (select COUNT(*) from #tempTable)
while(#Count > 0)
begin
set #Count = #Count-1
set #ClientID = (Select top 1 ClientID from #tempTable)
set #StoredID = (Select top 1 StoredID from #tempTable where ClientID=#ClientID)
set #Arrears = (Select top 1 Arrears from #tempTable where ClientID=#ClientID)
set #NetDues = (Select top 1 NDues from #tempTable where ClientID=#ClientID)
set #Received = (Select top 1 Received from #tempTable where ClientID=#ClientID)
Insert into tblRecovery (StoreID, ClientID, Arrears, NetDues, Received)
values (#StoreID,#ClientID,#Arrears,#NetDues,#Received)
select #RecoveryID = Scope_Identity()
insert into #RecoveryIDs (IDs,ClientIds) values (#RecoveryID, #ClientID )
delete from #tempTable where ClientID=#ClientID
end
Select * from #RecoveryIDs
it looks like you are using SQL Server. If yes then why are you using a while-loop to insert values into a table and return the inserted Ids?
The same can be accomplished in a far better way via the OUTPUT clause:
OUTPUT documentation
Example:
INSERT INTO tblRecovery(StoreID, ClientID, Arrears, NetDues, Received) OUTPUT INSERTED.ID, INSERTED.CLientId INTO #RecoveryIDs(IDs, ClientIds) SELECT StoredID, ClientID, Arrears, NDues, Received FROM #tempTable
Aside from that there seems to be no issue with your SQL code. So could you post the .NET code as well?

Why I get exception when I call stored procedure?

I am using Entity Framework 6 and Microsoft SQL Server 2012.
Here is my stored procedure:
ALTER PROCEDURE SPDeleteRegion
#siteId int,
#regionId int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #isDeleted BIT
IF EXISTS (SELECT 1 FROM SiteObjects WHERE SiteRegionId = #regionId)
BEGIN
SET #isDeleted = 0 ; --not deleted
RETURN #isDeleted;
END
ELSE
BEGIN
--do what needs to be done if not
DELETE FROM SiteRegions
WHERE Id = #regionId;
SET #isDeleted = 1; -- deleted
RETURN #isDeleted;
END
END
Here how I call the stored procedure in C#:
var t = _context.Database.SqlQuery<bool>("SPDeleteRegion #siteId, #regionId",
new SqlParameter("#siteId", siteId),
new SqlParameter("#regionId", regionId));
On the line of code above I get this exception:
The data reader has more than one field. Multiple fields are not valid for EDM primitive or enumeration types.
Any idea why I get the excewption and how to fix it?
Your procedure doesn't selecting anything. Change it like this:
ALTER PROCEDURE SPDeleteRegion
-- Add the parameters for the stored procedure here
#siteId int,
#regionId int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE
#isDeleted BIT
IF EXISTS (SELECT 1 FROM SiteObjects WHERE SiteRegionId = #regionId)
BEGIN
SET #isDeleted = 0 ; --not deleted
SELECT #isDeleted [IsDeleted]; --HERE
END
ELSE
BEGIN
--do what needs to be done if not
DELETE FROM SiteRegions WHERE Id = #regionId;
SET #isDeleted = 1;--deleted
SELECT #isDeleted [IsDeleted]; -- AND HERE
END
END

SqlBulkCopy alternative on SQL Server

I'm developing this Stored Procedure in SQL Server 2012:
CREATE PROCEDURE [dbo].[UploadCodes]
#param1 nvarchar(20),
#param2 nvarchar(20),
#param3 nvarchar(20),
#param4 nvarchar(20),
#param5 nvarchar(20),
#newCodes as dbo.CodeList READONLY
AS
declare #code nvarchar(20),
#codeLevel tinyint,
#headerId int,
#productId int;
set nocount on;
Begin transaction
-- Insert china header file.
Insert into CODES_HEADER
values (#param1, #param2, #param3, #param4, #param5);
-- If an error, end here.
If (##ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
-- Get header id from latest insert.
set #headerId = (select SCOPE_IDENTITY());
-- Get product's id.
set #productId = (select Id from PRODUCTS where PRODUCT_CODE = #param3)
-- If this product doesn't exist on database, insert it.
if (#productId is null)
begin
Insert into PRODUCTS values (#param3);
-- If an error, end here.
If (##ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
set #productId = (select SCOPE_IDENTITY());
end
set #codeLevel = (select CAST(#param1 as tinyint));
Create table #tempCodes (
Code nvarchar(20))
insert into #tempCodes (Code) select CODE from #newCodes;
set rowcount 1
select #code = Code from #tempCodes
-- Loop all child codes to check if they have a parent.
while ##rowcount <> 0
begin
set rowcount 0
select * from #tempCodes where Code = #code
delete #tempCodes where Code = #code
insert into EXTERNAL_CODES(CODE, CODE_LEVEL, CODES_HEADER_ID, PRODUCT_ID)
values (#code, #codeLevel, #headerId, #productId);
-- If an error, end here.
If (##ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
-- Get next code.
set rowcount 1
select #code = Code from #tempCodes
end
set rowcount 0
Commit transaction
return 0
This is the definition of dbo.CodeList type:
CREATE TYPE [dbo].[CodeList]
AS TABLE
(
CODE nvarchar(20)
);
My problem is with its loop, it gets too long when there are a lot of codes.
Is there another way to run it faster?
On C# I use SqlBulkCopy but I don't know if there is something similar on SQL. I have found Bulk Insert but it uses a file.
You should be able to rewrite the entire loop into a single insert statement.
This:
set rowcount 1
select #code = Code from #tempCodes
-- Loop all child codes to check if they have a parent.
while ##rowcount <> 0
begin
set rowcount 0
select * from #tempCodes where Code = #code
delete #tempCodes where Code = #code
insert into EXTERNAL_CODES(CODE, CODE_LEVEL, CODES_HEADER_ID, PRODUCT_ID)
values (#code, #codeLevel, #headerId, #productId);
-- If an error, end here.
If (##ERROR != 0)
Begin
rollback transaction
return -1 -- Database error
End
-- Get next code.
set rowcount 1
select #code = Code from #tempCodes
end
set rowcount 0
Does this:
Pick out a single code from #tempCodes into #code
Delete that code from #tempCodes
Insert one row into EXTERNAL_CODES, using that #code + other variables
Grab the next and go back to step #2.
You can rewrite that entire loop into this query (basically everything I pasted above):
INSERT INTO EXTERNAL_CODES (CODE, CODE_LEVEL, CODES_HEADER_ID, PRODUCT_ID)
SELECT Code, #codeLevel, #headerId, #productID FROM #tempCodes
If you also need to clear out #tempCodes (which I doubt), you would also issue this statement:
DELETE #tempCodes
If you also need to rollback other changes if the above fails you would add that single if-statement with a rollback in here as well

How to Return Database Result based on CASE statements?

I have an application right now that has special user roles hardwired into the executable. It is tamper proof, but is a bit of a mess when it comes to new hires, role changes, etc.
So, I want to create a stored procedure that can return the appropriate employee badge numbers for any given operation.
My expertise is in C# development, but I am also the guy who works on the SQL Server (2000) database.
Here is what I'm starting out with, but T-SQL does not like this at all!
CREATE PROCEDURE sp1_GetApprovalBadges(#operation varchar(50)) as
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
declare #op varchar(50);
declare #num varchar(50);
declare #table table (NUM varchar(50) null);
select #op=upper(#operation);
case
when #op='CLERK' then
insert into #table (NUM) values (#num) where #num in ('000988','001508','003790','007912') end
when #op='HRMANAGER' then
insert into #table (NUM) values (#num) where #num in ('003035') end
when #op='HUMANRESOURCES' then
insert into #table (NUM) values (#num) where #num in ('002864','005491') end
when #op='INFORMATIONTECHNOLOGY' then
insert into #table (NUM) values (#num) where #num in ('001258','003423','007135','007546') end
end;
SELECT NUM from #table order by NUM;
END
GO
I realize this is very much like code that I write and is not database related, but having the database there affords me a great way to store and execute some scripts that I can modify as needed to keep my application working.
I see at least two issues
Replace case with if elses
execute table variables using dynamic sql
for example
if #op='CLERK'
begin
exec 'insert into ' + #table + '(NUM) values (' + #num + ') where' + #num + 'in (''000988'',''001508',''003790',''007912'')'
end
else if #op='HRMANAGER'
begin
i-- see above
end
else if #op='HUMANRESOURCES'
begin
-- see above
end
else if #op='INFORMATIONTECHNOLOGY'
begin
-- see above
end
exec 'SELECT NUM from' + #table + 'order by NUM;'
Syntax may not be exact, but the idea will work
CASE is an EXPRESSION that returns a single value. You can't use it for control-of-flow logic like you're attempting. Based on the conditions (hard-coded sets of strings) you probably meant something like this (quietly glazing over several other problems with the syntax you've attempted):
...
SELECT #op=upper(#operation);
IF #op = 'CLERK'
BEGIN
INSERT #table (NUM)
SELECT '000988'
UNION ALL SELECT '001508'
UNION ALL SELECT '003790'
UNION ALL SELECT '007912';
END
IF #op = 'HRMANAGER'
BEGIN
INSERT #table (NUM)
SELECT '003035';
END
IF #op = 'HUMANRESOURCES'
BEGIN
INSERT #table (NUM)
SELECT '002864'
UNION ALL SELECT '005491';
END
IF #op = 'INFORMATIONTECHNOLOGY'
BEGIN
INSERT #table (NUM)
SELECT '001258'
UNION ALL SELECT '003423'
UNION ALL SELECT '007135'
UNION ALL SELECT '007546'
END
SELECT NUM from #table order by NUM;
...

Database triggers that are being used to log history seem to be causing deadlock exceptions?

I am getting Dead Lock exceptions in my C# code when I call my stored procedure to update/insert into my Configuration table.
The SP_Update_Configuration stored procedure will either insert a new record or update an existing record.
The Triggers are setup to keep a history of previous records in a history table. If the Configuration table has an update or an insert, then it should add that record to the Configuration_History table.
I believe the triggers are causing the deadlock? I did not have any problems previous to adding triggers.... Any Ideas?
I am using SQL Server 2012 Express.
Here is an example of my SQL:
CREATE PROCEDURE SP_Update_Configuration
(
--Input variables
)
AS
BEGIN TRANSACTION
DECLARE #RetCode INT
DECLARE #RowCnt INT
--Standard Update Logic
SELECT #RowCnt = ##ROWCOUNT
IF ##ERROR <> 0
BEGIN
ROLLBACK TRAN
SET #RetCode = 5
RETURN #RetCode
END
IF #RowCnt = 0
BEGIN
--Standard Insert Logic
END
IF ##ERROR <> 0
BEGIN
ROLLBACK TRAN
SET #RetCode = 5
RETURN #RetCode
END
COMMIT TRANSACTION
GO
create trigger dbo.Configuration_Log_Insert
on dbo.Configuration
for insert
as
set nocount on
insert into Configuration_History
select *
from Configuration
go
exec sp_settriggerorder #triggername = 'Configuration_Log_Insert', #order = 'last', #stmttype = 'insert'
create trigger dbo.Configuration_Log_Update
on dbo.Configuration
for update
as
set nocount on
insert into Configuration_History
select *
from Configuration
go
exec sp_settriggerorder #triggername = 'Configuration_Log_Update', #order = 'last', #stmttype = 'update'
SELECT #RowCnt = ##ROWCOUNT
IF ##ERROR <> 0
here you have trouble, because ##ERROR is error code of
SELECT #RowCnt = ##ROWCOUNT
You can do this as:
SELECT #RowCnt = ##ROWCOUNT, #error = ##ERROR
IF #error <> 0
In triggers you have
insert into Configuration_History
select *
from Configuration
but is must be
insert into Configuration_History
select *
from inserted

Categories

Resources