I'm not sure that's the correct way making an update followed by insert in a stored procedure.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[io_sp_admin_add_emp]
#id BIGINT,
#lastName VARCHAR(20),
#firstName VARCHAR(20)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION [TranAddEmp]
DECLARE #identity BIGINT = 0
INSERT INTO empTable(LastName, FirstName, hash_id)
VALUES (#lastName, #firstName,
HashBytes('SHA2_256', CAST(#id AS VARBINARY(50))))
SELECT #identity = ##identity
UPDATE empTable
SET rowId = incId -- both are columns in empTable
WHERE hash_id = #identity
COMMIT TRANSACTION [TranAddEmp]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [TranAddEmp]
END CATCH
END
A simple change to your current code can give you what you're looking for.
Instead of messing around with ##Identity, which is almost never the right thing to do, you compute the hash of the #Id value once, store it in a local variable, and use it for both the insert statement and the where clause of the update statement - That is, assuming the HashId column is unique.
That being said, I'm not sure why you need the rowId column as well as the incId column - unless one of them is designed to change it's value through an update statement in the lifetime of the row - you are simply keeping redundant data.
Here's an improved version of your stored procedure:
CRETAE PROCEDURE [dbo].[io_sp_admin_add_emp]
#id BIGINT,
#lastName varchar(20),
#firstName varchar(20)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION [TranAddEmp]
-- Compute the hash once, store in a local variable
DECLARE #HashId varbinary(8000) = HashBytes('SHA2_256', cast(#id as varbinary(50)))
INSERT INTO empTable(
LastName,
FirstName,
hash_id
)
VALUES(
#lastName,
#firstName,
#HashId
)
UPDATE empTable
SET rowId = incId
WHERE hash_id = #HashId
COMMIT TRANSACTION [TranAddEmp]
END TRY
BEGIN CATCH
-- make sure transaction has started and is not commited
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION [TranAddEmp]
END CATCH
END
There is a great keyword OUTPUT. As MSDN says:
Returns information from, or expressions based on, each row affected
by an INSERT, UPDATE, DELETE, or MERGE statement. These results can be
returned to the processing application for use in such things as
confirmation messages, archiving, and other such application
requirements. The results can also be inserted into a table or table
variable. Additionally, you can capture the results of an OUTPUT
clause in a nested INSERT, UPDATE, DELETE, or MERGE statement, and
insert those results into a target table or view.
You can insert your inserted id's into table through OUTPUT keyword. For example:
DECLARE #InsertedIDs TABLE (ID varbinary(8000))
INSERT INTO empTable(
LastName,
FirstName,
hash_id
)
OUTPUT HashBytes('SHA2_256', cast(INSERTED.ID as varbinary(50))) INTO #InsertedIDs(ID)
VALUES(
#lastName,
#firstName,
HashBytes('SHA2_256', cast(#id as varbinary(50)))
)
UPDATE empTable
Set rowId = incId -- both are columns in empTable
WHERE hash_id in (SELECT ID IN #InsertedIDs)
Related
I have two seperate stored procedures which one is for adding a new employee to DB and the other is for getting an employee from the DB.
I'm using SHA2_256 and it looks like it works good when inserting the data, but when using the same technique for getting the employee, something is not working.
This is the SP for adding the employee.
USE [db11]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[io_sp_admin_add_emp]
#Id BIGINT, #Lname VARCHAR(20), #Fname VARCHAR(15),#Gender TINYINT,#Bday DATETIME,#LoggedInUser VARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DECLARE #HashId varbinary(50) = HashBytes('SHA2_256', cast(#Id as varbinary(50)))
INSERT INTO io_t_employees(
lname,
fname,
gender,
bday,
[user_name],
hash_id
)
VALUES(
LTRIM(RTRIM(#Lname)),
LTRIM(RTRIM(#Fname)),
#Gender,
#Bday,
#loggedInUser,
#HashId,
)
SELECT CAST(1 as BIT) as 'Status', 'Succeeded' as 'ReturnMessage'
END TRY
BEGIN CATCH
END
And Then, I would like to get the User's UniqueId according to the HashId stored earlier.
USE [db11]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[io_sp_admin_emp_helper]
-- Add the parameters for the stored procedure here
#id INT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT UniqueId
FROM io_t_employees
WHERE hash_id = HashBytes('SHA2_256', cast(#id as varbinary(50)))
END
Unfortunately ,the result of the second procedure's query has no data.
I have a feeling I'm doing something wrong in the first procedure (add employee).
Update: This still remain a mystery. Checked the calling code and we did not find anything that would make the SP run in a loop.
For now we have split the SP into two which seems to have arrested the issue although not able to reason how that has helped out.
Database: MS SQL Server.
I have a SP which performs few operations - i.e inserts a row into 3 tables based on certain status as part of that SP being called.
It is getting called from our web application based on a user action.
We have cases, few times a day where the same row gets inserted multiple times (sometime more than 50+) with the same values in each row except that if you look at the datetime when the row was inserted there is a difference of few milliseconds. So it is unlikely that the user is initiating that action.
This SP is not running in a Transaction or with any locks however it is getting called probably concurrently multiple times as we have many concurrent users on the web application invoking this action.
My question is what is causing the same row to insert so many times? If concurrent execution of SP was an issue where we are updating same row then it is understood one may overwrite the other. However in this case each user calls in the SP with different parameters.
I have put the said operation in a Transaction to monitor the behavior however was looking to find out what exactly causes these kind of multiple inserts with same value just a few milliseconds apart?
USE [ABC]
GO
/****** Object: StoredProcedure [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[AddProcessAdmittedDocUploadScrutinyWithLog]
(
--Insert using bulk
#stdfrm_id int,
#course_id int,
#stdfrm_scrt_apprvby int,
#stdfrm_scrt_apprvcomment varchar(max),
#sRemainingDocs varchar(max),
#DTProcessAdmittedDocUploadScrutiny AS dbo.MyDTProcessAdmittedDocUploadScrutiny READONLY
)
AS
BEGIN
DECLARE #result char
SET #result='N'
--New
declare #AuditCount int=0;
select #AuditCount=count(scrtaudit_id) from tbl_ProcessAdmittedScrutinyAuditLog
where stdfrm_id=#stdfrm_id and stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
and stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment and convert(date,stdfrm_scrt_apprvon,103)=convert(date,getdate(),103)
--Checked extra conditon to avoid repeatation
if(#AuditCount=0)
BEGIN
--Call Insert
BEGIN TRY
/*Remaining Documents----------*/
DECLARE #sdtdoc_id Table (n int primary key identity(1,1), id int)
if(#sRemainingDocs is not null)
begin
--INSERT INTO #sdtdoc_id (id) SELECT Name from splitstring(#sRemainingDocs)
INSERT INTO #sdtdoc_id (id) SELECT [Value] from dbo.FN_ListToTable(#sRemainingDocs,',')
end
Declare #isRemaining int=0;
SELECT #isRemaining=Count(*) FROM #sdtdoc_id
/*Calculate stdfrm_scrt_apprvstatus*/
Declare #stdfrm_scrt_apprvstatus char(1)='A';--Approved
Declare #TotalDescripancies int;
select #TotalDescripancies=count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y'
if(#isRemaining>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
else if exists (select count(doc_id) from #DTProcessAdmittedDocUploadScrutiny where doc_id_scrtyn='Y')
begin
if(#TotalDescripancies>0)
begin
set #stdfrm_scrt_apprvstatus='H';--Discrepancies Found
end
end
/* Check if Discrepancies Found first time then assign to Checker o.w assign to direct college like grievance*/
if(#stdfrm_scrt_apprvstatus='H')
begin
declare #countAuditLog int=0;
select #countAuditLog=count(stdfrm_id) from tbl_ProcessAdmittedScrutinyAuditLog where stdfrm_id =#stdfrm_id
if (#countAuditLog=0)
begin
set #stdfrm_scrt_apprvstatus='G'--'E';--Discrepancies Found set Edit request assign to Checker
end
--else if (#countAuditLog=1)
-- begin
--set #stdfrm_scrt_apprvstatus='G';--Discrepancies Found set Grievance assign to college
-- end
end
/*----------------------*/
/*Update status in original table-----*/
Update tbl_ProcessAdmitted set stdfrm_scrt_apprvstatus=#stdfrm_scrt_apprvstatus
,stdfrm_scrt_apprvon=getdate(),stdfrm_scrt_apprvby=#stdfrm_scrt_apprvby
,stdfrm_scrt_apprvcomment=#stdfrm_scrt_apprvcomment
where stdfrm_id =#stdfrm_id
/*Add in Main Student Log-----------*/
/********* The row here gets inserted multiple times *******************/
INSERT into tbl_ProcessAdmittedScrutinyAuditLog
(stdfrm_id, stdfrm_scrt_apprvstatus, stdfrm_scrt_apprvon, stdfrm_scrt_apprvby, stdfrm_scrt_apprvcomment )
values
(#stdfrm_id, #stdfrm_scrt_apprvstatus, getdate(), #stdfrm_scrt_apprvby, #stdfrm_scrt_apprvcomment)
DECLARE #scrtaudit_id int =##identity
/*Completed -------------------------*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutiny WHERE stdfrm_id =#stdfrm_id
SET NOCOUNT ON;
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutiny
(stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Scrutiny Document Log -------------------------*/
/********* The row here gets inserted multiple times *******************/
INSERT tbl_ProcessAdmittedDocUploadScrutinyAuditLog
(scrtaudit_id,stdfrm_id, course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment)
SELECT #scrtaudit_id,#stdfrm_id, #course_id, doc_id, doc_id_scrtyn, doc_id_scrtrmrk, doc_id_comment
FROM #DTProcessAdmittedDocUploadScrutiny;
/*Remaining Documents Insert into table*/
DELETE FROM tbl_ProcessAdmittedDocUploadScrutinyRemiaing WHERE stdfrm_id =#stdfrm_id
DECLARE #Id int,#doc_id int
WHILE (SELECT Count(*) FROM #sdtdoc_id) > 0
BEGIN
Select Top 1 #Id = n,#doc_id=id From #sdtdoc_id
--Do some processing here
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaing(stdfrm_id, doc_id )
values (#stdfrm_id,#doc_id)
insert into tbl_ProcessAdmittedDocUploadScrutinyRemiaingAuditLog
(scrtaudit_id, stdfrm_id, doc_id )
values (#scrtaudit_id,#stdfrm_id,#doc_id)
DELETE FROM #sdtdoc_id WHERE n = #Id
END --Begin end While
/*End Remaining Documents-----------*/
SET #result=#stdfrm_scrt_apprvstatus
END TRY
BEGIN CATCH
SET #result='N'
insert into tbl_ErrorSql( ErrorMessage, stdfrm_id)
values(coalesce(Error_Message(),ERROR_LINE()),#stdfrm_id)
END CATCH;
--End of Call Insert
END
SELECT #result
END
I'm trying to write a stored procedure to modify a session in my Sessions table. I need to be able to insert values into a specified row i.e. with a condition included although I'm not sure how.
Here is my code (I'm aware that I cannot do INSERT INTO > VALUES > WHERE but I'm trying to give you an idea of what I want to do).
CREATE PROCEDURE [dbo].[TT_Modify_Session]
#SessionName NVARCHAR(50),
#TrainingName NVARCHAR(100),
#Trainee NVARCHAR(20),
#TrainingDate DATE,
#SessionID INT
AS
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN
INSERT INTO dbo.TT_Sessions (SessionName, Trainee, TrainingDate, TrainingName)
VALUES #SessionName, #Trainee, #TrainingDate, #TrainingName
WHERE #SessionID = [SessionID]
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
PRINT ERROR_MESSAGE()
END CATCH
RETURN #sessionID
Any help is greatly appreciated!
You describe code to "modify" values that already exist in the table. That's an UPDATE...
(INSERT adds a new row to a table, and leaves all pre-existing rows as they were...)
UPDATE
dbo.TT_Sessions
SET
SessionName = #SessionName,
Trainee = #Trainee,
TrainingDate = #TrainingDate,
TrainingName = #TrainingName
WHERE
SessionID = #SessionID
I have a stored procedure which inserts info into multiple tables and gets the IDs by SCOPE_IDENTITY(). I would like to prevent multiple users from executing it at the same time, so that my IDs don't get mixed up.
How do I lock it? I have read about sp_getapplock and sp_releaselock, but there is no clear explanation how to use it. Below I put my procedure.
create procedure AddPerson(
#Name nvarchar(255),
#LastName nvarchar(255),
#City nvarchar(255),
#Address nvarchar(255)
)
as
BEGIN TRY
BEGIN TRANSACTION
insert into Location(Address, City)
values(#Address, #City)
declare #LocationID int
set #LocationID = SCOPE_IDENTITY()
insert into PersonalInfo(Name, LastName)
values (#Name, #LastName)
declare #PersonInfoID int
set #PersonInfoID = SCOPE_IDENTITY()
insert into Teacher
values(#LocationID, #PersonInfoID)
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
No locking of any kind needed - this behavior you're trying to accomplish is already in place implicitly.
SCOPE_IDENTITY() returns the new ID in the scope of each transaction - so if 10 users are running this simultaneously, each will get their own, separate ID back from SCOPE_IDENTITY
I'm implementing in my application an event logging system to save some event types from my code, so I've created a table to store the log type and an Incremental ID:
|LogType|CurrentId|
|info | 1 |
|error | 5 |
And also a table to save the concrete log record
|LogType|IdLog|Message |
|info |1 |Process started|
|error |5 |some error |
So, every time I need to save a new record I call a SPROC to calculate the new id for the log type, basically: newId = (currentId + 1). But I am facing an issue with that calculation because if multiple processes calls the SPROC at the same time the "generated Id" is the same, so I'm getting log records with the same Id, and every record must be Id-unique.
This is my SPROC written for SQL Server 2005:
ALTER PROCEDURE [dbo].[usp_GetLogId]
#LogType VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
BEGIN TRY
DECLARE #IdCreated VARCHAR(MAX)
IF EXISTS (SELECT * FROM TBL_ApplicationLogId WHERE LogType = #LogType)
BEGIN
DECLARE #CurrentId BIGINT
SET #CurrentId = (SELECT CurrentId FROM TBL_ApplicationLogId WHERE LogType = #LogType)
DECLARE #NewId BIGINT
SET #NewId = (#CurrentId + 1)
UPDATE TBL_ApplicationLogId
SET CurrentId = #NewId
WHERE LogType = #LogType
SET #IdCreated = CONVERT(VARCHAR, #NewId)
END
ELSE
BEGIN
INSERT INTO TBL_ApplicationLogId VALUES(#LogType, 0)
EXEC #IdCreated = usp_GetLogId #LogType
END
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(MAX)
SET #ErrorMessage = ERROR_MESSAGE()
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
RAISERROR (#ErrorMessage, 16, 1)
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION
SELECT #IdCreated
END
I would appreciate your help to fix the sproc to return an unique id on every call.
It has to work on SQL Server 2005. Thanks
Can you achieve what you want with an identity column?
Then you can just let SQL Server guarantee uniqueness.
Example:
create table my_test_table
(
ID int identity
,SOMEVALUE nvarchar(100)
);
insert into my_test_table(somevalue)values('value1');
insert into my_test_table(somevalue)values('value2');
select * from my_test_table
If you must issue the new ID values yourself for some reason, try using a sequence, as shown here:
if object_id('my_test_table') is not null
begin
drop table my_test_table;
end;
go
create table my_test_table
(
ID int
,SOMEVALUE nvarchar(100)
);
go
if object_id('my_test_sequence') is not null
begin
drop sequence my_test_sequence;
end;
go
CREATE SEQUENCE my_test_sequence
AS INT --other options are here: https://msdn.microsoft.com/en-us/library/ff878091.aspx
START WITH 1
INCREMENT BY 1
MINVALUE 0
NO MAXVALUE;
go
insert into my_test_table(id,somevalue)values(next value for my_test_sequence,'value1');
insert into my_test_table(id,somevalue)values(next value for my_test_sequence,'value2');
insert into my_test_table(id,somevalue)values(next value for my_test_sequence,'value3');
select * from my_test_table
One more edit: I think this is an improvement to the existing stored procedure, given the requirements. Include the new value calculation directly in the UPDATE, ultimately return the value directly from the table (not from a variable which could be out of date) and avoid recursion.
A full test script is below.
if object_id('STACKOVERFLOW_usp_getlogid') is not null
begin
drop procedure STACKOVERFLOW_usp_getlogid;
end
go
if object_id('STACKOVERFLOW_TBL_ApplicationLogId') is not null
begin
drop table STACKOVERFLOW_TBL_ApplicationLogId;
end
go
create table STACKOVERFLOW_TBL_ApplicationLogId(CurrentID int, LogType nvarchar(max));
go
create PROCEDURE [dbo].[STACKOVERFLOW_USP_GETLOGID](#LogType VARCHAR(MAX))
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
BEGIN TRY
DECLARE #IdCreated VARCHAR(MAX)
IF EXISTS (SELECT * FROM STACKOVERFLOW_TBL_ApplicationLogId WHERE LogType = #LogType)
BEGIN
UPDATE STACKOVERFLOW_TBL_APPLICATIONLOGID
SET CurrentId = CurrentID + 1
WHERE LogType = #LogType
END
ELSE
BEGIN
--first time: insert 0.
INSERT INTO STACKOVERFLOW_TBL_ApplicationLogId(CurrentID,LogType) VALUES(0,#LogType);
END
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(MAX)
SET #ErrorMessage = ERROR_MESSAGE()
IF ##TRANCOUNT > 0
begin
ROLLBACK TRANSACTION;
end
RAISERROR(#ErrorMessage, 16, 1);
END CATCH
select CurrentID from STACKOVERFLOW_TBL_APPLICATIONLOGID where LogType = #LogType;
IF ##TRANCOUNT > 0
begin
COMMIT TRANSACTION
END
end
go
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType1';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType1';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType1';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType2';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType2';
exec STACKOVERFLOW_USP_GETLOGID 'TestLogType2';
You want your increment and read to be atomic, with a guarantee that no other process can increment in between.
You also need to ensure that the log type exists, and again for it to be thread-safe.
Here's how I would go about that, but you would be advised to read up on how it all works in SQL Server 2005 as I have not had to deal with these things in nearly 8 years.
This should complete the two actions atomically, and also without transactions, in order to prevent threads blocking each other. (Not just performance, but also to avoid DEADLOCKs when interacting with other code.)
ALTER PROCEDURE [dbo].[usp_GetLogId]
#LogType VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
-- Hold our newly created id in a temp table, so we can use OUTPUT
DECLARE #new_id TABLE (id BIGINT);
-- I think this is thread safe, doing all things in a single statement
----> Check that the log-type has no records
----> If so, then insert an initialising row
----> Output the newly created id into our temp table
INSERT INTO
TBL_ApplicationLogId (
LogType,
CurrentId
)
OUTPUT
INSERTED.CurrentID
INTO
#new_id
SELECT
#LogType, 1
FROM
TBL_ApplicationLogId
WHERE
LogType = #LogType
GROUP BY
LogType
HAVING
COUNT(*) = 0
;
-- I think this is thread safe, doing all things in a single statement
----> Ensure we don't already have a new id created
----> Increment the current id
----> Output it to our temp table
UPDATE
TBL_ApplicationLogId
SET
CurrentId = CurrentId + 1
OUTPUT
INSERTED.CurrentID
INTO
#new_id
WHERE
LogType = #LogType
AND NOT EXISTS (SELECT * FROM #new_id)
;
-- Select the result from our temp table
----> It must be populated either from the INSERT or the UPDATE
SELECT
MAX(id) -- MAX used to tell the system that it's returning a scalar
FROM
#new_id
;
END
Not much you can do here, but validate that:
table TBL_ApplicationLogId is indexed by column LogType.
#LogType sp parameter is the same data type as column LogType in table TBL_ApplicationLogId, so it can actually use the index if/when it exists.
If you have a concurrency issue, maybe forcing the lock level on table TBL_ApplicationLogId during select and update can help. Just add (ROWLOCK) after the table name, Eg: TBL_ApplicationLogId (ROWLOCK)