Stored procedure not updating record - sql

I have an issue with a stored procedure where it is updating one table, but not another, and it only happens on the odd occasion, not all the time. This is the stored procedure:
ALTER PROCEDURE [dbo].[AddTransaction]
#BeneficiaryName VARCHAR(200),
#Amount DECIMAL(18,2),
#Reference VARCHAR(200),
#Direction INT,
#Currency INT,
#CurrencyCulture VARCHAR(5),
#Status INT,
#Month INT,
#Year INT,
#BankAccountId INT,
#BusinessId INT,
#FeeType INT,
#IncomingPaymentsPercentage DECIMAL(18,2),
#IncomingPaymentFee DECIMAL(18,2),
#CompletedPaymentFee DECIMAL(18,2),
#DateProcessed DATETIME
AS
BEGIN
DECLARE #oldBalance DECIMAL(18,2)
DECLARE #newBalance DECIMAL(18,2)
DECLARE #fee DECIMAL(18,2)
DECLARE #amountAfterFee DECIMAL(18,2)
BEGIN TRANSACTION
-- SET NOCOUNT ON;
-- lock table 'BankAccounts' till end of transaction
SELECT #oldBalance = availableBalance
FROM BankAccounts
WITH (TABLOCKX, HOLDLOCK)
WHERE Id = #BankAccountId
-- set the new balance
SET #newBalance = #oldBalance + #Amount
UPDATE BankAccounts SET AvailableBalance = #newBalance WHERE Id = #BankAccountId
-- insert a new transaction
IF #FeeType = 1
BEGIN
IF #Direction = 1
BEGIN
SET #fee = #Amount * (#IncomingPaymentsPercentage / 100)
SET #amountAfterFee = #Amount - #fee;
END;
ELSE
BEGIN
SET #fee = 0.00
SET #amountAfterFee = #Amount
END;
END;
ELSE
BEGIN
IF #Direction = 1
BEGIN
SET #amountAfterFee = #Amount - #IncomingPaymentFee
SET #fee = #IncomingPaymentFee
END;
ELSE
BEGIN
SET #amountAfterFee = #Amount - #CompletedPaymentFee
SET #fee = #CompletedPaymentFee
END;
END;
INSERT INTO Transactions
(BeneficiaryName, amount, Reference, Direction, Currency, CurrencyCulture, DateAdded, [Status], DateProcessed, [Month], [Year], Balance, BankAccountId, AmountAfterFee, Fee)
VALUES
(#BeneficiaryName, #Amount, #Reference, #Direction, #Currency, #CurrencyCulture, #DateProcessed, #Status, #DateProcessed, #Month, #Year, #newBalance, #BankAccountId, #amountAfterFee, #fee)
-- now commit the transaction
COMMIT
The insert in to the transactions table is working all the time, but on the odd occasion, the update on the BankAccounts table doesn't happen. This line here:
UPDATE BankAccounts SET AvailableBalance = #newBalance WHERE Id = #BankAccountId
I am not sure if this is anything to do with the lock, but I thought you could still update the locked table within the same transaction. For more info, there could be multiple calls to this stored procedure in the same second, probably no more than 10, but often the issue is when nothing else is accessing the BankAccount record it is updating.

First off, TABLOCKX on a SELECT statement does not take an X-lock. SELECT statements never take X-locks anyway. Furthermore, it is unnecessary and extremely inefficient to lock the entire table.
Secondly, even HOLDLOCK is not enough here, because if no data has been modified, the lock will not be taken. You must use UPDLOCK for this to work as expected.
To be honest, you don't actually need the SELECT anyway, as the whole thing can be done in one atomic statement:
I note that #oldBalance isn't actually used.
UPDATE BankAccounts WITH (HOLDLOCK)
SET AvailableBalance += #amount,
#newBalance = AvailableBalance + #amount
WHERE Id = #BankAccountId;
You can even combine the Transaction insert by using an OUTPUT clause, this enables you to entirely remove the BEGIN TRANSACTION/COMMIT
ALTER PROCEDURE [dbo].[AddTransaction]
#BeneficiaryName VARCHAR(200),
#Amount DECIMAL(18,2),
#Reference VARCHAR(200),
#Direction INT,
#Currency INT,
#CurrencyCulture VARCHAR(5),
#Status INT,
#Month INT,
#Year INT,
#BankAccountId INT,
#BusinessId INT,
#FeeType INT,
#IncomingPaymentsPercentage DECIMAL(18,2),
#IncomingPaymentFee DECIMAL(18,2),
#CompletedPaymentFee DECIMAL(18,2),
#DateProcessed DATETIME
AS
SET NOCOUNT ON;
DECLARE #fee DECIMAL(18,2);
DECLARE #amountAfterFee DECIMAL(18,2);
SET #fee = CASE WHEN #FeeType = 1 THEN
CASE WHEN #Direction = 1
THEN #Amount * (#IncomingPaymentsPercentage / 100)
ELSE 0.0 END
ELSE
CASE WHEN #Direction = 1
THEN #IncomingPaymentFee
ELSE #CompletedPaymentFee END
END;
SET #amountAfterFee = #Amount - #fee;
UPDATE BankAccounts WITH (HOLDLOCK)
SET AvailableBalance += #amount
OUTPUT
#BeneficiaryName, #Amount, #Reference, #Direction, #Currency,
#CurrencyCulture, #DateProcessed, #Status, #DateProcessed,
#Month, #Year, inserted.AvailableBalance, #BankAccountId, #amountAfterFee, #fee
INTO Transactions
(BeneficiaryName, amount, Reference, Direction, Currency,
CurrencyCulture, DateAdded, [Status], DateProcessed, [Month], [Year],
Balance, BankAccountId, AmountAfterFee, Fee)
WHERE Id = #BankAccountId;
GO

Related

RAISERROR Dosn't Work Inside CATCH With ROLLBACK TRANSACTION

I created a Stored Procedure to Insert Into 2 Table With Transaction to make sure That Both Inserts Done and I Used TRY and CATCH to Handle The Errors .. The Problem Is In The Catch Statement I Put ROLLBACK TRANS and RAISERROR The RoLLBACK Works But The Procedure Dose not RAISERROR
Here is The Code
ALTER PROC SP_InsertPlot
#PlotName nvarchar(50),
#GrossArea int,
#SectorName Nvarchar(50),
#PlotYear int,
#OwnerName Nvarchar(50),
#Remarks text,
#NumberOfPlants INT,
#NetArea INT,
#Category Nvarchar(50),
#Type Nvarchar(50),
#Variety Nvarchar(50),
#RootStock Nvarchar(50),
#PlantDistance Decimal(18,2)
AS
BEGIN
DECLARE #PlotID INT
SET #PlotID = (SELECT ISNULL(MAX(PlotID),0) FROM Plots) + 1
DECLARE #SectorID INT
SET #SectorID = (SELECT SectorID FROM Sectors WHERE SectorName = #SectorName)
DECLARE #OwnerID INT
SET #OwnerID = ( SELECT OwnerID FROM Owners WHERE OwnerName = #OwnerName)
DECLARE #CategoryID INT
SET #CategoryID = (SELECT CategoryID FROM Categories WHERE CategoryName = #Category)
DECLARE #TypeID INT
SET #TypeID = (SELECT TypeID FROM Types WHERE TypeName = #Type)
DECLARE #VarietyID INT
SET #VarietyID = (SELECT VarietyID FROM Varieties WHERE VarietyName = #Variety)
DECLARE #RootStockID INT
SET #RootStockID = (SELECT RootStockID FROM RootStocks WHERE RootStockName = #RootStock)
DECLARE #PlotDescID INT
SET #PlotDescID = (SELECT ISNULL(MAX(PlotDescID),0) FROM PlotDescriptionByYear) + 1
BEGIN TRY
SET XACT_ABORT ON
SET NOCOUNT ON
IF(SELECT Count(*) FROM Plots WHERE PlotName = #PlotName) = 0
BEGIN
BEGIN TRANSACTION
INSERT INTO Plots (PlotID,PlotName,GrossArea,SectorID,PlantYear,OnwerID,Remarks)
VALUES(#PlotID,#PlotName,#GrossArea,#SectorID,#PlotYear,#OwnerID,#Remarks)
INSERT INTO PlotDescriptionByYear (PlotDescID, PlantYear, NumberOfPlants,PlotID,NetArea,CategoryID,TypeID,VarietyID,RootStockID,PlantDistance)
VALUES(#PlotDescID,YEAR(GETDATE()),#NumberOfPlants,#PlotID -1,#NetArea,#CategoryID,#TypeID,#VarietyID,#RootStockID,#PlantDistance)
COMMIT TRANSACTION
END
END TRY
BEGIN CATCH
IF(XACT_STATE())= -1
BEGIN
ROLLBACK TRANSACTION
RAISERROR('This Plot Is Already Exists !!',11,1)
END
END CATCH
END
By the way i tried to change the Severity and I tried ##TRANCOUNT instead of XACT_STATE and the Same Problem Happens Which is When I Exec Proc and Pass an Existing Data To The Parameters The Transaction Roll back and did not rise the error
Change IF(XACT_STATE())= -1 to IF(XACT_STATE()) <> 0 and your problem will be done.

i want to decrease the quantity field and decrease its number

CREATE PROCEDURE dbo.IssueBook
(
#bookid nvarchar(50),
#ano nvarchar(50),
#mid int,
#librarian varchar(10),
#quantity int
)
AS
declare #cnt int
declare #msg varchar(100)
if not exists( select * from books where bookid = #bookid and quantity = #quantity)
begin
raiserror('Book is not available',16,1);
return;
end;
select #cnt = count(bookid) from issues where mid = #mid;
if ( #cnt >= 2 )
begin
raiserror('Maximum Limit Has Been Reached For Member!',16,1);
return;
end;
begin tran
begin try
update books set quantity =#quantity-1 where bookid= #bookid;
insert into issues values (#bookid, #mid, getdate(), #librarian, #ano);
commit tran
end try
begin catch
rollback tran
/* select #msg = error_message() */
raiserror( 'Unknown Error', 16,1);
end catch
i want to change value of quantity field in sql table how can i do that please help me i tried many things but they are not working i will be very thankful to you...
I think the problem is in this part: set quantity =#quantity-1. If I understand you correctly, it should either be
set quantity = quantity-1 -- Decreease the book quantity by 1
or
set quantity = quantity - #quantity -- Decreease the book quantity by #quantity

Transaction count after EXECUTE indicates mismatching number of BEGIN and COMMIT statements

When I execute the below stored procedure I get this error:
System.Data.SqlClient.SqlException: Transaction count after EXECUTE
indicates a mismatching number of BEGIN and COMMIT statements.
Previous count = 0, current count = 1.
Code:
create procedure [dbo].[sp_crm_diler_master]
(
#Fullname varchar(100),
#Email varchar(50),
#Mobile varchar(12),
#qualification varchar(50),
#presentaddress varchar(250),
#permanentaddress varchar(250),
#location varchar(50),
#skills varchar(100),
#Dob varchar(15),
#myphoto varbinary(Max),
#uniqueid varchar(25),
#Message varchar(150) output
)
AS
BEGIN
if not exists (select emailid,phone from crm_masterdata where emailid=#Email And phone=#Mobile)
begin
begin transaction
declare #small smalldatetime = (select CAST(#Dob as smalldatetime))
declare #todaydate datetime=(select getdate())
insert into crm_masterdata(uniqueid,fullname,phone,dob,photo,emailid,qualification,location,present_address,permanent_address,skillsets,datasource,entrydate,active)
values(#uniqueid,#Fullname,#Mobile,#small,#myphoto,#Email,#qualification,#location,#presentaddress,#permanentaddress,#skills,'reception',#todaydate,1)
Set #Message=' Registration Successfull,Please Login'
end
else
begin
set #Message='This User Already Registered'
end
end
Where is my error?
create procedure [dbo].[sp_crm_diler_master]
(
#Fullname varchar(100),
#Email varchar(50),
#Mobile varchar(12),
#qualification varchar(50),
#presentaddress varchar(250),
#permanentaddress varchar(250),
#location varchar(50),
#skills varchar(100),
#Dob varchar(15),
#myphoto varbinary(Max),
#uniqueid varchar(25),
#Message varchar(150) output
)
AS
BEGIN
SET NOCOUNT ON;
declare #small smalldatetime = (select CAST(#Dob as smalldatetime));
declare #todaydate datetime=getdate();
declare #Error INT;
BEGIN TRANSACTION; --<-- You need to commit it
IF not exists (select 1 from crm_masterdata where emailid=#Email And phone=#Mobile)
BEGIN
insert into crm_masterdata(uniqueid,fullname,phone,dob,photo,emailid
,qualification,location,present_address,permanent_address,skillsets,datasource,entrydate,active)
values(#uniqueid,#Fullname,#Mobile,#small,#myphoto,#Email,#qualification
,#location,#presentaddress,#permanentaddress,#skills,'reception',#todaydate,1)
SET #Error = ##ERROR;
Set #Message =' Registration Successfull,Please Login'
END
ELSE
BEGIN
SET #Message='This User Already Registered'
END
IF (#Error = 0)
COMMIT TRANSACTION; --<-- Commit tran
ELSE
ROLLBACK TRANSACTION;
END
I don't see a COMMIT TRANSACTION to match your BEGIN TRANSACTION.

Sql Stored Procedure to insert and update

I am new to Stored Procedures and SQL. Looking in to various articles, I found how to insert an record using stored procedure and it works.
CREATE PROCEDURE [dbo].[stprOrder]
#OrderDate date,
#OrderID nchar(50),
#ShipToID nchar(50),
#TotalAmt decimal(18,2),
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO ORDER(OrderDate,OrderID,ShipToID,TotalAmt)
Values(#OrderDate,#OrderID,#ShipToID,#TotalAmt)
END
I am not sure how to update an record using the same stprOrder stored procedure. Like the stored procedure should do inserting and updating depending on the OrderID.
Most likely you're looking for something like this
CREATE PROCEDURE [dbo].[stprOrder]
#OrderDate date,
#OrderID nchar(50),
#ShipToID nchar(50),
#TotalAmt decimal(18,2),
AS
BEGIN
SET NOCOUNT ON;
IF (SELECT TOP (1) 1 FROM ORDER WHERE OrderID = #OrderID) IS NULL
INSERT INTO ORDER(OrderDate,OrderID,ShipToID,TotalAmt)
Values(#OrderDate,#OrderID,#ShipToID,#TotalAmt)
ELSE
UPDATE ORDER SET OrderDate = #OrderDate, ShipToID = #ShipToID, TotalAmt = #TotalAmt
WHERE OrderID = #OrderID
END
First it checks if order with given ID already exists - if it doesn't - a new entry is created, otherwise existing record is updated
CREATE PROCEDURE [dbo].[stprOrder]
#OrderDate date,
#OrderID nchar(50),
#ShipToID nchar(50),
#TotalAmt decimal(18,2),
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT null FROM ORDER WHERE id = #orderID)
BEGIN
UPDATE ORDER SET ..... WHERE id = #orderID
END
ELSE
BEGIN
INSERT INTO ORDER(OrderDate,OrderID,ShipToID,TotalAmt)
VALUES(#OrderDate,#OrderID,#ShipToID,#TotalAmt)
END

stored procedure with insert & update using identity column

I have a table called 'tasks' in that 'task id' is identity column, for that table I have to write save stored procedure, in which when 'task id' is not given it should insert the values and when 'task id' is given it should update the table.
how can this achievable when task id is identity column could anyone explain with example.
here is the code
Alter PROCEDURE TaskSave
(
#taskid int,
#ProjectId int,
#EmployeeId int,
#TaskName nvarchar(50),
#Duration_Hrs int,
#StartDate nvarchar(20),
#FinishDate nvarchar(20),
#CreateUserId int,
#CreatedDate nvarchar(20),
#ModifiedUserID int,
#ModifiedDate nvarchar(20),
#Is_CommonTask bit
) AS
BEGIN
IF Exists( select null from TblTasks where TaskId=#TaskId)
BEGIN
INSERT TblTasks
VALUES (#ProjectId,#EmployeeId,#TaskName,#Duration_Hrs,
#StartDate,#FinishDate,#CreateUserId,#CreatedDate,
#ModifiedUserID,#ModifiedDate,#Is_CommonTask)
END
ELSE
BEGIN
UPDATE TblTasks SET
StartDate=#StartDate,FinishDate=#FinishDate,
Duration_Hrs=#Duration_Hrs
WHERE TaskId=#TaskId
END
END
GO
First of all, give your input variable TaskID a default value like below, then simply check to see if the variable is NULL, if so, insert a new row
Alter PROCEDURE TaskSave
(
#taskid int = NULL,
#ProjectId int,
#EmployeeId int,
#TaskName nvarchar(50),
#Duration_Hrs int,
#StartDate nvarchar(20),
#FinishDate nvarchar(20),
#CreateUserId int,
#CreatedDate nvarchar(20),
#ModifiedUserID int,
#ModifiedDate nvarchar(20),
#Is_CommonTask bit
) AS
BEGIN
IF #taskid IS NULL
BEGIN
INSERT TblTasks
VALUES (#ProjectId,#EmployeeId,#TaskName,#Duration_Hrs,
#StartDate,#FinishDate,#CreateUserId,#CreatedDate,
#ModifiedUserID,#ModifiedDate,#Is_CommonTask)
END
ELSE
BEGIN
UPDATE TblTasks SET
StartDate=#StartDate,FinishDate=#FinishDate,
Duration_Hrs=#Duration_Hrs
WHERE TaskId=#TaskId
END
END
GO
You are close, check if the record doesn't exist and perform insert, otherwise update. You can also declare the #TaskId parameter as OUTPUT and return it when inserting, using the SCOPE_IDENTITY() function:
ALTER PROCEDURE TaskSave(
#TaskId INT = NULL OUTPUT
, #ProjectId INT
, #EmployeeId INT
, #TaskName NVARCHAR(50)
, #Duration_Hrs INT
, #StartDate NVARCHAR(20)
, #FinishDate NVARCHAR(20)
, #CreateUserId INT
, #CreatedDate NVARCHAR(20)
, #ModifiedUserID INT
, #ModifiedDate NVARCHAR(20)
, #Is_CommonTask BIT
)
AS
BEGIN
IF NOT(EXISTS(SELECT * FROM TblTasks WHERE TaskId = #TaskId))
BEGIN
INSERT INTO TblTasks(
ProjectId
, EmployeeId
, TaskName
, Duration_Hrs
, StartDate
, FinishDate
, CreateUserId
, CreatedDate
, ModifiedUserID
, ModifiedDate
, Is_CommonTask
)
VALUES(
#ProjectId
, #EmployeeId
, #TaskName
, #Duration_Hrs
, #StartDate
, #FinishDate
, #CreateUserId
, #CreatedDate
, #ModifiedUserID
, #ModifiedDate
, #Is_CommonTask
)
SET #TaskId = SCOPE_IDENTITY()
END
ELSE
BEGIN
UPDATE TblTasks SET
StartDate = #StartDate
, FinishDate = #FinishDate
, Duration_Hrs = #Duration_Hrs
WHERE TaskId=#TaskId
END
END
GO
declare #taskid parameter as NULL. the if it is null then insert else update. see below.
Alter PROCEDURE TaskSave
(
#taskid int =NULL,
#ProjectId int,
#EmployeeId int,
#TaskName nvarchar(50),
#Duration_Hrs int,
#StartDate nvarchar(20),
#FinishDate nvarchar(20),
#CreateUserId int,
#CreatedDate nvarchar(20),
#ModifiedUserID int,
#ModifiedDate nvarchar(20),
#Is_CommonTask bit
) AS
BEGIN
if #taskid is null
BEGIN
INSERT TblTasks
VALUES (#ProjectId,#EmployeeId,#TaskName,#Duration_Hrs,
#StartDate,#FinishDate,#CreateUserId,#CreatedDate,
#ModifiedUserID,#ModifiedDate,#Is_CommonTask)
END
ELSE
BEGIN
UPDATE TblTasks SET
StartDate=#StartDate,FinishDate=#FinishDate,
Duration_Hrs=#Duration_Hrs
WHERE TaskId=#taskid
END
END
GO
In cases like these, I used to create a general stored procedure which is capable of inserting, updating and deleting an item. The general pattern looked like this
create procedure Modify_MyTable
#Action char(1),
#Data int,
#PK int output
as
if #Action = 'I' begin -- Insert
insert into MyTable (Data) values (#Data)
set #PK = Scope_Identity()
end
if #Action = 'M' begin -- Modify
update MyTable set Data = #Data where PKID = #PK
end
if #Action = 'D' begin -- Delete
delete MyTable where PKID = #PK
end
It is quite a while ago when I used this but I found it quite handy as I have all the manipulation code in one SP (I could have used three, of course) and could also add logging features and other basic logic in this procedure.
I am not saying that this is still the best method but it should show the basic logic.