(Coding for Concurrency).sql

---------------------------------------------------------------------------------------------
-- Query Optimization Basics
---------------------------------------------------------------------------------------------

SELECT  productModel.name as productModel,
        product.name as productName
FROM    AdventureWorks2008.production.product as product
          join AdventureWorks2008.production.productModel as productModel
                 on productModel.productModelId = product.productModelId
WHERE   product.name like '%glove%'


---------------------------------------------------------------------------------------------
-- Transactions; Transaction Syntax; Basic Transactions
---------------------------------------------------------------------------------------------

BEGIN TRANSACTION one
ROLLBACK TRANSACTION one
GO
BEGIN TRANSACTION one
BEGIN TRANSACTION two
ROLLBACK TRANSACTION two
GO
ROLLBACK
GO

SELECT  recovery_model_desc
FROM    sys.databases
WHERE   name = 'AdventureWorks2008'
GO

USE Master
GO

ALTER DATABASE AdventureWorks2008
      SET RECOVERY FULL
GO
EXEC sp_addumpdevice 'disk', 'TestAdventureWorks2008', 'C:\Temp\AdventureWorks2008.bak'
EXEC sp_addumpdevice 'disk', 'TestAdventureWorks2008Log',
                              'C:\Temp\AdventureWorks2008Log.bak'
GO
BACKUP DATABASE AdventureWorks2008 TO TestAdventureWorks2008
GO

USE AdventureWorks2008
GO
SELECT count(*)
FROM   SALES.SalesTaxRate

BEGIN TRANSACTION Test WITH MARK 'Test'
DELETE SALES.SalesTaxRate
COMMIT TRANSACTION
GP
BACKUP LOG AdventureWorks2008  to TestAdventureWorks2008Log
GO

USE Master
GO
RESTORE DATABASE AdventureWorks2008 FROM TestAdventureWorks2008
                                                   WITH REPLACE, NORECOVERY
go
RESTORE LOG AdventureWorks2008 FROM TestAdventureWorks2008Log
                                                   WITH STOPBEFOREMARK = 'Test'
GO
USE AdventureWorks2008
GO
SELECT count(*)
FROM   SALES.SalesTaxRate

---------------------------------------------------------------------------------------------
-- Transactions; Transaction Syntax; Basic Transactions
---------------------------------------------------------------------------------------------
Use tempdb
go
SELECT @@TRANCOUNT AS zeroDeep
BEGIN TRANSACTION
SELECT @@TRANCOUNT AS oneDeep
GO

BEGIN TRANSACTION
SELECT @@TRANCOUNT AS twoDeep
COMMIT TRANSACTION --commits very last transaction started with BEGIN TRANSACTION
SELECT @@TRANCOUNT AS oneDeep
GO

COMMIT TRANSACTION
SELECT @@TRANCOUNT AS zeroDeep

GO


BEGIN TRANSACTION
BEGIN TRANSACTION
BEGIN TRANSACTION
BEGIN TRANSACTION
BEGIN TRANSACTION
BEGIN TRANSACTION
BEGIN TRANSACTION
SELECT @@trancount as InTran
ROLLBACK TRANSACTION
SELECT @@trancount as OutTran

GO

COMMIT TRANSACTION
GO

---------------------------------------------------------------------------------------------
-- Transactions; Transaction Syntax; savepoints
---------------------------------------------------------------------------------------------
GO
CREATE SCHEMA arts
CREATE TABLE arts.performer
(
    performerId int identity,
    name varchar(100)
 )
GO
BEGIN TRANSACTION
INSERT INTO arts.performer(name) VALUES ('Elvis Costello')

SAVE TRANSACTION savePoint

INSERT INTO arts.performer(name) VALUES ('Air Supply')

--don't insert Air Supply, yuck! ...
ROLLBACK TRANSACTION savePoint

COMMIT TRANSACTION

SELECT *
FROM arts.performer
GO

---------------------------------------------------------------------------------------------
-- Transactions; Compiled SQL Server Code; Stored Procedures
---------------------------------------------------------------------------------------------
CREATE PROCEDURE tranTest
AS
BEGIN
  SELECT @@TRANCOUNT AS trancount

  BEGIN TRANSACTION
  ROLLBACK TRANSACTION
END
GO
BEGIN TRANSACTION
EXECUTE tranTest
COMMIT TRANSACTION
GO

ALTER PROCEDURE tranTest
AS
BEGIN
  --gives us a unique savepoint name, trim it to 125 characters if the
  --user named the procedure really really large, to allow for nestlevel
  DECLARE @savepoint nvarchar(128) =
      cast(object_name(@@procid) AS nvarchar(125)) +
                         cast(@@nestlevel AS nvarchar(3))

  BEGIN TRANSACTION
  SAVE TRANSACTION @savepoint
    --do something here
  ROLLBACK TRANSACTION @savepoint
  COMMIT TRANSACTION
END
GO

BEGIN TRANSACTION
EXECUTE tranTest
COMMIT TRANSACTION
GO


ALTER PROCEDURE tranTest
AS
BEGIN
  --gives us a unique savepoint name, trim it to 125
  --characters if the user named it really large
  DECLARE @savepoint nvarchar(128) =
               cast(object_name(@@procid) AS nvarchar(125)) +
                                      cast(@@nestlevel AS nvarchar(3))
  --get initial entry level, so we can do a rollback on a doomed transaction
  DECLARE @entryTrancount int = @@trancount

  BEGIN TRY
    BEGIN TRANSACTION
    SAVE TRANSACTION @savepoint

    --do something here
    RAISERROR ('Invalid Operation',16,1)

    COMMIT TRANSACTION
  END TRY
  BEGIN CATCH

   --if the tran is doomed, and the entryTrancount was 0
   --we have to rollback   
    IF xact_state()= -1 and @entryTrancount = 0
        rollback transaction
    --otherwise, we can still save the other activities in the
    --transaction.
    ELSE IF xact_state() = 1 --transaction not doomed, but open
       BEGIN
         ROLLBACK TRANSACTION @savepoint
         COMMIT TRANSACTION
       END

    DECLARE @ERRORmessage nvarchar(4000)
    SET @ERRORmessage = 'Error occurred in procedure ''' + object_name(@@procid)
                        + ''', Original Message: ''' + ERROR_MESSAGE() + ''''
    RAISERROR (@ERRORmessage,16,1)
    RETURN -100
  END CATCH
END
GO

CREATE SCHEMA menu
CREATE TABLE menu.foodItem
(
    foodItemId int not null IDENTITY(1,1)
        CONSTRAINT PKmenu_foodItem PRIMARY KEY,
    name varchar(30) not null
        CONSTRAINT AKmenu_foodItem_name UNIQUE,
    description varchar(60) not null,
        CONSTRAINT CHKmenu_foodItem_name CHECK (name <> ''),
        CONSTRAINT CHKmenu_foodItem_description CHECK (description <> '')
)
GO

CREATE PROCEDURE menu.foodItem$insert
(
    @name   varchar(30),
    @description varchar(60),
    @newFoodItemId int = null output --we will send back the new id here
)
AS
BEGIN
  SET NOCOUNT ON

  --gives us a unique savepoint name, trim it to 125
  --characters if the user named it really large
  DECLARE @savepoint nvarchar(128) =
               cast(object_name(@@procid) AS nvarchar(125)) +
                                      cast(@@nestlevel AS nvarchar(3))
  --get initial entry level, so we can do a rollback on a doomed transaction
  DECLARE @entryTrancount int = @@trancount

  BEGIN TRY
    BEGIN TRANSACTION
    SAVE TRANSACTION @savepoint

    INSERT INTO menu.foodItem(name, description)
    VALUES (@name, @description)

    SET @newFoodItemId = scope_identity() --if you use an instead of trigger
                                          --you will have to use name as a key
                                          --to do the identity "grab" in a SELECT
                                          --query

    COMMIT TRANSACTION
  END TRY
  BEGIN CATCH

   --if the tran is doomed, and the entryTrancount was 0
   --we have to rollback   
    IF xact_state()= -1 and @entryTrancount = 0
        rollback transaction
    --otherwise, we can still save the other activities in the
    --transaction.
    ELSE IF xact_state() = 1 --transaction not doomed, but open
       BEGIN
         ROLLBACK TRANSACTION @savepoint
         COMMIT TRANSACTION
       END

    DECLARE @ERRORmessage nvarchar(4000)
    SET @ERRORmessage = 'Error 0ccurred in procedure ''' + object_name(@@procid)
                        + ''', Original Message: ''' + ERROR_MESSAGE() + ''''
    RAISERROR (@ERRORmessage,16,1)
    RETURN -100
  END CATCH
END
GO

DECLARE @foodItemId int, @retval int
EXECUTE @retval = menu.foodItem$insert  @name ='Burger',
                                        @description = 'Mmmm Burger',
                                        @newFoodItemId = @foodItemId output
SELECT  @retval as returnValue
IF @retval >= 0
    SELECT  foodItemId, name, description
    FROM    menu.foodItem
    where   foodItemId = @foodItemId
GO

DECLARE @foodItemId int, @retval int
EXECUTE @retval = menu.foodItem$insert  @name ='Big Burger',
                                        @description = '',
                                        @newFoodItemId = @foodItemId output
SELECT  @retval as returnValue
IF @retval >= 0
    SELECT  foodItemId, name, description
    FROM    menu.foodItem
    where   foodItemId = @foodItemId
GO


---------------------------------------------------------------------------------------------
-- Transactions; Compiled SQL Server Code; Triggers
---------------------------------------------------------------------------------------------

CREATE TRIGGER menu.foodItem$InsertTrigger
ON menu.foodItem
AFTER INSERT
AS
BEGIN
   DECLARE @rowsAffected int,    --stores the number of rows affected
           @msg varchar(2000)    --used to hold the error message

   SET @rowsAffected = @@rowcount

   --no need to continue on if no rows affected
   IF @rowsAffected = 0 return
 
   SET NOCOUNT ON --to avoid the rowcount messages
   SET ROWCOUNT 0 --in case the client has modified the rowcount

   BEGIN TRY
        --[validation blocks][validation section]
        RAISERROR ('FoodItem''s cannot be done that way',16,1)
       --[modification blocks][modification section]
   END TRY
   BEGIN CATCH
              IF @@trancount > 0
                  ROLLBACK TRANSACTION

              DECLARE @ERROR_MESSAGE nvarchar(4000)
              SET @ERROR_MESSAGE = ERROR_MESSAGE()
              RAISERROR (@ERROR_MESSAGE,16,1)

     END CATCH
END

GO

ALTER PROCEDURE menu.foodItem$insert --modded error handler to return positional output
(
    @name   varchar(30),
    @description varchar(60),
    @newFoodItemId int = null output --we will send back the new id here
)
AS
BEGIN
  SET NOCOUNT ON

  --gives us a unique savepoint name, trim it to 125
  --characters if the user named it really large
  DECLARE @savepoint nvarchar(128) =
               cast(object_name(@@procid) AS nvarchar(125)) +
                                      cast(@@nestlevel AS nvarchar(3))
  --get initial entry level, so we can do a rollback on a doomed transaction
  DECLARE @entryTrancount int = @@trancount

  BEGIN TRY
    BEGIN TRANSACTION
    SAVE TRANSACTION @savepoint

    INSERT INTO menu.foodItem(name, description)
    VALUES (@name, @description)

    SET @newFoodItemId = scope_identity() --if you use an instead of trigger
                                          --you will have to use name as a key
                                          --to do the identity "grab" in a SELECT
                                          --query

    COMMIT TRANSACTION
  END TRY
  BEGIN CATCH
   SELECT 'In error handler'

   --if the tran is doomed, and the entryTrancount was 0
   --we have to rollback   
    IF xact_state()= -1 and @entryTrancount = 0
     begin 
        SELECT 'Transaction Doomed'
        ROLLBACK TRANSACTION
     end
    --otherwise, we can still save the other activities in the
    --transaction.
    ELSE IF xact_state() = 1 --transaction not doomed, but open
       BEGIN
         SELECT 'Savepoint Rollback'
         ROLLBACK TRANSACTION @savepoint
         COMMIT TRANSACTION
       END


    DECLARE @ERRORmessage nvarchar(4000)
    SET @ERRORmessage = 'Error 0ccurred in procedure ''' + object_name(@@procid)
                        + ''', Original Message: ''' + ERROR_MESSAGE() + ''''
    RAISERROR (@ERRORmessage,16,1)
    RETURN -100
  END CATCH
END
GO

DECLARE @foodItemId int, @retval int
EXECUTE @retval = menu.foodItem$insert  @name ='Big Burger',
                                        @description = '',
                                        @newFoodItemId = @foodItemId output
SELECT @retval
GO

DECLARE @foodItemId int, @retval int
EXECUTE @retval = menu.foodItem$insert  @name ='Big Burger',
                                        @description = 'Yummy Big Burger',
                                        @newFoodItemId = @foodItemId output
SELECT @retval
GO

ALTER TRIGGER menu.foodItem$InsertTrigger
ON menu.foodItem
AFTER INSERT
AS
BEGIN
   DECLARE @rowsAffected int,    --stores the number of rows affected
           @msg varchar(2000)    --used to hold the error message

   SET @rowsAffected = @@rowcount

   --no need to continue on if no rows affected
   IF @rowsAffected = 0 return
 
   SET NOCOUNT ON --to avoid the rowcount messages
   SET ROWCOUNT 0 --in case the client has modified the rowcount

   RAISERROR ('FoodItem''s cannot be done that way',16,1)
 
END
GO

DECLARE @foodItemId int, @retval int
EXECUTE @retval = menu.foodItem$insert  @name ='Big Burger',
                                        @description = 'Yummy Big Burger',
                                        @newFoodItemId = @foodItemId output
SELECT @retval
GO

----------------------------------------------------------------------------------
--SQL Server Concurrency Controls; Isolation Levels
----------------------------------------------------------------------------------
GO
CREATE TABLE dbo.testIsolationLevel
(
   testIsolationLevelId int identity(1,1)
                CONSTRAINT PKtestIsolationLevel PRIMARY KEY,
   value varchar(10)
)

INSERT dbo.testIsolationLevel(value)
VALUES ('Value1'),
       ('Value2')
GO

SELECT  case transaction_isolation_level
            when 1 then 'Read Uncomitted'      when 2 then 'Read Committed'
            when 3 then 'Repeatable Read'      when 4 then 'Serializable'
            when 5 then 'Snapshot'             else 'Unspecified'
         end
FROM    sys.dm_exec_sessions
WHERE  session_id = @@spid
GO
----------------------------------------------------------------------------------
--SQL Server Concurrency Controls; Isolation Levels; READ UNCOMMITTED
----------------------------------------------------------------------------------

--CONNECTION A
SET TRANSACTION ISOLATION LEVEL READ COMMITTED --this is the default, just
                                               --setting for emphasis
BEGIN TRANSACTION
INSERT INTO dbo.testIsolationLevel(value)
VALUES('Value3')
GO

--CONNECTION B
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT *
FROM dbo.testIsolationLevel
GO
--CONNECTION A

COMMIT TRANSACTION
GO

----------------------------------------------------------------------------------
--SQL Server Concurrency Controls; Isolation Levels; READ COMMITTED
----------------------------------------------------------------------------------

--CONNECTION A

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

BEGIN TRANSACTION
SELECT * FROM dbo.testIsolationLevel
GO
--CONNECTION B

DELETE FROM dbo.testIsolationLevel
WHERE testIsolationLevelId = 1
GO

--CONNECTION A
SELECT *
FROM dbo.testIsolationLevel
COMMIT TRANSACTION

GO
----------------------------------------------------------------------------------
--SQL Server Concurrency Controls; Isolation Levels; REPEATABLE READ
----------------------------------------------------------------------------------
--CONNECTION A

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

BEGIN TRANSACTION
SELECT * FROM dbo.testIsolationLevel
GO
--CONNECTION B

INSERT INTO dbo.testIsolationLevel(value)
VALUES ('Value4')
GO
--CONNECTION B
DELETE FROM dbo.testIsolationLevel
WHERE value = 'Value3'

GO
--CONNECTION A

SELECT * FROM dbo.testIsolationLevel
COMMIT TRANSACTION

GO

----------------------------------------------------------------------------------
--SQL Server Concurrency Controls; Isolation Levels; SERIALIZABLE
----------------------------------------------------------------------------------
--CONNECTION A

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRANSACTION
SELECT * FROM dbo.testIsolationLevel
GO
--CONNECTION B

INSERT INTO dbo.testIsolationLevel(value)
VALUES ('Value5')
GO
--CONNECTION A

SELECT * FROM dbo.testIsolationLevel
COMMIT TRANSACTION
GO
----------------------------------------------------------------------------------
--SQL Server Concurrency Controls; Isolation Levels; SNAPSHOT
----------------------------------------------------------------------------------
ALTER DATABASE tempDb
SET ALLOW_SNAPSHOT_ISOLATION ON
GO
--CONNECTION A

SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRANSACTION
SELECT * from dbo.testIsolationLevel
GO
--CONNECTION B

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
INSERT INTO dbo.testIsolationLevel(value)
VALUES ('Value6')

GO

--CONNECTION B

DELETE FROM dbo.testIsolationLevel
WHERE  value = 'Value4'
GO

--CONNECTION A

UPDATE  dbo.testIsolationLevel
SET     value = 'Value2-mod'
WHERE   testIsolationLevelId = 2
GO

--CONNECTION A

COMMIT TRANSACTION
SELECT * from dbo.testIsolationLevel
GO

--CONNECTION A
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRANSACTION

--touch the data
SELECT * FROM dbo.testIsolationLevel

GO
--CONNECTION B
SET TRANSACTION ISOLATION LEVEL READ COMMITTED --any will do

UPDATE dbo.testIsolationLevel
SET    value = 'Value5-mod'
WHERE  testIsolationLevelId = 5 --might be different in yours
GO

--CONNECTION A
UPDATE dbo.testIsolationLevel
SET   value = 'Value5-mod'
WHERE testIsolationLevelId = 5 --might be different in yours
GO

----------------------------------------------------------------------------------
--Coding for Integrity and Concurrency; Pessimistic Locking
----------------------------------------------------------------------------------

--CONNECTION A

BEGIN TRANSACTION
   DECLARE @result int
   EXEC @result = sp_getapplock @Resource = 'invoiceId=1', @LockMode = 'Exclusive'
   SELECT @result
GO

--CONNECTION B
BEGIN TRANSACTION
   DECLARE @result int
   EXEC @result = sp_getapplock @Resource = 'invoiceId=1', @LockMode = 'Exclusive'
   PRINT @result
GO

--CONNECTION B

BEGIN TRANSACTION
SELECT  APPLOCK_TEST('public','invoiceId=1','Exclusive','Transaction')
                                                        as CanTakeLock
ROLLBACK TRANSACTION
GO

--CLEANUP on Connection A
ROLLBACK TRANSACTION
GO

create table applock
(
    applockId int primary key,  --the value that we will be generating
                                --with the procedure
    connectionId int,           --holds the spid of the connection so you can
                                --who creates the row
    insertTime datetime default (getdate()) --the time the row was created, so
                                             --you can see the progression
)

GO

create procedure applock$test
(
    @connectionId int,
    @useApplockFlag bit = 1,
    @stepDelay varchar(10) = '00:00:00'
) as
set nocount on
begin try
    begin transaction
        declare @retval int = 1
        if @useApplockFlag = 1 --turns on and off the applock for testing
            begin
                exec @retval = sp_getapplock @Resource = 'applock$test',
                                                    @LockMode = 'exclusive';
                if @retval < 0
                    begin
                        declare @errorMessage nvarchar(200)
                        set @errorMessage = case @retval
                                    when -1 then 'Applock request timed out.'
                                    when -2 then 'Applock request canceled.'
                                    when -3 then 'Applock involved in deadlock'
                                else 'Parameter validation or other call error.'
                                             end
                        raiserror (@errorMessage,16,1)
                    end
            end

    --get the next primary key value
    declare @applockId int   
    set @applockId = coalesce((select max(applockId) from applock),0) + 1

    --delay for parameterized amount of time to slow down operations
    --and guarantee concurrency problems
    waitfor delay @stepDelay

    --insert the next value
    insert into applock(applockId, connectionId)
    values (@applockId, @connectionId)

    --won't have much effect on this code, since the row will now be
    --exclusively locked, and the max will need to see the new row to
    --be of any effect.
    exec @retval = sp_releaseapplock @Resource = 'applock$test'

    --this releases the applock too
    commit transaction
end try
begin catch
    --if there is an error, rollback and display it.
    if @@trancount > 0
        rollback transaction
        select cast(error_number() as varchar(10)) + ':' + error_message()
end catch

GO
waitfor time '23:46' --set for a time to run so multiple batches
                            --can simultaneously execute
go
exec applock$test @@spid, 1 -- <1=use applock, 0 = don't use applock>,
            ,'00:00:00.001'--'delay in hours:minutes:seconds.parts of seconds'
go 10000 --runs the batch 10000 times in SSMS
GO


----------------------------------------------------------------------------------
--Coding for Integrity and Concurrency; Optimistic Locking; Adding Optimistic Lock Columns
----------------------------------------------------------------------------------
GO
CREATE SCHEMA hr
CREATE TABLE hr.person
(
     personId int IDENTITY(1,1) CONSTRAINT PKperson primary key,
     firstName varchar(60) NOT NULL,
     middleName varchar(60) NOT NULL,
     lastName varchar(60) NOT NULL,

     dateOfBirth date NOT NULL,
     rowLastModifyTime datetime NOT NULL
         CONSTRAINT DFLTperson_rowLastModifyTime default getdate(),
     rowModifiedByUserIdentifier nvarchar(128) NOT NULL
         CONSTRAINT DFLTperson_rowModifiedByUserIdentifier default suser_sname()

)
GO

CREATE TRIGGER hr.person$InsteadOfUpdate
ON hr.person
INSTEAD OF UPDATE AS
BEGIN

    --stores the number of rows affected
   DECLARE @rowsAffected int = @@rowcount,
           @msg varchar(2000) = ''    --used to hold the error message

      --no need to continue on if no rows affected
   IF @rowsAffected = 0 return

   SET NOCOUNT ON --to avoid the rowcount messages
   SET ROWCOUNT 0 --in case the client has modified the rowcount

   BEGIN TRY
          --[validation blocks]
          --[modification blocks]
          --remember to update ALL columns when building instead of triggers
          UPDATE hr.person
          SET    firstName = inserted.firstName,
                 middleName = inserted.middleName,
                 lastName = inserted.lastName,
                 dateOfBirth = inserted.dateOfBirth,
                 rowLastModifyTime = default, -- set the value to the default
                 rowModifiedByUserIdentifier = default
          FROM   hr.person                             
                     JOIN inserted
                             on hr.person.personId = inserted.personId
   END TRY
   BEGIN CATCH
              IF @@trancount > 0
                 ROLLBACK TRANSACTION

              --EXECUTE dbo.errorLog$insert

              DECLARE @ERROR_MESSAGE varchar(8000)
              SET @ERROR_MESSAGE = ERROR_MESSAGE()
              RAISERROR (@ERROR_MESSAGE,16,1)

     END CATCH
END

GO

INSERT INTO hr.person (firstName, middleName, lastName, dateOfBirth)
VALUES ('Paige','O','Anxtent','19391212')

SELECT *
FROM   hr.person
GO

UPDATE hr.person
SET     middleName = 'Ona'
WHERE   personId = 1

SELECT rowLastModifyTime
FROM   hr.person

GO

ALTER TABLE hr.person
  ADD rowversion rowversion
GO
SELECT personId, rowversion
FROM   hr.person
GO
UPDATE  hr.person
SET     firstName = 'Paige' --no actual change occurs
WHERE   personId = 1
Go
SELECT personId, rowversion
FROM   hr.person
GO

----------------------------------------------------------------------------------
--Coding for Integrity and Concurrency; Optimistic Locking; Coding for Row-Level Optimistic Locking
----------------------------------------------------------------------------------
GO
UPDATE  hr.person
SET     firstName = 'Headley'
WHERE   personId = 1  --include the key
  and   firstName = 'Paige'
  and   middleName = 'ona'
  and   lastName = 'Anxtent'
  and   dateOfBirth = '19391212'
GO
UPDATE  hr.person
SET     firstName = 'Fred'
WHERE   personId = 1  --include the key
  and   rowLastModifyTime = '2005-07-30 00:28:28.397'
GO
UPDATE  hr.person
SET     firstName = 'Fred'
WHERE   personId = 1
  and   rowversion = 0x00000000000007D3
GO
DELETE FROM hr.person
WHERE   personId = 1
  And   rowversion = 0x00000000000007D3
GO
----------------------------------------------------------------------------------
--Coding for Integrity and Concurrency; Optimistic Locking; Logical Unit of Work
----------------------------------------------------------------------------------

CREATE SCHEMA invoicing
go
--leaving off who invoice is for
CREATE TABLE invoicing.invoice
(
     invoiceId int IDENTITY(1,1),
     number varchar(20) NOT NULL,
     objectVersion rowversion not null,
     constraint PKinvoicing_invoice primary key (invoiceId)
)
--also forgetting what product that the line item is for
CREATE TABLE invoicing.invoiceLineItem

(
     invoiceLineItemId int NOT NULL,
     invoiceId int NULL,
     itemCount int NOT NULL,
     cost int NOT NULL,
      constraint PKinvoicing_invoiceLineItem primary key (invoiceLineItemId),
      constraint FKinvoicing_invoiceLineItem$references$invoicing_invoice
            foreign key (invoiceId) references invoicing.invoice(invoiceId)
)
GO

CREATE PROCEDURE invoiceLineItem$del
(
    @invoiceId int, --we pass this because the client should have it
                    --with the invoiceLineItem row
    @invoiceLineItemId int,
    @objectVersion rowversion
) as
  BEGIN
    --gives us a unique savepoint name, trim it to 125
    --characters if the user named it really large
    DECLARE @savepoint nvarchar(128) =
                          cast(object_name(@@procid) AS nvarchar(125)) +
                                         cast(@@nestlevel AS nvarchar(3))
    --get initial entry level, so we can do a rollback on a doomed transaction
    DECLARE @entryTrancount int = @@trancount

    BEGIN TRY
        BEGIN TRANSACTION
        SAVE TRANSACTION @savepoint

        UPDATE  invoice
        SET     number = number
        WHERE   invoiceId = @invoiceId
          And   objectVersion = @objectVersion

        DELETE  invoiceLineItem
        FROM    invoiceLineItem
        WHERE   invoiceLineItemId = @invoiceLineItemId

        COMMIT TRANSACTION

    END TRY
    BEGIN CATCH

        --if the tran is doomed, and the entryTrancount was 0
        --we have to rollback   
        IF xact_state()= -1 and @entryTrancount = 0
            rollback transaction
        --otherwise, we can still save the other activities in the
       --transaction.
       ELSE IF xact_state() = 1 --transaction not doomed, but open
         BEGIN
             ROLLBACK TRANSACTION @savepoint
             COMMIT TRANSACTION
         END

    DECLARE @ERRORmessage nvarchar(4000)
    SET @ERRORmessage = 'Error occurred in procedure ''' +
          object_name(@@procid) + ''', Original Message: '''
          + ERROR_MESSAGE() + ''''
    RAISERROR (@ERRORmessage,16,1)
    RETURN -100

     END CATCH
 END

原文地址:https://www.cnblogs.com/dushu/p/2509034.html