Lock Stored Procedure in SQL - sql

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

Related

I want to write the code I created with the 'Stored procedure' as a function

CREATE PROC add_person
(
#id tinyint,
#name nvarchar(max),
#surname nvarchar(max),
#salary int,
#job nvarchar(max)
)
AS
BEGIN
INSERT INTO information
VALUES(#id,#name,#surname,#salary,#job)
END
I want to write this code as a function. But the concept of "return" confuses me. That's why I couldn't.
I tried to write the code above as a function. This code came out.
CREATE FUNCTION add_person
(
#id tinyint,
#name nvarchar(max),
#surname nvarchar(max),
#salary int,
#job nvarchar(max)
)
RETURNS TABLE
AS
BEGIN
RETURN INSERT INTO information -- not work
VALUES(#id,#name,#surname,#salary,#job)
END
If you want to return the newly created table, you can use the stored procedure to do that. If you're using SQL Server, the code would be:
BEGIN
INSERT INTO information -- not work
VALUES(#id,#name,#surname,#salary,#job);
SELECT * FROM information WHERE id = ##identity; -- this is the primary key just created.
END
Functions are much more limited in their functionality than are stored procedures.
Although insert is allowed, it is only allowed in local variables. As the documentation says:
INSERT, UPDATE, and DELETE statements modifying local table variables.
On the other hand, a stored procedure can return a value. Normally, this is a status code, where 0 means everything succeeded, and any other value means that the process failed.

Update followed by insert in a stored procedure

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)

Stored Procedure Cross Table ID Input

I'm trying to insert other table's IDs (Company and Bank) into the uBankID and uCompanyID of the EndUser table and the BankID of the Company table on INSERT.
Whatever way I do this, the required fields aren't being populated, what am I doing wrong? I had a look at an inline select statement at the ID to try and grab it but couldn't fathom it and it wouldn't compile.
The variables are all present and correct in the backend and are being parsed through, all but these IDs, as nothing is going wrong with the C# I'm thinking there's something amiss with my SQL, especially as I'm fairly new to stored procedures.
Any help would be greatly appreciated.
Code (slimmed down):
CREATE PROCEDURE [ProcedureInsert]
#Title nvarchar(10),
#FirstName nvarchar(50),
#LastName nvarchar(50),
#Organisation nvarchar(50),
#Address nvarchar(50),
#uBankID int,
#uCompanyID int,
#BankID int,
#SortCode int,
#AccountNumber nvarchar(50),
#AccNameHolder nvarchar(50),
#cId int output,
#bId int output,
#euId int output
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [Bank](SortCode, AccountNumber, AccNameHolder)
VALUES(#SortCode, #AccountNumber, #AccNameHolder)
SET #bId = SCOPE_IDENTITY();
INSERT INTO [Company](Organisation, Address, BankID)
VALUES(#Organisation, #Address, #bId)
SET #cId = SCOPE_IDENTITY();
INSERT INTO [EndUser](Title, FirstName, LastName, uBankID, uCompanyID)
VALUES(#Title, #FirstName, #LastName, #uBankID, #cId)
SET #euId = SCOPE_IDENTITY();
END
You need to declare the variables. And Tab Alleman is right, get rid of the unused parameters.
DECLARE #cId int;
DECLARE #bId int;
DECLARE #euId int;
INSERT INTO [BAD](SortCode,AccountNumber,AccNameHolder)
VALUES('1234','a234','Test Name')
SET #bId=SCOPE_IDENTITY();
INSERT INTO [Company](Organisation,Address1, Address2,City,County,PostCode,Telephone,BankID)
VALUES('AnOrganisation','addressesss','Address2','City','County','PostCode','0123one', #bId)
SET #cId=SCOPE_IDENTITY();
INSERT INTO [EndUser](Title,FirstName,LastName,Email,uBankID,uCompanyID)
VALUES('Sitle','Fiame','astName','vv#Email',#bId,#cId)
SET #euId=SCOPE_IDENTITY();
EDIT
Remove those parameters if they're not being used, but they weren't in the sample code, else leave them, obviously.
Also, I used single quotes to dump in the data into the table rather than variables, if it's not working then there's something wrong with the bank end code.

problem with raiseerror()

what I want to do is to create a stored procedure that executes insert statement.There is a possibility the execution to fail because of a check constraint for the table Employee.In that case I want to handle a user-defined error.Obviously the following procedure is not working properly because it always raises my error,but not only when insertion fails.
EXEC sp_addmessage 50001, 16, N'Title must be one of the following - Captain,Engineer,Flight-attendant,Purser,First-officer';
CREATE PROCEDURE InsertIntoEmployee
#firstName nvarchar(30),
#familyName nvarchar(30),
#title nvarchar(50),
#address nvarchar(50),
#chiefID int ,
#salary money ,
#FK_IDCrew int,
#FK_DepartmentID int
AS
BEGIN
declare #err_num int;
declare #err_sev int;
declare #err_msg int;
begin try
insert into Employee(firstName, familyName, title, address, chiefID, salary, FK_IDCrew,
FK_DepartmentID)
values(#firstName, #familyName, #title, #address, #chiefID, #salary, #FK_IDCrew,
#FK_DepartmentID);
raiserror(50001,16,1);
END try
begin catch
set #err_num=ERROR_NUMBER();
set #err_sev=ERROR_SEVERITY();
set #err_msg=ERROR_STATE();
raiserror(#err_num,#err_sev,#err_msg);
end catch
end
GO
In this case:
Title should be a lookup to another table and a foreign key
In the CATCH block you can trap the FK constraint violation separately if you want...
...but you'd only allow rows from the new table in your client so I wouldn't personally
No need for a sys.messages entry
Your code will also always hit the RAISERROR too which doesn't add any value,
I hope that the dimensions mentioned in the parameter list is sycn with table columns length.
Before insertion, You should check take care of following points.
Check the existence of #FK_IDCrew value in it's table.
Check the existence of #FK_DepartmentID value in it's table.
It should be like below.
If Not Exists(Select IDCrewColumnName From Table Where columnName = #FK_IDCrew)
Begin
return here from the stored procedure.
End
In case any of them fails to meet the conditions, you should show some user friendly message to user that
(a) Crew ID, you are going to insert, either deleted or does not exists in the database.
(b) DepartmentID, you are going to insert, either deleted or does not exists in the database.
In this way the probability of error will also come to an end.

stored procedure executes correctly from management studio, but not in production

I have a stored procedure that works correctly when I execute the stored procedure from SQL Server Management Studio. Unfortunately, it doesn't behave the the same way on the production server. On an insert statement, it is only inserting some of the values and the rest are NULL. The NULL values are coming from user defined scalar function calls (which also work correctly when executed from Management Studio). Has anyone run into anything similar? I was thinking it might be a permissions issue, but I connected to the database through Management Studio with the production connection credentials and saw the same behavior. I'm a C# developer that normally works with ORMs, so I'm definitely no SQL expert. Thanks in advance, guys.
Here is the code:
BEGIN
DECLARE #UserExists int
SET #UserExists = 0
SELECT #UserExists = COUNT(*) FROM UserPass WHERE UserId = #UserID AND PortalID = #PORTALID
--If the Action is add and a User Exists change the Action to EDT (Update)
IF #Action = 'ADD' AND #UserExists > 0
SET #Action = 'EDT'
--Get All Of the Properties for this User
DECLARE #EMAIL nvarchar(255)
DECLARE #FIRSTNAME nvarchar(50)
DECLARE #LASTNAME nvarchar(50)
DECLARE #GENDER char(1)
DECLARE #BIRTHDATE smalldatetime
DECLARE #ADDRESS nvarchar(50)
DECLARE #CITY nvarchar(50)
DECLARE #STATE nchar(2)
DECLARE #COUNTRY nvarchar(50)
DECLARE #POSTALCODE nvarchar(10)
DECLARE #TELEPHONE nvarchar(20)
DECLARE #CELL nvarchar(20)
DECLARE #EMAILPERMISSION bit
DECLARE #TEXTPERMISSION bit
DECLARE #UPDATEDIRECTION nvarchar(3)
BEGIN TRY
SELECT #BIRTHDATE = CAST(dbo.GetPropertyValue(#PORTALID,#USERID,'Birthdate') AS SmallDatetime)
END TRY
BEGIN CATCH
SELECT #BIRTHDATE = NULL
END CATCH
SELECT #EMAIL = Email,
#FIRSTNAME = dbo.Proper(Firstname),
#LASTNAME = dbo.Proper(Lastname),
#GENDER = dbo.GetPropertyValue(#PORTALID,#USERID,'Gender'),
#ADDRESS = dbo.GetPropertyValue(#PORTALID,#USERID,'Street'),
#CITY = dbo.Proper(dbo.GetPropertyValue(#PORTALID,#USERID,'City')),
#STATE = Upper(dbo.GetState(dbo.GetPropertyValue(#PORTALID,#USERID,'Region'))),
#COUNTRY = dbo.GetPropertyValue(#PORTALID,#USERID,'Country'),
#POSTALCODE = dbo.GetPropertyValue(#PORTALID,#USERID,'Postalcode'),
#TELEPHONE = dbo.STRFILTER(dbo.GetPropertyValue(#PORTALID,#USERID,'Telephone'),'0,1,2,3,4,5,6,7,8,9'),
#CELL = dbo.STRFILTER(dbo.GetPropertyValue(#PORTALID,#USERID,'Cell'),'0,1,2,3,4,5,6,7,8,9'),
#EMAILPERMISSION = dbo.GetPropertyValue(#PORTALID,#USERID,'eNewsLetter'),
#TEXTPERMISSION = dbo.GetPropertyValue(#PORTALID,#USERID,'TextPermission')
FROM Users
WHERE UserId = #USERID
-- Insert new user
IF #Action = 'ADD'
BEGIN
INSERT INTO UserPass
(UserID, Portalid, CreatedDate, Username, UserPass.Password, email, firstname, lastname, gender, birthdate, UserPass.address, city, UserPass.state, country, postalcode, telephone, cell, emailpermission, textpermission, UpdateDirection)
VALUES
(#UserID, #PORTALID, #CREATEDDATE, #Username, #Password, #EMAIL, #FIRSTNAME, #LASTNAME,#GENDER, #BIRTHDATE, #ADDRESS, #CITY, #STATE, #COUNTRY, #POSTALCODE, #TELEPHONE, #CELL, #EMAILPERMISSION, #TEXTPERMISSION, 'OUT')
END
#PORTALID and #USERID are passed to the stored procedure as parameters, and those values are actually saving in the insert. The columns that aren't updating are the ones that call the GetPropertyValue function for the value. This is only on one database server (I am not connecting to a dev database through Management studio, I am connecting directly to the production database). When I execute the stored procedure from Management Studio, it's perfect. When the trigger on the table calls the sproc, the GetPropertyValue function fails.
Get SQL Profiler on it and then copy & paste & execute the statements from that in query analyser.
It's likely that your production is producing subtlely different code or is injecting different param values than the ones you expect and this will catch exactly what is happening.
While it's impossible to see what is happening without table structures, sprocs and functions my thoughts would be to compare the table structures, defaults, identity columns, etc. in PROD and DEV.
That says to me something is funny in your application code. I would check your C# logic and make sure you are using the right function call on the command object. It might help to post your code.
As Nissan Fan says, you haven't provided nearly enough information. That said, one possibility is that you're on SQL Server 2000 and are encountering an old bug. If you want a useful answer, though, ask a useful question.