MSSQL Try Catch Rollback and Conditional RETURN - sql

SET XACT_ABORT ON
BEGIN TRY
BEGIN TRANSACTION
UPDATE [Members] SET [Count] = [Count] - 1 WHERE [Count] > 0;
**return 0 here if this statement updated 0 rows**
INSERT INTO [Sessions] ([Name], [Etc]) VALUES ('Test', 'Other')
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
RETURN 0
END CATCH
SELECT SCOPE_IDENTITY(); **only return the identity if the first statement updated 1 row and no exceptions occurred.**
With the code above, is there a way to return 0 if the first UPDATE updated no rows? I only want the INDENTITY() if the first UPDATE edits a row and no errors occur. Is this possible?

You can use ##ROWCOUNT to get the number changed. This should work:
UPDATE [Members] SET [Count] = [Count] - 1 WHERE [Count] > 0;
IF ##ROWCOUNT = 0
BEGIN
COMMIT TRANSACTION;
RETURN;
END;
Because no rows are updated, I suppose rolling back the transaction would be equivalent.
To get the identify values inserted, I would recommend that you learn to use the OUTPUT clause in INSERT (documented here). This is a good habit to get into, although the syntax is a bit more cumbersome (you have to define a table/table variable to store the inserted values). It has no race conditions and it allows you return multiple identity values.

Only read SCOPE_IDENTITY if you actually INSERT
This way, you have one exit point only
BEGIN TRY
BEGIN TRANSACTION
UPDATE [Members] SET [Count] = [Count] - 1 WHERE [Count] > 0;
IF ##ROWCOUNT = 1
BEGIN
INSERT INTO [Sessions] ([Name], [Etc]) VALUES ('Test', 'Other')
SET #rtn = SCOPE_IDENTITY();
END
SET #rtn = 0;
COMMIT TRANSACTION;
SELECT #rtn
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
END CATCH

Related

How to union multiple select statements while they are not near together?

I have an sql query which check for existence of some records, if those records exist rise error for them otherwise insert them to database. In my query as I need to return error messages for every record, I need to select some custom texts, problem is that they are showing as separate tables, not in one table, which I want (as I am calling this query from nodejs app and it returns an array for me so it only returns first table (error message) for me).
I searched and reach these two options:
1- Use UNION (which is not solving my case)
2- Insert all records in another table and then get all it's record (which isn't beautiful! :) )
DECLARE #errorCOUNT int
SET #errorCOUNT = 0
BEGIN TRANSACTION [Tran1]
IF EXISTS (SELECT * FROM Categories WHERE CategoryName = 'myCat1')
BEGIN
SELECT 'This is error for is = 4' As err
SET #errorCOUNT = #errorCOUNT + 1
END
ELSE
BEGIN
INSERT INTO Categories VALUES ('myCat1')
END
----------------------------
IF EXISTS (SELECT * FROM Categories WHERE CategoryName = 'myCat2')
BEGIN
SELECT 'This is error for is = 5' AS err
SET #errorCOUNT = #errorCOUNT + 1
END
ELSE
BEGIN
INSERT INTO Categories VALUES ('myCat2')
END
----------------------------
IF #errorCOUNT > 0
BEGIN
ROLLBACK TRANSACTION [Tran1]
END
ELSE
BEGIN
COMMIT TRANSACTION [Tran1]
END
As I mentioned I want all these select statements to be shown in one table so they return to my server as one array.
I just think it is good to mention that my query completes in a loop, so it may have different amount of IF...ELSE (between --- lines).
I hope I was clear. Thanks in advance.
Try this one, would work:
BEGIN TRANSACTION [Tran1]
DECLARE #err AS TABLE ( msg NVARCHAR(MAX) NOT NULL )
DECLARE #errorCOUNT AS INT = 0
IF EXISTS (SELECT * FROM Categories WHERE CategoryName = 'myCat1')
BEGIN
INSERT INTO #err (msg) VALUES ('This is error for is = 4')
SET #errorCOUNT = #errorCOUNT + 1
END
ELSE
BEGIN
INSERT INTO Categories VALUES ('myCat1')
END
IF EXISTS (SELECT * FROM Categories WHERE CategoryName = 'myCat2')
BEGIN
INSERT INTO #err (msg) VALUES ('This is error for is = 5')
SET #errorCOUNT = #errorCOUNT + 1
END
ELSE
BEGIN
INSERT INTO Categories VALUES ('myCat2')
END
IF #errorCOUNT > 0
BEGIN
SELECT * FROM #err
ROLLBACK TRANSACTION [Tran1]
END
ELSE
BEGIN
COMMIT TRANSACTION [Tran1]
END
I don't understand what you're really want to do there, but here is a tip using MERGE statement and OUTPUT clause maybe it's what you're after
DECLARE #T TABLE(CategoryName VARCHAR(45));
MERGE INTO T
USING (VALUES('MyCat1'), ('MyCat2')) TT(CategoryName)
ON T.CategoryName = TT.CategoryName -- Or <> instead of =
WHEN NOT MATCHED THEN
INSERT VALUES(TT.CategoryName)
OUTPUT TT.CategoryName INTO #T;
SELECT CASE WHEN CategoryName = 'MyCat1'
THEN 'This is error for is = 4'
WHEN CategoryName = 'MyCat2'
THEN 'This is error for is = 5'
END Res
FROM #T;
Also, I don't think you need to the #ErrorCount variable, since you already have ##ROWCOUNT which you can use it instead.
Here is a db<>fiddle where you can see how it's working.

Insertion of records based on some condition

I'm trying to insert few records from the temporary table using a SQL Server stored procedure. There is a percentage column in the temporary table and a PQ number column. In a table there may exists more than 1 row with the same PQ number. But for insertion to happen the sum of percentage for the same PQ number should be 100%. I couldn't write the where clause for this situation.
CREATE PROCEDURE [dbo].[Upsert_DebitSheet]
#filename VARCHAR(250)
AS
BEGIN
SET XACT_ABORT ON
RETRY: -- Label RETRY
BEGIN TRANSACTION
BEGIN TRY
SET NOCOUNT ON;
INSERT INTO [dbo].[DebitSheet]([Date], [RMMName], [Invoice],[PQNumber], [CAF],
[Percentage], [Amount], [FileName])
SELECT
*, #filename
FROM
(SELECT
[Date], [RMMName], [Invoice], [PQNumber], [CAF],
[Percentage], [Amount]
FROM
[dbo].[TempDebitSheet]
WHERE) result
SELECT ##ROWCOUNT
TRUNCATE TABLE [dbo].[TempDebitSheet]
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
ROLLBACK TRANSACTION
IF ERROR_NUMBER() = 1205 -- Deadlock Error Number
BEGIN
WAITFOR DELAY '00:00:00.05' -- Wait for 5 ms
GOTO RETRY -- Go to Label RETRY
END
END CATCH
SET ROWCOUNT 0;
END
Temporary Table
MainTable(Expected Result)
You can use subquery in the WHERE
INSERT INTO [dbo].[DebitSheet]
([Date]
,[RMMName]
,[Invoice]
,[PQNumber]
,[CAF]
,[Percentage]
,[Amount]
,[FileName])
SELECT [Date]
,[RMMName]
,[Invoice]
,[PQNumber]
,[CAF]
,[Percentage]
,[Amount]
FROM [dbo].[TempDebitSheet]
WHERE EXISTS (
SELECT tmp.[PQNumber]
FROM [dbo].[TempDebitSheet] tmp
WHERE tmp.[PQNumber] = [TempDebitSheet].[PQNumber]
GROUP BY tmp.[PQNumber]
HAVING SUM(tmp.[Percentage]) = 100
)
Modify your query like this
Insert into ...
Select result.*, #filename from (....) result

Is this a good way to add transaction to sql ?

Is this a good way to add TRANSACTION to my code. I have to first update my code and if it fails then insert should also not work. Have a look please if it's a correct way or not. If not then please improve.
Begin Transaction[Transaction1]
Begin Try
IF(#ServiceInfoToJobStatus = 1)
Update ServiceInfo
Set ServiceInfoToJobStatus= 0 --To set all current jobs to prior because one person cannot have many jobs selected as current
Where #ServiceInfoToJobStatus = 1 AND ServiceInfo.fk_PersonalInfo_ServiceInfo_PID= #fk_PersonalInfo_ServiceInfo_PID
Set #ServiceInfoEntryDateTime= (Select GetDate())
Insert into dbo.ServiceInfo
(
ServiceInfoInitialDesignation,
ServiceInfoInitialBPS,
fk_Districts_ServiceInfo_InitialDistrictID,
ServiceInfoJobStatus,
ServiceInfoFromDate,
ServiceInfoDepartment,
fk_PersonalInfo_ServiceInfo_PID,
ServiceInfoServiceType ,
ServiceInfoOffice ,
ServiceInfoCadre ,
fk_WebUsers_ServiceInfo_UserID,
ServiceInfoEntryDateTime,
ServiceInfoToDesignation ,
ServiceInfoToBPS ,
fk_Districts_ServiceInfo_ToDistrictID ,
ServiceInfoToJobStatus ,
ServiceInfoToDate ,
ServiceInfoToDepartment ,
ServiceInfoToServiceType ,
ServiceInfoToOffice ,
ServiceInfoToCadre
)
Values
(
#ServiceInfoInitialDesignation,
#ServiceInfoInitialBPS,
#fk_Districts_ServiceInfo_InitialDistrictID,
#ServiceInfoJobStatus,
#ServiceInfoFromDate,
#ServiceInfoDepartment,
#fk_PersonalInfo_ServiceInfo_PID,
#ServiceInfoServiceType ,
#ServiceInfoOffice ,
#ServiceInfoCadre ,
#fk_WebUsers_ServiceInfo_UserID,
Convert(varchar, #ServiceInfoEntryDateTime, 113),
#ServiceInfoToDesignation ,
#ServiceInfoToBPS ,
#fk_Districts_ServiceInfo_ToDistrictID ,
#ServiceInfoToJobStatus ,
#ServiceInfoToDate ,
#ServiceInfoToDepartment ,
#ServiceInfoToServiceType ,
#ServiceInfoToOffice ,
#ServiceInfoToCadre
)
Set #ReturnStatus = 1
Commit Transaction[Transaction1]
End Try
Begin Catch
ROLLBACK Transaction[Transaction1]
Set #ReturnStatus= 0
Set #ReturnStatusMessage= (Select ERROR_MESSAGE())
End Catch
It is recommended to use add transaction (try/catch block) if the code updates or inserts data in to the table. In your case the answer is pretty yes. But in your transaction block , there is no way to recognize whether the data insert / update completed.
I would use following syntax with ##error added.
BEGIN TRY
BEGIN TRAN
//YOUR CODE
IF ##ERROR = 0
BEGIN
COMMIT TRAN;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR
ROLLBACK TRAN;
END CATCH
For me it is unclear what update fails means (error or no rows updated), but it does not matter much, as you can test for both pretty easy:
DECLARE #UpdateErrorNo INT
DECLARE #UpdateCount INT
DECLARE #InsertErrorNo INT
Begin Transaction[Transaction1]
Begin Try
IF(#ServiceInfoToJobStatus = 1)
Update ServiceInfo
Set ServiceInfoToJobStatus= 0 --To set all current jobs to prior because one person cannot have many jobs selected as current
Where #ServiceInfoToJobStatus = 1 AND ServiceInfo.fk_PersonalInfo_ServiceInfo_PID= #fk_PersonalInfo_ServiceInfo_PID
SET #UpdateErrorNo = ##ERROR
SET #UpdateCount = ##ROWCOUNT
Set #ServiceInfoEntryDateTime= (Select GetDate())
Insert into dbo.ServiceInfo
(
-- skipped for readability
)
Values
(
-- skipped for readability
)
SET #InsertErrorNo = ##ERROR
Set #ReturnStatus = 1
Commit Transaction[Transaction1]
End Try
Begin Catch
ROLLBACK Transaction[Transaction1]
Set #ReturnStatus= 0
Set #ReturnStatusMessage= (Select ERROR_MESSAGE())
End Catch
-- here you can read `#UpdateErrorNo`, `#InsertErrorNo` to check for errors
The tricky part is that ##ERROR is set after each statement, so if you have a case like this (no try/catch):
BEGIN TRAN
INSERT ... -- fails
UPDATE ... -- success
-- ##ERROR will not show that it is a failure
COMMIT
it might lead to unexpected results.
declare #cdt datetime = getdate()
begin try
begin transaction
Update dbo.ServiceInfo
Set ServiceInfoToJobStatus= 0 --To set all current jobs to prior because one person cannot have many jobs selected as current
Where #ServiceInfoToJobStatus = 1
AND fk_PersonalInfo_ServiceInfo_PID= #fk_PersonalInfo_ServiceInfo_PID
Insert into dbo.ServiceInfo
(
ServiceInfoInitialDesignation,
ServiceInfoInitialBPS,
fk_Districts_ServiceInfo_InitialDistrictID,
ServiceInfoJobStatus,
ServiceInfoFromDate,
ServiceInfoDepartment,
fk_PersonalInfo_ServiceInfo_PID,
ServiceInfoServiceType ,
ServiceInfoOffice ,
ServiceInfoCadre ,
fk_WebUsers_ServiceInfo_UserID,
ServiceInfoEntryDateTime,
ServiceInfoToDesignation ,
ServiceInfoToBPS ,
fk_Districts_ServiceInfo_ToDistrictID ,
ServiceInfoToJobStatus ,
ServiceInfoToDate ,
ServiceInfoToDepartment ,
ServiceInfoToServiceType ,
ServiceInfoToOffice ,
ServiceInfoToCadre
)
Values
(
#ServiceInfoInitialDesignation,
#ServiceInfoInitialBPS,
#fk_Districts_ServiceInfo_InitialDistrictID,
#ServiceInfoJobStatus,
#ServiceInfoFromDate,
#ServiceInfoDepartment,
#fk_PersonalInfo_ServiceInfo_PID,
#ServiceInfoServiceType ,
#ServiceInfoOffice ,
#ServiceInfoCadre ,
#fk_WebUsers_ServiceInfo_UserID,
Convert(varchar, #cdt, 113),
#ServiceInfoToDesignation ,
#ServiceInfoToBPS ,
#fk_Districts_ServiceInfo_ToDistrictID ,
#ServiceInfoToJobStatus ,
#ServiceInfoToDate ,
#ServiceInfoToDepartment ,
#ServiceInfoToServiceType ,
#ServiceInfoToOffice ,
#ServiceInfoToCadre
)
return ...
commit transaction
end try
begin catch
if(##TRANCOUNT > 0)
rollback transaction
return ...
end catch
That's what I use:
Begin Try
Begin Tran
-- Do your thing... some DML statements
Commit Tran
End Try
Begin Catch
If ( ##TranCount > 0 )
Rollback Tran
End Catch

SQL Server 2005 BULK INSERT's Committed Count

I use BULK INSERT WITH BATCHSIZE OPTION.
How can I Get Committed Count When the BULK INSERT fail in processing.
like:
BEGIN TRY
BULK INSERT t1 FROM "C:\temp\temp.dat" WITH(BATCHSIZE=1000)
END TRY
BEGIN CATCH
PRINT CONVERT(VARCHAR, ##rowcount)
END CATCH
the ##rowcount returned 0
You could count the table rows first into a variable.
DECLARE #cntBefore bigint;
SELECT #cntBefore = COUNT(*) FROM t1;
BEGIN TRY
BULK INSERT t1 FROM "C:\temp\temp.dat" WITH(BATCHSIZE=1000)
END TRY
BEGIN CATCH
DECLARE #cntAfter bigint;
SELECT #cntAfter = COUNT(*) FROM t1;
PRINT 'Imported ' + CONVERT(VARCHAR, #cntAfter-#cntBefore)
END CATCH
Alternatively, use ROWS_PER_BATCH to optimise the import, then all the rows will be rolled back.

How to restrict insertion of value that already exists in other table

I have 2 tables. I need to check before insertion in one table if the value exist in other table.
I suggest that, you should first check the records that is going to be inserted in each request.
Create Proc Testing
as
Set NoCount ON
Set XACT_ABORT ON
Begin Try
Begin Tran
IF Not Exists(SELECT 1 FROM Table2 i JOIN Table1 t ON i.key = t.key)
Begin
//Your insert statement
END
Commit Tran
End Try
Begin Catch
Rollback Tran
End Catch
IF NOT EXISTS ( SELECT * FROM TableA WHERE Col1 = #Value)
INSERT INTO TableB(Col1) SELECT #Value
Using an INSERT trigger perhaps?
I'm not very sure about the syntax.
CREATE TRIGGER InsertTableTrigger ON Table1 FOR INSERT
AS
BEGIN
IF EXISTS ( SELECT 1 FROM Inserted i JOIN Table1 t ON i.key = t.key )
BEGIN
RAISERROR('Transaction Failed.',16,1)
ROLLBACK TRAN "insert on Table1"
END
END
GO
Inserted is used to access the inserting values.