Creating an Instead of insert Trigger SQL - sql

I am a DBA with my company. I am trying to create trigger that will check any insert statement for duplicates first then if none allow the original insert. Not even sure this can be done. The insert statements may be written by various users so the statements will never be the same. All I have found so far is the check for duplicates but the insert statement is then hard coded in the trigger. My plan is also to check update as well, but it is not important right now.
Here is my current code.
ALTER TRIGGER [dbo].[BlockDuplicatesOnTable]
ON [dbo].[blockduplicates]
Instead of INSERT, Update
AS
BEGIN
SET NOCOUNT ON;
Declare #ProviderAcctNumber nvarchar(50)
Declare #Referredby int
Declare #Action as char(1)
Declare #Count as int
Set #Action = 'I'
Select #Count = Count(*) from DELETED
IF #Count > 0
Begin
Set #Action = 'D'
Select #Count = count(*) from INSERTED
IF #Count > 0
Set #Action = 'U'
IF #Action = 'I'
Begin
IF not exists (Select 1 from inserted as i
inner join dbo.blockduplicates as b
on i.ProviderAcctNumber = b.ProviderAcctNumber
and i.Referredby = b.Referredby)
Begin
--execute original insert
End
Else
Begin
Print 'Duplicate insert'
Raiserror ('Duplicate Entry for Insert',16,1);
Return
End
End
Else IF #Action = 'U'
Begin
Select #ProviderAcctNumber = ProviderAcctNumber, #Referredby = Referredby from inserted
IF Not exists (Select 1 from deleted where ProviderAcctNumber = #ProviderAcctNumber and Referredby = #Referredby)
Begin
Print 'Update Statement is True';
End
Else
Begin
Print 'duplicate'
Raiserror ('Duplicate Entry for Update',16,1);
Return
End
End
End
End;

Related

How to use results from an SQL Query as a list to delete (WSUS) updates

My problem is that I want to use the results from a SELECT query as the input values for a Stored Procedure. The issue is that the SP will only accept Scalar values, and I do not know SQL and so have been struggling to find a workaround or solution.
I want to modify the following Proc to accept multiple values to be used within the query:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spDeleteUpdateByUpdateID]
#updateID UNIQUEIDENTIFIER
AS
SET NOCOUNT ON
DECLARE #localUpdateID INT
SET #localUpdateID = NULL
SELECT #localUpdateID = LocalUpdateID FROM dbo.tbUpdate WHERE UpdateID = #updateID
IF #localUpdateID IS NULL
BEGIN
RAISERROR('The update could not be found.', 16, 40)
RETURN(1)
END
IF EXISTS (SELECT r.RevisionID FROM dbo.tbRevision r
WHERE r.LocalUpdateID = #localUpdateID
AND (EXISTS (SELECT * FROM dbo.tbBundleDependency WHERE BundledRevisionID = r.RevisionID)
OR EXISTS (SELECT * FROM dbo.tbPrerequisiteDependency WHERE PrerequisiteRevisionID = r.RevisionID)))
BEGIN
RAISERROR('The update cannot be deleted as it is still referenced by other update(s).', 16, 45)
RETURN(1)
END
DECLARE #retcode INT
EXEC #retcode = dbo.spDeleteUpdate #localUpdateID
IF ##ERROR <> 0 OR #retcode <> 0
BEGIN
RAISERROR('spDeleteUpdateByUpdateID got error from spDeleteUpdate', 16, -1)
RETURN(1)
END
RETURN (0)
TLDR: if anyone knows a quick way for me to use the results from SELECT UpdateID FROM tbUpdate WHERE UpdateTypeID = 'D2CB599A-FA9F-4AE9-B346-94AD54EE0629' to run exec spDeleteUpdateByUpdateID #updateID= i'd be extremely grateful.
There are some examples online of people using cursors to clean up WSUS. It will be slow but you are presumably only running it once. As mentioned there are other strategies for WSUS cleanup that should probably be investigated first.
DECLARE #var1 INT
DECLARE #msg nvarchar(100)
-- Get obsolete updates into temporary table
-- insert your own ID's here if you wish
CREATE TABLE #results (Col1 INT)
INSERT INTO #results(Col1) EXEC spGetObsoleteUpdatesToCleanup
DECLARE WC Cursor
FOR SELECT Col1 FROM #results
OPEN WC
FETCH NEXT FROM WC INTO #var1
WHILE (##FETCH_STATUS > -1)
BEGIN
SET #msg = 'Deleting' + CONVERT(varchar(10), #var1)
RAISERROR(#msg,0,1) WITH NOWAIT EXEC spDeleteUpdateByUpdateId #var1
FETCH NEXT FROM WC INTO #var1
END
CLOSE WC
DEALLOCATE WC
DROP TABLE #results

Stored procedure structure and concurrency issues

Someone wrote the following stored procedure in SQL Server 2008. I think it's just not one the best ways to achieve what he needed to achieve.
As you can see that we need to update MyTable under some conditions but not under the other. And whenever there is a condition that lets a user update MyTable we don't want multiple users to update MyTable concurrently. The thing is the way this procedure is written all the variables are set using select statements and then Begin Transaction starts.
But given the structure of this procedure is it better to execute an sp_getapplock procedure to allow just one user to do the update (that might work well with this procedure given its structure but I read all over that sp_getapplock can lead to deadlocks) and the other options is to do a serializable READ_COMMITED.
But in case of serializable wouldn't this stored procedure allow one of the concurrent user (out of say 2) still select the old values of col1, col2, col3 and col4 and populate variables like #onGoingSession, #timeDiff etc with those old values when the other done updating and released the row (through serializable).Since after the transaction completion it's the turn of second user. And I do wanna block other users from reading the row that is being modified.
Shouldn't we move the begin transaction up right after declaring the variables and the do a serializable after it. So that no concurrent users should read old values and populate the variables while one of the users (apparently who already acquired the row lock) is updating the locked row but hasn't committed as yet.
USE [myDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[MyTableProc]
(#UserId char(20))
AS
BEGIN
DECLARE #error varchar(200)
DECLARE #warning varchar(300)
DECLARE #timeDiff smallint
DECLARE #onGoingSession bit = 1
DECLARE #userName char(20)
DECLARE #counterOfResync tinyint
SET #counterOfResync = (SELECT [Col1] FROM MyTable)
SET #userName = (SELECT [Col2] FROM MyTable)
SET #onGoingSession = (SELECT [Col3] FROM MyTable)
SET #timeDiff = (SELECT DATEDIFF(MINUTE, Col4, CURRENT_TIMESTAMP) from MyTable)
BEGIN TRANSACTION
IF(#onGoingSession = 1)
BEGIN
IF(#timeDiff >= 360)
BEGIN
UPDATE MyTable
SET UserId = #UserId, Col4 = CURRENT_TIMESTAMP
IF(##ERROR = 0)
BEGIN
SET #warning = RTRIM('An unfinsihed session for ' +
LTRIM(RTRIM(#userName)) + ' is going on for the past ' +
LTRIM(RTRIM(#timeDiff)) + ' minutes but updates from ' + LTRIM(RTRIM(#UserId)) + ' are successful')
COMMIT
RAISERROR(#warning,7, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN ##ERROR
END
END
ELSE
BEGIN
SET #error = RTRIM('A session of updates for '+ LTRIM(RTRIM(#userName))+ ' is already in progress concurrent updates are not allowed')
IF(##ERROR = 0)
BEGIN
COMMIT
RAISERROR(#error, 8, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN ##ERROR
END
END
END
ELSE IF(#onGoingSession = 0 AND #counterOfResync = 0)
BEGIN
UPDATE MyTable
SET UserId = #UserId, Col3 = 1, Col4 = CURRENT_TIMESTAMP
IF(##ERROR =0)
BEGIN
COMMIT
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN ##ERROR
END
END
ELSE IF(#onGoingSession = 0 AND #counterOfResync > 0 AND #timeDiff >= 5)
BEGIN
UPDATE MyTable
SET Col3 = 1, CountOfResync = 0, UserId = #UserId, Col4 =
CURRENT_TIMESTAMP
IF(##ERROR = 0)
BEGIN
COMMIT
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN ##ERROR
END
END
ELSE
BEGIN
SET #error = RTRIM('A server resync session already in progress, updates can''t be made at the same time')
IF(##ERROR = 0)
BEGIN
COMMIT
RAISERROR(#error, 9, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN ##ERROR
END
END
RETURN ##ERROR
END
Something like the following would reduce the transaction to a single update statement with the logic to update individual columns handled by case expressions. Post processing handles any errors and generating the curious variety of results expected.
create procedure dbo.MyTableProc( #UserId as Char(20) )
as
begin
declare #CounterOfResync as TinyInt;
declare #Error as Int;
declare #ErrorMessage as VarChar(200);
declare #OngoingSession as Bit = 1;
declare #PriorUsername as Char(20);
declare #TimeDiff as SmallInt;
declare #WarningMessage as VarChar(300);
update MyTable
set
#CounterOfResync = Col1,
#PriorUsername = UserId,
#OngoingSession = Col3,
#TimeDiff = DateDiff( minute, Col4, Current_Timestamp ),
UserId = #UserId,
Col2 = case when Col3 = 0 and Col1 > 0 and DateDiff( minute, Col4, Current_Timestamp ) >= 5 then 1 else Col2 end,
Col3 = case when Col1 = 0 and Col3 = 0 then 1 else Col3 end,
Col4 = Current_Timestamp
set #Error = ##Error;
if ( #CounterOfResync = 1 )
if ( #TimeDiff >= 360 )
begin
if ( #Error = 0 )
begin
set #WarningMessage = 'An unfinished session for user ' + #PriorUsername + ' is going on for the past ' +
Cast( #TimeDiff as VarChar(10) ) + ' minutes but updates from ' + #UserId + ' are successful';
RaIsError( #WarningMessage, 7, 1 );
end
else
return #Error;
end
else
begin
if ( #Error = 0 )
begin
set #ErrorMessage = 'A session of updates for '+ #PriorUsername + ' is already in progress concurrent updates are not allowed';
RaIsError( #ErrorMessage, 8, 1 );
end
else
return #Error;
end
else
if ( #OngoingSession = 0 and #CounterOfResync = 0 )
return #Error
else
-- ...
Apologies for any errors in trying to wade through the existing code and translate it into something stranger. My intent is to provide a (mis)direction that you may choose to follow, not the completed code.

SQL Server stored procedure compile error

Stored procedure is:
CREATE PROCEDURE CountUtily
#domain varchar(50),
#count int,
#totalCount int OUT
AS
BEGIN
SET NOCOUNT ON
SET #totalCount=0
IF (EXISTS (SELECT #totalCount = count
FROM FormFillerAuto2_DomainCount
WHERE domain = #domain))
BEGIN
SET #totalCount = #totalCount + #count
UPDATE FormFillerAuto2_DomainCount
SET count = #totalCount
WHERE domain = #domain
END
ELSE
BEGIN
INSERT INTO FormFillerAuto2_DomainCount (domain, count)
VALUES (#domain, #count)
END
END
Error:
Incorrect syntax near '='. Incorrect syntax near the keyword 'ELSE'.
your Select #totalCount = count does not return a bool. Try setting #totalCount before the if evaluates and eval the count in the if
CREATE PROCEDURE CountUtily
#domain varchar(50),
#count int,
#totalCount int OUT
AS BEGIN
SET NOCOUNT ON
SET #totalCount=0
SELECT #totalCount=count FROM FormFillerAuto2_DomainCount WHERE
IF (#totalCount > 0)
begin
SET #totalCount=#totalCount+#count
UPDATE FormFillerAuto2_DomainCount SET count=#totalCount WHERE domain=#domain
end
ELSE
begin
INSERT INTO FormFillerAuto2_DomainCount (domain, count) VALUES (#domain, #count)
end
end
See EXISTS
Specifies a subquery to test for the existence of rows. it Returns
TRUE if a subquery contains any rows,it accept restricted SELECT statement, The INTO keyword is not allowed.
the problem here is you can't set the value inside the Exists.
Try
alter PROCEDURE CountUtily
#domain varchar(50),
#count int,
#totalCount int OUT
AS BEGIN
SET NOCOUNT ON
SET #totalCount=0;
IF (EXISTS (SELECT [count] FROM FormFillerAuto2_DomainCount WHERE domain=#domain))
begin
SELECT #totalCount=[count] FROM FormFillerAuto2_DomainCount WHERE domain=#domain
UPDATE FormFillerAuto2_DomainCount SET count=#totalCount WHERE domain=#domain
end
ELSE
begin
INSERT INTO FormFillerAuto2_DomainCount (domain, count) VALUES (#domain, #count)
end
end
I believe you are missing ; to mark the end of the statement here. Again, count is a reserve word and so escape it using []. Your posted procedure can be modified to
CREATE PROCEDURE CountUtily(
#domain varchar(50),
#count int,
#totalCount int OUT)
AS BEGIN
SET NOCOUNT ON;
SET #totalCount=0;
SELECT #totalCount=[count] FROM FormFillerAuto2_DomainCount WHERE domain=#domain;
IF (#totalCount IS NOT NULL)
begin
SET #totalCount=#totalCount+#count;
UPDATE FormFillerAuto2_DomainCount SET [count]=#totalCount WHERE domain=#domain;
end
ELSE
INSERT INTO FormFillerAuto2_DomainCount (domain, [count]) VALUES (#domain, #count);
end
You've made this query too complicated with the count and totalcount variables; all of which you do not need.
So, you want to update the "count" field of your "FormFillerAuto2_DomainCount" when the field "domain" matches a parameter #domain; or you want to Insert it if it doesn't exist.
OK, let's do that using ##RowCount.
UPDATE FormFillerAuto2_DomainCount SET [count] = ([count]+#count) where [domain] = #domain
If (##ROWCOUNT > 0)
BEGIN
return 1 --updated : or return whatever you need to show it updated
END
ELSE
BEGIN
INSERT INTO FormFillerAuto2_DomainCount ([domain], [count]) VALUES (#domain, #count)
return 2 --inserted : or return whatever you need to show it inserted
END
It seems like your update is a bit screw up.. It should be
UPDATE TABLENAME
SET COLUMNNAME = VALUE
WHERE CONDITION
Readability will help understand and maintain code.

stored procedure with if else sql server 2008

I have 3 tables, I have to check if grandparent table id has records in grandchildren table. If yes, return yes, else return no. Here is my stored procedure. I got an incorrect syntax error. I am new to stored procedure. Please help me.
CREATE PROCEDURE P_Check
#PKG_ID INT,
#S_ID INT,
#FLAG VCHAR(10) OUT
DECLARE IDS CURSOR LOCAL FOR SELECT S_ID FROM T1 WHERE P_ID = #PKG_ID
OPEN IDS
FETCH NEXT FROM IDS into #S_ID
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM T2 WHERE S_ID = #S_ID
IF ##ROWCOUNT<>0
#FLAG = 'YES'
RETURN
ELSE
#FLAG = 'NO'
FETCH NEXT FROM IDS into #S_ID
END
CLOSE IDS
DEALLOCATE IDS
A few things to check:
I don't think there is a vchar data type in SQL Server unless that is your custom type. So change it to varchar
You forgot the AS
You might want to enclose your logic inside if in between begin and end
Your code that can be compiled:
CREATE PROCEDURE P_Check
#PKG_ID INT,
#S_ID INT,
#FLAG VARCHAR(10) OUT
AS
DECLARE IDS CURSOR LOCAL FOR SELECT S_ID FROM T1 WHERE P_ID = #PKG_ID
OPEN IDS
FETCH NEXT FROM IDS into #S_ID
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM T2 WHERE S_ID = #S_ID
IF ##ROWCOUNT<>0
BEGIN
SET #FLAG = 'YES'
RETURN
END
ELSE
BEGIN
SET #FLAG = 'NO'
FETCH NEXT FROM IDS into #S_ID
END
END
CLOSE IDS
DEALLOCATE IDS
However, I think your cursor will not be closed as you are returning here IF ##ROWCOUNT<>0. I think what you should do is change this:
IF ##ROWCOUNT<>0
BEGIN
SET #FLAG = 'YES'
RETURN
END
to this:
IF ##ROWCOUNT<>0
BEGIN
SET #FLAG = 'YES'
GOTO ON_EXIT
END
then end your procedure like this:
ON_EXIT:
CLOSE IDS
DEALLOCATE IDS
Then to answer your question in the comment, you are "returning" it already in a sense. You can call and test your procedure like this:
declare #result varchar(10)
exec P_Check 1, 1, #result out
print #result
This is just way too complicated and using a cursor here is totally not needed and totally unnecessary.
Simplify your procedure to be:
CREATE PROCEDURE P_Check
#PKG_ID INT,
#S_ID INT,
#FLAG CHAR(1) OUT
AS BEGIN
IF EXISTS (SELECT * FROM T2
INNER JOIN T1 ON T2.S_ID = T1.S_ID WHERE P_ID = #PKG_ID)
SET #FLAG = 'Y'
ELSE
SET #FLAG = 'N'
END
When working seriously with SQL Server, you need to get away from the procedural row-by-agonizing-row thinking using cursors and loops, and you need to start thinking in sets to be efficient and productive.

Yet another primary key exception

Last year I ask this question: What could be causing the primary key exception?
But I still have another error in this stored procedure: randomly I get
INSERT EXCEPTION WITH FOREIGN KEY "FK_Sessions" WITH TABLE
"Sessions", column "id".
CREATE PROCEDURE [dbo].[MyProcedure]
#sessionId varchar(512),
#variable varchar(350),
#value image
AS
BEGIN
BEGIN TRAN
DECLARE #result int = 0;
DECLARE #locked bit;
IF (SELECT COUNT(*) FROM Sessions WITH(ROWLOCK,HOLDLOCK) WHERE id = #sessionId) = 0
BEGIN
SET #result = -1;
END
ELSE BEGIN
DELETE Variables WITH(ROWLOCK,HOLDLOCK) WHERE sessionId = #sessionId AND variable = #variable
IF #value IS NOT NULL
BEGIN
INSERT Variables VALUES(#sessionId, #variable, #value, 0)
END
END
COMMIT TRAN
RETURN #result
END
Any ideas? Thanks again