i want to decrease the quantity field and decrease its number - sql

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

Related

Stored procedure not updating record

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

SQL transaction/ErrorHandling/TryandCatch

Taking a SQL programming(2012 version) class and totally stuck, program wont work no matter how many times I try. The requirements (Questions) as well as what I have so far are below. Below the dashed line is another proc I wrote for error handling. Please help me finish this...please!
/*Create a Stored Procedure that accepts StockName, NewOpenPrice, NewClosePrice.
a. If the Stock Name does not EXIST a new record should be added into the dbo.Stocks table
b. If the Stock Name does EXIST, the OpenPrice an ClosePrice will be updated with the newly inserted Prices.
c. Insert and Update statements should be built using a transaction (Repeatable Read Isolation Level)
d. A Try Catch Statement should be used for the Update and Insert statements. If there is an error, the dbo.error_handler Stored Procedure should be called.
*/
CREATE PROCEDURE spc_Stocks
#Name varchar(25), #NewOpenPrice money, #NewClosePrice money
as
BEGIN
CREATE TABLE dbo.Stocks (
StockID int IDENTITY(1,1),
StockName varchar(50),
OpenPrice money,
ClosePrice money )
INSERT INTO dbo.Stocks
SELECT 'Walmart',21.58,22.98 UNION
SELECT 'Target',17.32,15.23 UNION
SELECT 'Taco Bell',4.58,12.98 UNION
SELECT 'Microsoft',7.15,8.15 UNION
SELECT 'Apple',10.79,9.89
Select StockName from stocks
where StockName = #Name
------Name does NOT exist
if (#Name = NULL)
Begin
Insert into dbo.Stocks (StockName)
Values (#Name)
END
----If name DOES exist
ELSE
BEGIN
Begin TRY
Begin SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
UPDATE dbo.Stocks SET OpenPrice =#NewOpenPrice, ClosePrice= #NewClosePrice where StockName = #Name
Commit transaction
END TRY
BEGIN CATCH
?!?!?!?!?!?
*This is my ErrorHandler stored proc query
ALTER PROCEDURE dbo.error_handler
as
BEGIN
DECLARE #errnum INT,
#severity INT,
#errstate INT,
#proc NVARCHAR(126),
#line INT,
#message NVARCHAR(4000)
-- capture the error information that caused the CATCH block to be invoked
SELECT #errnum = ERROR_NUMBER(),
#severity = ERROR_SEVERITY(),
#errstate = ERROR_STATE(),
#proc = ERROR_PROCEDURE(),
#line = ERROR_LINE(),
#message = ERROR_MESSAGE()
end
Create PROCEDURE [dbo].[USP_Stocks]
(#Name varchar(25), #OpenPrice MONEY, #ClosePrice MONEY)
AS
BEGIN
-----Name does NOT exist
IF NOT EXISTS (SELECT StockName FROM [dbo].[Stocks]
WHERE StockName = #Name)
BEGIN
BEGIN TRY
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
INSERT INTO dbo.Stocks (StockName, OpenPrice, ClosePrice)
VALUES (#Name ,#OpenPrice, #ClosePrice)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
EXEC dbo.error_handler
ROLLBACK
END CATCH
END
----If name DOES exist
ELSE
BEGIN
BEGIN TRY
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
UPDATE dbo.Stocks SET OpenPrice =#OpenPrice, ClosePrice= #ClosePrice
where StockName = #Name
Commit transaction
END TRY
BEGIN CATCH
ROLLBACK TRUANSACTION
EXEC dbo.error_handler
END CATCH
END
END

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.

Merge sql with date condition

I have create merge stored procedures as below, what i am trying to achieve is the following scenario:
Merge the new record if ProductTRN is not exist in ProductList table (complete)
Only Update the ProductList record in where the PU.CreateDate is bigger than CreateDate of target table which is ProductList (Not Complete)
Please advise me how I can achieve the second scenario above, thank you
CREATE PROCEDURE [dbo].[usp_ProductList_Merge]
AS
BEGIN
DECLARE #retValue INT
BEGIN TRY
IF OBJECT_ID('ProductList') IS NOT NULL
BEGIN
BEGIN TRANSACTION MergeConsumerTable
SET NOCOUNT ON;
MERGE dbo.ProductList AS target
USING
( SELECT
PU.ProductTRN,
PU.ProductName,
PU.ProductDescription,
PU.CreateDate
FROM dbo.TmpProductList PU
WHERE PU.ProductTRN = ProductTRN
) AS source (
ProductTRN,
ProductName,
ProductDescription
CreateDate)
ON ( (target.ProductTRN) = LOWER(source.ProductTRN)
)
WHEN MATCHED
THEN
UPDATE SET
ProductTRN= source.ProductTRN
WHEN NOT MATCHED
THEN
INSERT (
ProductTRN,
ProductName,
ProductDescription,
CreateDate
) VALUES
(
source.ProductTRN,
source.ProductName,
source.ProductDescription,
source.CreateDate,
);
DELETE PU
FROM dbo.TmpProductList PU
COMMIT TRANSACTION MergeProductListTable
SET #retValue = 1
SELECT #retValue
END
ELSE
BEGIN
SET #retValue = -1
SELECT #retValue
END
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION MergeProductListTable
DECLARE #ErrorMsg VARCHAR(MAX);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SET #ErrorMsg = ERROR_MESSAGE();
SET #ErrorSeverity = ERROR_SEVERITY();
SET #ErrorState = ERROR_STATE();
SET #retValue = 0
SELECT #retValue
-- SELECT 0 AS isSuccess
END CATCH
END
WITH Source AS (
SELECT ProductTRN
,ProductName
,ProductDescription
,CreateDate
FROM dbo.TmpProductList
)
MERGE ProductList AS Target
USING Source
ON Target.ProductTRN = Source.ProductTRN
WHEN MATCHED
AND Source.CreatedDate > Target.CreatedDate
THEN UPDATE SET
ProductName = Source.ProductName
,ProductDescription = Source.ProductDescription
,CreateDate = Source.CreatedDate
WHEN NOT MATCHED BY TARGET
THEN INSERT (
ProductTRN
,ProductName
,ProductDescription
,CreateDate
)
VALUES (
Source.ProductTRN
,Source.ProductName
,Source.ProductDescription
,Source.CreatedDate
)

SQL Server Transaction inside stored procedure

Suppose, i have two tables Product and ProductSales.
Product table has:
ProductID(int)(pk)
Name(varchar2)
UnitPrice(float)
ProductAvailable(int)
ProductSales table has:
ProductSalesID(int)(pk)
ProductID(int)
Quantity(int)
I want to create a transaction inside stored procedure that first checks if the Quantity is less than ProductAvailable. If it is greater then Rollback the transaction else Deduct the Quantity(given by user) from ProductAvailable and also insert into ProductSales table.
how can i solve the scenario using SQL Server
Solution for concurrent updates:
-- Test Data
CREATE TABLE Product (
ProductID int NOT NULL IDENTITY PRIMARY KEY CLUSTERED
,Name varchar(256) NOT NULL
,UnitPrice money NOT NULL
,ProductAvailable int NOT NULL)
CREATE TABLE ProductSales (
ProductSalesID int NOT NULL IDENTITY PRIMARY KEY CLUSTERED
,ProductID int NOT NULL
,Quantity int NOT NULL)
INSERT INTO Product (Name, UnitPrice, ProductAvailable)
VALUES ('Prod1', 5.0, 10)
,('Prod2',6.0, 5)
GO
First variant - MSDN OUTPUT Clause
CREATE PROCEDURE Sale
#ProductID int
,#Quantity int
AS
BEGIN
IF #Quantity <= 0 RETURN -1;
-- atomic operation
UPDATE Product
SET ProductAvailable = ProductAvailable - #Quantity
OUTPUT INSERTED.ProductID, #Quantity INTO ProductSales (ProductID, Quantity)
WHERE ProductID = #ProductID
AND ProductAvailable >= #Quantity;
IF ##ROWCOUNT = 0 RETURN -1;
END
GO
But it has limitation - output_table cannot:
Have enabled triggers defined on it.
Participate on either side of a FOREIGN KEY constraint.
Have CHECK constraints or enabled rules.
Second variant - MSDN Transaction Statements
CREATE PROCEDURE Sale2
#ProductID int
,#Quantity int
AS
BEGIN
DECLARE #ProcID CHAR(36) = NEWID()
,#TranCount Int = ##TranCount
,#ProductAvailable int
,#errorMessage nvarchar(4000)
,#errorSeverity int
,#errorState int
,#ReturnCode int = -1;
IF #Quantity <= 0 RETURN -1;
IF (#TranCount = 0)
BEGIN TRANSACTION;
ELSE
SAVE TRANSACTION #ProcID;
BEGIN TRY
SELECT #ProductAvailable = ProductAvailable
FROM Product WITH (XLOCK, HOLDLOCK)
WHERE ProductID = #ProductID;
-- Record is locked, you can do any checks here
IF #ProductAvailable >= #Quantity
BEGIN
UPDATE Product
SET ProductAvailable = ProductAvailable - #Quantity
WHERE ProductID = #ProductID;
INSERT INTO ProductSales (ProductID, Quantity)
VALUES(#ProductID, #Quantity);
SET #ReturnCode = 1;
END
ELSE
SET #ReturnCode = -1;
IF (#TranCount = 0)
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF (#TranCount = 0)
ROLLBACK TRANSACTION;
ELSE
IF (XACT_STATE() <> -1)
ROLLBACK TRANSACTION #ProcID;
SELECT #errorMessage = 'Error in [Sale2]: ' + ERROR_MESSAGE(), #errorSeverity = ERROR_SEVERITY(), #errorState = ERROR_STATE();
RAISERROR (#errorMessage, #errorSeverity, #errorState);
SET #ReturnCode = -1;
END CATCH
RETURN #ReturnCode;
END
GO
Check result
-- Test
DECLARE #RetCode int
EXEC #RetCode = Sale #ProductID = 1, #Quantity = 7
SELECT #RetCode as RetCode
EXEC #RetCode = Sale2 #ProductID = 1, #Quantity = 2
SELECT #RetCode as RetCode
EXEC #RetCode = Sale #ProductID = 2, #Quantity = 7
SELECT #RetCode as RetCode
GO
SELECT * FROM ProductSales
SELECT * FROM Product
GO