{"id":1675,"date":"2015-11-17T16:30:00","date_gmt":"2015-11-17T14:30:00","guid":{"rendered":"https:\/\/www.sqlinthewild.co.za\/?p=1675"},"modified":"2015-12-17T11:23:50","modified_gmt":"2015-12-17T09:23:50","slug":"savepoints-and-conditional-transactions","status":"publish","type":"post","link":"https:\/\/www.sqlinthewild.co.za\/index.php\/2015\/11\/17\/savepoints-and-conditional-transactions\/","title":{"rendered":"Savepoints and conditional transactions"},"content":{"rendered":"<p>This is the second in a short series on transactions. In the <a href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2015\/11\/03\/a-mess-of-nested-transactions\/\">previous part<\/a> I discussed nested transactions and showed that they actually don\u2019t exist. SQL has syntax which may suggest that nested transactions exist, but it\u2019s just a syntactical lie.<\/p>\n<p>One thing I suggested in that post was that transactions should be started and committed (or rolled back) at one level only.<\/p>\n<p>That\u2019s a nice idea, but it often doesn\u2019t work when dealing with existing systems which may have been developed with less attention to transactions than ideal. What happens when a stored procedure needs to run in a transaction and potentially roll back its changes, but can be called either within an existing transaction or independently?<\/p>\n<p>The answer to that comes in two parts, first the conditional starting of a transaction and secondly save points.<\/p>\n<p>I should mention that this is fairly advanced transaction management, it\u2019s not something I see in production code very often. It should be considered carefully before being used, as if the people supporting the code don\u2019t understand this, there could be trouble.<\/p>\n<p>Let\u2019s start with some setup. Some random tables and a stored procedure which inserts into parent and child tables.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE Parent (\r\n\u00a0\u00a0 \u00a0SomeID INT IDENTITY PRIMARY KEY,\r\n\u00a0\u00a0 \u00a0SomeRandomValue VARCHAR(50),\r\n\u00a0\u00a0 \u00a0NotificationRequired BIT DEFAULT 0\r\n);\r\n\r\nCREATE TABLE Child (\r\n\u00a0\u00a0 \u00a0SomeOtherID INT IDENTITY PRIMARY KEY,\r\n\u00a0\u00a0 \u00a0ParentID INT,\r\n\u00a0\u00a0 \u00a0State INT,\r\n\u00a0\u00a0 \u00a0SomeValue INT\r\n);\r\n\r\nCREATE TABLE Notifications (\r\n\u00a0\u00a0 \u00a0ParentID INT,\r\n\u00a0\u00a0 \u00a0NotificationText VARCHAR(1000),\r\n\u00a0\u00a0 \u00a0NotificationDate DATETIME DEFAULT GETDATE()\r\n);\r\n<\/pre>\n<p>Let\u2019s say an outer procedure which may insert into the parent table, inserts into the child table and then calls another procedure. The other procedure inserts a notification into a table.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE PROCEDURE OuterProc (@SomeValue VARCHAR(50), @SomeOtherValue INT)\r\nAS\r\n\r\nDECLARE @ParentID INT;\r\n\r\nBEGIN TRY\r\n\u00a0\u00a0\u00a0 BEGIN TRANSACTION\r\n\r\n\u00a0\u00a0\u00a0 IF NOT EXISTS ( SELECT\u00a0 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeRandomValue = @SomeValue )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INSERT\u00a0 INTO dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (SomeRandomValue)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 VALUES\u00a0 (@SomeValue);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 @ParentID = @@IDENTITY;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END\r\n\u00a0\u00a0\u00a0 ELSE\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 @ParentID = SomeID\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeRandomValue = @SomeValue;\r\n\r\n\u00a0\u00a0\u00a0 INSERT\u00a0 INTO dbo.Child\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (ParentID, SomeValue)\r\n\u00a0\u00a0\u00a0 VALUES\u00a0 (@ParentID, @SomeOtherValue);\r\n\r\n\u00a0\u00a0\u00a0 UPDATE\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0 SET\u00a0\u00a0\u00a0\u00a0 NotificationRequired = 1\r\n\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeID = @ParentID;\r\n\r\n\u00a0\u00a0\u00a0 EXEC NotificationProc @ParentID;\r\n\r\n\u00a0\u00a0\u00a0 COMMIT TRANSACTION\r\nEND TRY\r\nBEGIN CATCH\r\n\u00a0\u00a0\u00a0 ROLLBACK TRANSACTION;\r\n-- other error handling here\r\nEND CATCH\r\nGO<\/pre>\n<p>There are some potential race conditions in there, but let\u2019s ignore those for now, it\u2019s not the point of this post.<\/p>\n<p>The requirement for the notification procedure is that if it throws an error all of its changes must be undone however the changes in the outer procedure must still commit. The notification procedure can be called independently or from within the OuterProc<\/p>\n<p>The starting code for the notification procedure is:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE PROCEDURE NotificationProc (@AnID INT)\r\nAS\r\n\u00a0\u00a0\u00a0 UPDATE\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0 SET\u00a0\u00a0\u00a0\u00a0 NotificationRequired = 0\r\n\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeID = @AnID\r\n\r\n\u00a0\u00a0\u00a0 INSERT\u00a0 INTO dbo.Notifications\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (ParentID,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NotificationText,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NotificationDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 SomeID,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SomeRandomValue,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GETDATE()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeID = @AnID;\r\n\r\nGO<\/pre>\n<p>This currently has no transaction management at all. Let\u2019s start by implementing the first requirement, if called from outside of any transaction, it must begin a transaction and either commit or rollback all changes.<\/p>\n<p>To do this, we can check the value of @@Trancount to see whether a transaction should be started or not.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE PROCEDURE NotificationProc (@AnID INT)\r\nAS\r\n\u00a0\u00a0\u00a0 DECLARE @Independent BIT = 0;\r\n\u00a0\u00a0\u00a0 IF @@TRANCOUNT = 0\u00a0 -- proc called from outside of any transaction\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SET @Independent = 1;\r\n\r\n\u00a0\u00a0\u00a0 BEGIN TRY\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @Independent = 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN TRANSACTION;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 UPDATE\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SET\u00a0\u00a0\u00a0\u00a0 NotificationRequired = 0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeID = @AnID;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INSERT\u00a0 INTO dbo.Notifications\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (ParentID,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NotificationText,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NotificationDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 SomeID,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SomeRandomValue,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GETDATE()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeID = @AnID;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @Independent = 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 COMMIT TRANSACTION;\r\n\r\n\u00a0\u00a0\u00a0 END TRY\r\n\u00a0\u00a0\u00a0 BEGIN CATCH\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @Independent = 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ROLLBACK TRANSACTION;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 THROW;\r\n\r\n\u00a0\u00a0\u00a0 END CATCH;\r\n\r\nGO<\/pre>\n<p>The THROW is there so that any error can be passed up to the caller to be logged or otherwise handled. It also ensures that it fires any catch block in the calling code.<\/p>\n<p>That\u2019s the first half, the transaction handling if called independently, but it doesn\u2019t help with the second requirement, that the changes made in the notification procedure roll back if there\u2019s an error, but leave the changes made in the calling procedure unaffected and the transaction open. To do that, we need a savepoint.<\/p>\n<p>From <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms188378.aspx\">Books Online<\/a>:<\/p>\n<blockquote><p>A user can set a savepoint, or marker, within a transaction. The savepoint defines a location to which a transaction can return if part of the transaction is conditionally canceled. If a transaction is rolled back to a savepoint, it must proceed to completion with more Transact-SQL statements if needed and a COMMIT TRANSACTION statement, or it must be canceled altogether by rolling the transaction back to its beginning.<\/p><\/blockquote>\n<p>It\u2019s not a nested transaction, it\u2019s not like Oracle\u2019s autonomous transactions, it\u2019s just a point within the transaction to which we can roll back to. It lets us undo the most recent part of a transaction.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">ALTER PROCEDURE NotificationProc (@AnID INT)\r\nAS\r\n\u00a0\u00a0\u00a0 DECLARE @Independent BIT = 0;\r\n\u00a0\u00a0\u00a0 IF @@TRANCOUNT = 0\u00a0 -- proc is being called from outside of any transaction\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SET @Independent = 1;\r\n\r\n\u00a0\u00a0\u00a0 BEGIN TRY\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @Independent = 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 BEGIN TRANSACTION;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SAVE TRANSACTION Notifications; -- Define a savepoint which we can roll the transaction back to.\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 UPDATE\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SET\u00a0\u00a0\u00a0\u00a0 NotificationRequired = 0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeID = @AnID;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INSERT\u00a0 INTO dbo.Notifications\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (ParentID,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NotificationText,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NotificationDate\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 SomeID,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SomeRandomValue,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GETDATE()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\u00a0\u00a0\u00a0 dbo.Parent\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE\u00a0\u00a0 SomeID = @AnID;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @Independent = 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 COMMIT TRANSACTION;\r\n\r\n\u00a0\u00a0\u00a0 END TRY\r\n\u00a0\u00a0\u00a0 BEGIN CATCH\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 IF @Independent = 1\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ROLLBACK TRANSACTION; -- roll back the entire transaction\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ROLLBACK TRANSACTION Notifications; -- roll back to the save point\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT\u00a0 ERROR_NUMBER(),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ERROR_MESSAGE(),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ERROR_LINE(); -- In reality, log this somewhere\r\n\r\n\u00a0\u00a0\u00a0 END CATCH;\r\n\r\nGO<\/pre>\n<p>Looks good, let\u2019s test. To test, I\u2019m going to add a check constraint to the Notifications table that will be violated by the insert in the Notification procedure. This is to simulate the kind of errors that can happen in a real system (key violations, data type conversion failures, check constraint violations, etc). I\u2019ll also add some sample data to the Parent table.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">ALTER TABLE Notifications ADD CONSTRAINT Testing CHECK (NotificationDate &gt; '2020-01-01')\r\n\r\nINSERT INTO dbo.Parent (SomeRandomValue, NotificationRequired)\r\nVALUES\u00a0 ('abc',0), ('def',1),('ghi',0),('jkl',1),('mno',1);<\/pre>\n<p>First test, outside of a transaction. The requirement is that neither of the changes in the proc remain after the execution (because of the error)<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT SomeID, SomeRandomValue, NotificationRequired FROM dbo.Parent WHERE SomeID = 2;\r\n\r\nEXEC dbo.NotificationProc @AnID = 2;\r\n\r\nSELECT SomeID, SomeRandomValue, NotificationRequired FROM dbo.Parent WHERE SomeID = 2;\r\nSELECT * FROM dbo.Notifications WHERE ParentID = 2;<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2015\/11\/IndependentTransaction.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"IndependentTransaction\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2015\/11\/IndependentTransaction_thumb.png\" alt=\"IndependentTransaction\" width=\"484\" height=\"322\" border=\"0\" \/><\/a><\/p>\n<p>Success. When run independently the two data modifications were run in a transaction and when the error occurred, were rolled back.<\/p>\n<p>Now let\u2019s try from within an existing transaction. The changes made in the outer procedure (insert of a row into Child and update NotificationRequired to 1 in Parent must commit, but the changes made in the inner proc must not)<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT SomeID, SomeRandomValue, NotificationRequired FROM dbo.Parent WHERE SomeID = 1;\r\n\r\nEXEC dbo.OuterProc @SomeValue = 'abc', @SomeOtherValue = 7;\r\n\r\nSELECT SomeID, SomeRandomValue, NotificationRequired FROM dbo.Parent WHERE SomeID = 1;\r\nSELECT SomeOtherID, ParentID, SomeValue FROM dbo.Child WHERE ParentID = 1;\r\nSELECT * FROM dbo.Notifications WHERE ParentID = 1;<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2015\/11\/NestedTransaction.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"NestedTransaction\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2015\/11\/NestedTransaction_thumb.png\" alt=\"NestedTransaction\" width=\"484\" height=\"332\" border=\"0\" \/><\/a><\/p>\n<p>Again, exactly the desired behaviour. The changes made in the outer procedure were committed, the changes in the inner procedure, the procedure where the error was thrown, were rolled back.<\/p>\n<p>Used correctly, savepoints can be a powerful mechanism for managing transactions in SQL Server. Unfortunately they\u2019re not well known and as such their use can also make code much harder for later developers to debug.<\/p>\n<p>The <a href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2015\/12\/01\/why-would-you-want-to-name-a-transaction\/\">next part<\/a> of the series on transactions will look at why (and why not) you&#8217;d want to name a transaction.<\/p>\n<p>The full list is:<\/p>\n<ol>\n<li><a href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2015\/11\/03\/a-mess-of-nested-transactions\/\">A Mess of Nested Transactions<\/a><\/li>\n<li>Savepoints and conditional transactions (This post)<\/li>\n<li><a href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2015\/12\/01\/why-would-you-want-to-name-a-transaction\/\">Why would you want to name a transaction?<\/a><\/li>\n<li><a href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2015\/12\/15\/when-naming-transactions-causes-an-error\/\">When naming transactions causes an error<\/a><\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>This is the second in a short series on transactions. In the previous part I discussed nested transactions and showed that they actually don\u2019t exist. SQL has syntax which may suggest that nested transactions exist, but it\u2019s just a syntactical&#8230; <a class=\"read-more-button\" href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2015\/11\/17\/savepoints-and-conditional-transactions\/\">(Read more)<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"Blog post: Savepoints and conditional transactions","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[15,16,17],"tags":[],"class_list":["post-1675","post","type-post","status-publish","format-standard","hentry","category-sql-server","category-syndication","category-t-sql"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7h6n-r1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1675","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/comments?post=1675"}],"version-history":[{"count":17,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1675\/revisions"}],"predecessor-version":[{"id":1737,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1675\/revisions\/1737"}],"wp:attachment":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=1675"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/categories?post=1675"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/tags?post=1675"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}