I have this procedure to delete a row from table
CREATE PROCEDURE [dbo].[spConfiguration_test]
#ID int
AS
Declare #ERR int
set #ERR = 0
DECLARE #Rows int
SET #Rows =0
begin tran
delete from de where ClaimVersion=5
set #ERR = ##Error
SELECT #Rows= #Rows + ##ROWCOUNT;
if #ERR = 0 commit tran
else rollback tran
I want it return a value like 1 or 2 or 3 depending on the number of rows deleted
but it gives me (1 row(s) affected)
what changes do I have to make?
Note that the sp prefix on stored procedure names should be left for Microsoft's use.
If you want to have access to the flag that indicates whether or not rows were deleted then you probably want to add it as an output parameter.
Using a transaction to wrap a single DELETE statement isn't going to do much. I've left the transaction code in on the assumption that this is part of a larger procedure.
If you do have multiple steps within the transaction you may want to use RaIsError to indicate to the caller where a problem occurred and provide some application specific context, e.g. the arguments passed to the procedure.
You can also use try/catch in a stored procedure. Sometimes handy, sometimes clumsy depending on what sorts of operations you need to perform and how much you need to know about exceptions.
create procedure [dbo].[Configuration_test]
#Id Int, -- Unused.
#Deleted Int Output -- Flag: 1 if any rows are deleted, 0 otherwise.
as
set nocount on
declare #Err as Int = 0;
set #Deleted = 0;
begin transaction;
delete
from de
where ClaimVersion = 5;
select #Deleted = case when ##RowCount > 0 then 1 else 0 end,
#Err = ##Error;
if #Err != 0
begin
rollback transaction;
return;
end;
-- Next operation...
set #Err = ##Error;
if #Err != 0
begin
rollback transaction;
RaIsError( 'Boo boo in step 42 processing Id %d.', 13, 42, #Id );
end;
-- Repeat operation/error check...
commit transaction;
Move the reference to ##RowCount immediately after the delete. Or, combine the two assignments in one statement:
delete from de where ClaimVersion = 5 ;
select #Rows = #Rows + ##ROWCOUNT, #ERR = ##Error;
The issue you are facing is that ##RowCount returns the number of rows affected by the previous statement.
In your case the previous statement is: set #ERR = ##Error
Try this instead
DELETE
FROM de
WHERE ClaimVersion = 5
;
SELECT #ERR = ##Error
, #Rows = ##RowCount
;
PRINT #Rows;
P.S. don't prefix your procedure names with things like "sp". It's as bad as prefixing all your tables with "tbl".
You can also SET NOCOUNT OFF.
Remarks
When SET NOCOUNT is ON, the count (indicating the number of rows affected by a Transact-SQL statement) is not returned. When SET NOCOUNT is OFF, the count is returned.
CREATE PROCEDURE [dbo].[spConfiguration_test]
#ID int
AS
Declare #ERR int
set #ERR = 0
SET NOCOUNT OFF
begin tran
delete from de where ClaimVersion=5
set #ERR = ##Error
if #ERR = 0 commit tran
else rollback tran
After SP Creation execute the SP As follows :
EXEC [dbo].[spConfiguration_test]
#ID = 0
Related
This is my stored procedure
ALTER Proc [dbo].[DeleteQualityAssemblyProduction]
#id int,
#Quantity int,
#idPartShip int,
#FK_idNextProcess int
AS
DELETE FROM [dbo].DailyQualityAssemblyProduction
WHERE id=#id
if #FK_idNextProcess=11
Begin
UPDATE [dbo].[ProjectShipping]
SET
QualityAssemblyQty = QualityAssemblyQty- #Quantity
WHERE id=#idPartShip
End
I want when both DELETE and UPDATE run successfully COMMIT the changes otherwise ROLLBACK .
I was wondering if adding COMMIT in the end of stored procedure do the job or I need other method
Here is one way you could tackle this. This is adding a transaction which you will need to handle multiple DML statements in one autonomous block. Then added a try/catch so that if either statement fails the transaction will deal with both statements as one unit of work.
ALTER Proc [dbo].[DeleteQualityAssemblyProduction]
(
#id int,
#Quantity int,
#idPartShip int,
#FK_idNextProcess int
) AS
set nocount on;
begin transaction
begin try
DELETE FROM [dbo].DailyQualityAssemblyProduction
WHERE id = #id
if #FK_idNextProcess = 11
begin
UPDATE [dbo].[ProjectShipping]
SET QualityAssemblyQty = QualityAssemblyQty - #Quantity
WHERE id = #idPartShip
end
commit transaction
end try
begin catch
rollback transaction
declare #error int
, #message varchar(4000);
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
raiserror ('DeleteQualityAssemblyProduction: %d: %s', 16, 1, #error, #message) ;
end catch
My stored procedure executes a delete statement that sets off a trigger that can't record who deleted the row, but records a blank for changed_by.
Then the stored procedure updates the changed_by with the username it was given.
Half the time, part 2 of the below stored procedure finds the results of the trigger, the other half of the time, it doesn't find the results of the trigger, so there is nothing to update.
How can I "yield" control and ensure the update's trigger finishes before continuing with the stored procedure?
(In comments you see some things I've tried so far that haven't worked)
DROP PROCEDURE IF EXISTS dbo.deleteAndUpdateChangedByInAuditTrail
GO
CREATE PROCEDURE dbo.deleteAndUpdateChangedByInAuditTrail
(#tableName VARCHAR(100),
#pkIDColName VARCHAR(100),
#pkIDValue NUMERIC,
#delUser VARCHAR(100) )
AS
BEGIN TRANSACTION;
-- PART 1: DO THE DELETE:
DECLARE #JUST_BEFORE_DELETION_TIMESTAMP AS DATETIME2;
SET #JUST_BEFORE_DELETION_TIMESTAMP = CONVERT(varchar, SYSDATETIME(), 121);
DECLARE #DELETION_TEMPLATE AS VARCHAR(MAX);
SET #DELETION_TEMPLATE = 'delete from {THE_TABLE_NAME} WHERE {PK_ID_COL_NAME} = {PK_ID_VALUE}';
SET #DELETION_TEMPLATE = REPLACE(#DELETION_TEMPLATE, '{THE_TABLE_NAME}', #tableName);
SET #DELETION_TEMPLATE = REPLACE(#DELETION_TEMPLATE, '{PK_ID_COL_NAME}', #pkIDColName);
SET #DELETION_TEMPLATE = REPLACE(#DELETION_TEMPLATE, '{PK_ID_VALUE}', #pkIDValue);
--PRINT #DELETION_TEMPLATE
EXEC (#DELETION_TEMPLATE);
COMMIT TRANSACTION;
BEGIN TRANSACTION;
-- PART 2: UPDATE THE AUDIT_TRAIL:
DECLARE #TOTAL_NUM_ROWS_UPDATED_WITH_USERNAME AS NUMERIC;
SET #TOTAL_NUM_ROWS_UPDATED_WITH_USERNAME = 0;
--DECLARE #TOTAL_TRIES_SO_FAR AS NUMERIC;
--SET #TOTAL_TRIES_SO_FAR = 0;
--WHILE #TOTAL_NUM_ROWS_UPDATED_WITH_USERNAME < 1 AND #TOTAL_TRIES_SO_FAR < 5
--BEGIN
--SET #TOTAL_TRIES_SO_FAR = #TOTAL_TRIES_SO_FAR + 1;
--WAITFOR DELAY '00:00:01.000' -- SEEN IT FAIL FOR 4 SECONDS :(
DECLARE #UPDATE_AUDIT_TRAIL_TEMPLATE AS VARCHAR(MAX);
SET #UPDATE_AUDIT_TRAIL_TEMPLATE = 'update AUDIT_TRAIL set changed_by = ''{CHANGED_BY}'' WHERE upper(table_name) = upper(''{THE_TABLE_NAME}'') and table_pk_value = {PK_ID_VALUE} and CONVERT(varchar, changed_at, 121) >= ''{CHANGED_AT}'' ';
SET #UPDATE_AUDIT_TRAIL_TEMPLATE = REPLACE(#UPDATE_AUDIT_TRAIL_TEMPLATE, '{CHANGED_BY}', #delUser);
SET #UPDATE_AUDIT_TRAIL_TEMPLATE = REPLACE(#UPDATE_AUDIT_TRAIL_TEMPLATE, '{THE_TABLE_NAME}', #tableName);
SET #UPDATE_AUDIT_TRAIL_TEMPLATE = REPLACE(#UPDATE_AUDIT_TRAIL_TEMPLATE, '{PK_ID_VALUE}', #pkIDValue);
SET #UPDATE_AUDIT_TRAIL_TEMPLATE = REPLACE(#UPDATE_AUDIT_TRAIL_TEMPLATE, '{CHANGED_AT}', #JUST_BEFORE_DELETION_TIMESTAMP);
--PRINT #UPDATE_AUDIT_TRAIL_TEMPLATE
EXEC (#UPDATE_AUDIT_TRAIL_TEMPLATE);
SELECT #TOTAL_NUM_ROWS_UPDATED_WITH_USERNAME = ##ROWCOUNT;
--END
COMMIT TRANSACTION;
RETURN #TOTAL_NUM_ROWS_UPDATED_WITH_USERNAME;
GO
Triggers don't get executed asynchronously. The next step after the DELETE will not happen until the trigger is finished.
If you are seeing something that makes you think otherwise, there is some other reason for it. It's not because the trigger "didn't finish".
How to use BEGIN TRANSACTION with while loop in SQL Server?
This query never finishes perhaps because it stops and look for COMMIT TRANSACTION after inserting one row (when #cnt = 1) but I don't want to COMMIT TRANSACTION because I want to see results before committing.
BEGIN TRANSACTION
DECLARE #cnt INT = 0;
WHILE #cnt <= 100
BEGIN
DECLARE #offset INT = 1
INSERT INTO totalSales (col1, col2)
SELECT
'Col1', ROW_NUMBER() OVER (ORDER BY col2) + #offset
FROM
sales
SET #cnt = #cnt + 1;
END;
So how I can check result before commit in while loop?
You should create a BEGIN TRAN outer (general), and inside loop while create a BEGIN TRAN inner (with a trans name).
Inside loop, if are conditions to rollbacks only for this iteration i use SAVE TRAN savepoint for not lose previous trans.
I 've created an example tests in loop while with conditional inserts and rollback savepoint:
declare #num int
set #num = 0
--drop table #test
create table #test (
valor Varchar(100)
)
begin tran
while #num <= 5
begin
begin transaction tran_inner
insert into #test (valor) values ('INSERT 1 INNER -> ' + convert(varchar(10),#num))
save transaction sv_inner
insert into #test (valor) values ('INSERT 2 EVEN - SAVEPOINT -> ' + convert(varchar(10),#num))
if #num % 2 = 0 begin
commit transaction sv_inner
end else begin
rollback transaction sv_inner
end
insert into #test (valor) values ('INSERT 3 INNER -> ' + convert(varchar(10),#num))
set #num = #num + 1
if ##trancount > 0 begin
commit transaction tran_inner
end
end
select valor from #test;
if ##trancount > 0 begin
commit tran
end
Return rows: 1, 2 if iteration even, and 3.
In the same batch (within the same transaction) you can simply issue a SELECT command to see the updated content of the table. Changes will be persisted when the COMMIT TRANSACTION statement is executed or reverted on ROLLBACK.
CREATE TABLE test (id INT IDENTITY(1,1), x VARCHAR(32));
GO
BEGIN TRANSACTION;
INSERT INTO test (x) VALUES ('a');
INSERT INTO test (x) VALUES ('b');
SELECT * FROM test;
ROLLBACK TRANSACTION;
Example: http://sqlfiddle.com/#!6/e4910/2
Alternatively you can use the INSERT INTO .. OUTPUT construct to output the result of the INSERT statement.
Docs: https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
Outside the batch (using a second connection), you can use READ UNCOMMITTED isolation level to be able to read records not committed yet.
Docs: https://technet.microsoft.com/en-us/library/ms189122(v=sql.105).aspx
If you are saying it never finishes it sounds to me like you are getting some blocking going on because that loop runs just fine.
https://www.mssqltips.com/sqlservertip/2429/how-to-identify-blocking-in-sql-server/
I HIGHLY recommend using Adam Machanic's sp_WhoIsActive for this as well: http://whoisactive.com
I'm experiencing some problems that look a LOT like a transaction in a stored procedure has been rolled back, even though I'm fairly certain that it was committed, since the output variable isn't set until after the commit, and the user gets the value of the output variable (I know, because they print it out and I also set up a log table where i input the value of the output variable).
In theory someone COULD manually delete and update the data such that it would look like a rollback, but it is extremely unlikely.
So, I'm hoping someone can spot some kind of structural mistake in my stored procedure. Meet BOB:
CREATE procedure [dbo].[BOB] (#output_id int OUTPUT, #output_msg varchar(255) OUTPUT)
as
BEGIN
SET NOCOUNT ON
DECLARE #id int
DECLARE #record_id int
SET #output_id = 1
-- some preliminary if-statements that doesn't alter any data, but might do a RETURN
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
--insert into table A
SET #id = SCOPE_IDENTITY()
--update table B
DECLARE csr cursor local FOR
SELECT [some stuff] and record_id
FROM temp_table_that_is_not_actually_a_temporary_table
open csr
fetch next from csr into [some variables], #record_id
while ##fetch_status=0
begin
--check type of item + if valid
IF (something)
BEGIN
SET SOME VARIABLE
END
ELSE
BEGIN
ROLLBACK TRANSACTION
SET #output_msg = 'item does not exist'
SET #output_id = 0
RETURN
END
--update table C
--update table D
--insert into table E
--execute some other stored procedure (without transactions)
if (something)
begin
--insert into table F
--update table C again
end
DELETE FROM temp_table_that_is_not_actually_a_temporary_table WHERE record_id=#record_id
fetch next from csr into [some variables], #record_id
end
close csr
deallocate csr
COMMIT TRANSACTION
SET #output_msg = 'ok'
SET #output_id = #id
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SET #output_msg = 'transaction failed !'
SET #output_id = 0
INSERT INTO errors (record_time, sp_name, sp_msg, error_msg)
VALUES (getdate(), 'BOB', #output_msg, error_message())
END CATCH
RETURN
END
I know, my user gets an #output_id that is the SCOPE_IDENTITY() and he also gets an #output_msg that says 'ok'. Is there ANY way he can get those outputs without the transaction getting committed?
Thank you.
You know the problem is that transaction dose NOT support rollback on variables because there is no data change inside database. Either commit or rollback of the transactions ONLY make difference on those database objects (tables, temp table, etc.), NOT THE VARIABLES (including table variables).
--EDIT
declare #v1 int = 0, #v2 int = 0, #v3 int = 0
set #v2 = 1
begin tran
set #v1 = 1
commit tran
begin tran
set #v3 = 1
rollback tran
select #v1 as v1, #v2 as v2, #v3 as v3
RESULT is as follows
Personally I never used transactions in stored procedures, especially when they are used simultaniously by many people. I seriously avoid cursors as well.
I think I would go with passing the involved rows of temp_table_that_is_not_actually_a_temporary_table into a real temp table and then go with an if statement for all rows together. That's so simple in tsql:
select (data) into #temp from (normal_table) where (conditions).
What's the point of checking each row, doing the job and then rollback the whole thing if say the last row doesn't meet the condition? Do the check for all of them at once, do the job for all of them at once. That's what sql is all about.
I am trying to write a stored procedure that reads a column in a particular row of a table, then updates that column with a new value. The orig. is returned.
I want it to lock the row from others till I am done. What is the process?
I have something like
CREATE PROCEDURE [dbo].[aptc_Prt_NextDocumentNumberGet]
(#_iFormatConfigID INT, #_oNextDocumentNumber FLOAT OUTPUT)
AS
BEGIN
DECLARE #FrameworkConfig XML
SET #_oNextDocumentNumber = - 1
DECLARE #NewNextDocumentID FLOAT
SELECT
#_oNextDocumentNumber = FrameworkConfig.value('(/Parameters/Parameter[#Name="NextDocNo.NextDocumentNumber"])[1]', 'float')
FROM
[ttcPrtFormatConfig] WITH (ROWLOCK)
WHERE
FormatConfigID = #_iFormatConfigID
-- Select the Next Doc num out of the xml field
-- increment appropriate control and set output
IF #_iFormatConfigID IS NOT NULL
BEGIN
-- set what will be the "next" doc number after we add this current txn
IF (ABS(#_oNextDocumentNumber - 99999999999999999) < 0.0001)
BEGIN
SELECT #NewNextDocumentID = 1
END
ELSE
BEGIN
SELECT #NewNextDocumentID = #_oNextDocumentNumber + 1
END
UPDATE [ttcPrtFormatConfig]
WITH (ROWLOCK)
SET FrameworkConfig.modify('
replace value of
(/Parameters/Parameter[#Name="NextDocNo.NextDocumentNumber"]/text())[1]
with sql:variable("#NewNextDocumentID")')
WHERE FormatConfigID = #_iFormatConfigID
END
END
This should get you close to what you want.
DECLARE #MyValue INT
--You need a transaction so that the scope of your lock is well defined
BEGIN TRANSACTION
BEGIN TRY
--Get the value you are interested in, This select will lock the row so other people will not even be able to read it until you are finished!!!!!
SELECT #MyValue = MyValue
FROM MyTable WITH (UPDLOCK HOLDLOCK)
WHERE MyValue = SomeValue
--Do your checks and updates. You can take as long as you like as you are the only person who can do a read or update of this data.
IF
BEGIN
UPDATE MyTable
END
--Make sure you commit or rollback! this will release the lock
END TRY
BEGIN CATCH
--Oh no bad stuff! give up and put it back to how it was
PRINT ERROR_MESSAGE() + N' Your message here'
--Check there is a transaction that we can rollback
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK;
END
--You may want to return some error state and not throw!
THROW;
--RETURN -1 --(for example)
END CATCH;
--yay it all worked and your lock will be released
COMMIT
--Do what you like with the old value
RETURN #MyValue