{"id":1041,"date":"2010-11-25T00:00:00","date_gmt":"2010-11-25T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/modifying-contiguous-time-periods-in-a-history-table\/"},"modified":"2021-09-29T16:21:55","modified_gmt":"2021-09-29T16:21:55","slug":"modifying-contiguous-time-periods-in-a-history-table","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/modifying-contiguous-time-periods-in-a-history-table\/","title":{"rendered":"Modifying Contiguous Time Periods in a History Table"},"content":{"rendered":"<div id=\"pretty\">\n<h1>Modifying Contiguous Time Periods <\/h1>\n<p class=\"MsoNormal\">This article explains how to modify contiguous time periods that were described in Joe Celko&#8217;s article &#8216;<a href=\"http:\/\/www.simple-talk.com\/sql\/t-sql-programming\/contiguous-time-periods\/\">Contiguous Time Periods in SQL<\/a>&#8216;.&#160; Joe describes the table itself that he calls the &#8216;Kuznetsov History Table&#8217;. He explains how it is used to store contiguous time intervals with constraint to ensure that the date periods really are contiguous, The editor suggested that I give a brief description of how to modify the data in the History table as this may not be entirely obvious.<\/p>\n<p class=\"MsoNormal\">When trusted constraints enforce data integrity, the data is guaranteed to be valid at the end of any statement, even if it is not committed. When we modify contiguous time periods, in order to get from one valid state to another we may need to insert a row and update another one, or we may need to delete a row and update another one. This is one of those cases when MERGE really shines &#8211; it allows us to get from one valid state to another in one statement, inserting, updating, and deleting rows as needed.<\/p>\n<h2>Prerequisites.<\/h2>\n<p class=\"MsoNormal\">All we need is an empty table, as follows:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\nCREATE TABLE dbo.Responsibilities\n&#160;&#160;&#160;&#160;(\n&#160;&#160;&#160;&#160;&#160;&#160;TaskId INT NOT NULL ,\n&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge VARCHAR(50) NOT NULL ,\n&#160;&#160;&#160;&#160;&#160;&#160;StartedAt DATETIME NOT NULL ,\n&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt DATETIME NOT NULL ,\n&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt DATETIME NULL ,\n&#160;&#160;&#160;&#160;&#160;&#160;CONSTRAINT PK_Responsibilities_TaskId_FinishedAt \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRIMARY KEY ( TaskId, FinishedAt ) ,\n&#160;&#160;&#160;&#160;&#160;&#160;CONSTRAINT UNQ_Responsibilities_TaskId_PreviousFinishedAt \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; UNIQUE ( TaskId, PreviousFinishedAt ) ,\n&#160;&#160;&#160;&#160;&#160;&#160;CONSTRAINT FK_Responsibilities_TaskId_PreviousFinishedAt \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FOREIGN KEY ( TaskId, PreviousFinishedAt ) \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; REFERENCES dbo.Responsibilities ( TaskId, FinishedAt ) ,\n&#160;&#160;&#160;&#160;&#160;&#160;CONSTRAINT CHK_Responsibilities_PreviousFinishedAt_NotAfter_StartedAt \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CHECK ( PreviousFinishedAt &lt;= StartedAt ) ,\n&#160;&#160;&#160;&#160;&#160;&#160;CONSTRAINT CHK_Responsibilities_StartedAt_Before_FinishedAt \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CHECK ( StartedAt &lt; FinishedAt )\n&#160;&#160;&#160;&#160;) ; &#160;\n\n<\/pre>\n<h2>Some Easy Modifications.<\/h2>\n<p class=\"MsoNormal\">It is easy to begin a new series of time periods<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT&#160;&#160;INTO dbo.Responsibilities\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;)\nVALUES&#160;&#160;( 1 ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Joe' ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101002' ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101023' ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;NULL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;),\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( 1 ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Andrew' ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101023' ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101103' ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101023'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;) ; &#160;\n<\/pre>\n<p class=\"MsoNormal\">It is just as easy to continue adding periods to the end of the series.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT&#160;&#160;INTO dbo.Responsibilities\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;)\nSELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Alex' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101120' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101103' AS PreviousFinishedAt\nUNION ALL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \nSELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Andrew' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101126' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101127' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS PreviousFinishedAt &#160;\n<\/pre>\n<p class=\"MsoNormal\">Deleting one or more rows from the end is just as easy, and we shall skip the example. As we have seen, it is easy to perform typical, the most common operations against history of periods. <\/p>\n<p class=\"MsoNormal\">However, some other operations are less easy and need more explanations. Now that we have enough test data, let us move on to more complex examples. Here is the test data at this moment:<\/p>\n<p class=\"MsoNormal\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1191-clip_image002.jpg\" width=\"469\" height=\"76\" alt=\"1191-clip_image002.jpg\" \/><\/p>\n<h2>Adding periods to the beginning.<\/h2>\n<p class=\"MsoNormal\">Each series of periods has exactly one first period &#8211; this is enforced by the following constraint: <span class=\"STCodeinTextChar\"><strong>UNQ_Responsibilities_TaskId_PreviousFinishedAt<\/strong><\/span>.<\/p>\n<p class=\"MsoNormal\">As a result, when we are inserting one or more periods to the beginning of the series, we have to update the period that used to be the first before, as follows:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\nMERGE dbo.Responsibilities AS Target\n&#160;&#160;&#160;&#160;USING \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( SELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Alex' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20090301' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20090306' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;NULL AS PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;UNION ALL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Joe' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101002' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101023' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20090306' AS PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;) AS source ( TaskId, PersonInCharge, StartedAt, FinishedAt,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt )\n&#160;&#160;&#160;&#160;ON ( Target.TaskId = source.TaskId\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND Target.StartedAt = source.StartedAt\n&#160;&#160;&#160;&#160;&#160;&#160; )\n&#160;&#160;&#160;&#160;WHEN MATCHED \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;UPDATE\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SET&#160;&#160; PersonInCharge = source.PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt = source.StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt = source.FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt = source.PreviousFinishedAt\n&#160;&#160;&#160;&#160;WHEN NOT MATCHED \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN&#160;&#160; \nINSERT&#160;&#160;(\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;) VALUES\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( source.TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;); &#160;\n<\/pre>\n<p class=\"MsoNormal\">\n<p class=\"MsoNormal\">Now we will verify that our test data looks as expected, with a new row at the beginning, and <strong>PreviousFinishedAt <\/strong>column is modified to point to the new row for the row that used to be the first before this modification:<\/p>\n<p class=\"MsoNormal\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1191-clip_image004.jpg\" width=\"469\" height=\"90\" alt=\"1191-clip_image004.jpg\" \/><\/p>\n<p class=\"MsoNormal\">We are also going to discuss some other scenarios, such as adding\/deleting periods in the middle of the series. In all these cases we shall be using MERGE, and the DML looks quite similar, so let us wrap it up in a stored procedure.<\/p>\n<h2>Creating a stored procedure<\/h2>\n<p class=\"MsoNormal\">The following code implements this merging functionality with a stored procedure that uses a table valued parameter, as follows:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\nCREATE TYPE ResponsibilitiesChangesList AS TABLE ( \nTaskId INT NOT NULL ,\nPersonInCharge VARCHAR(50) NOT NULL ,\nStartedAt DATETIME NOT NULL ,\nFinishedAt DATETIME NOT NULL ,\nPreviousFinishedAt DATETIME NULL,\nDeleteThisRow CHAR(1)\n) ;\nGO\n\nCREATE PROCEDURE dbo.MergeResponsibilities\n&#160;&#160;&#160;&#160;@changes ResponsibilitiesChangesList READONLY\n\nAS \n&#160;&#160;&#160;&#160;BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SET NOCOUNT ON ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;MERGE dbo.Responsibilities AS Target\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;USING \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( SELECT&#160;&#160;&#160;&#160;TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM&#160;&#160;&#160;&#160;&#160;&#160;@changes AS c\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;) AS source ( TaskId, PersonInCharge, StartedAt, FinishedAt,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt, DeleteThisRow )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ON ( Target.TaskId = source.TaskId\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND Target.StartedAt = source.StartedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHEN MATCHED AND DeleteThisRow = 'Y'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;DELETE\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHEN MATCHED \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;UPDATE&#160;&#160;&#160;&#160;&#160;&#160;SET\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge = source.PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt = source.StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt = source.FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt = source.PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHEN NOT MATCHED \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN&#160;&#160; \nINSERT&#160;&#160;(\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; VALUES\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( source.TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;source.PreviousFinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;) ;\n&#160;&#160;&#160;&#160;END ; &#160;\n<\/pre>\n<p class=\"MsoNormal\">Let us use this stored procedure.<\/p>\n<h2>Filling a gap in the middle of the series<\/h2>\n<p class=\"MsoNormal\">The following code fills the gap on November 25th.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\nDECLARE @changes ResponsibilitiesChangesList ;\nINSERT&#160;&#160;INTO @changes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Michelle' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101126' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS PreviousFinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'N' AS DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;UNION ALL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Andrew' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101126' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101127' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101126' AS PreviousFinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'N' AS DeleteThisRow ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;\nEXEC dbo.MergeResponsibilities @changes = @changes ; &#160;\n<\/pre>\n<p class=\"MsoNormal\">Here is the data after this modification, with a period added in the middle fo the series:<\/p>\n<p class=\"MsoNormal\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1191-clip_image006.jpg\" width=\"469\" height=\"110\" alt=\"1191-clip_image006.jpg\" \/><\/p>\n<h2>Deleting a period in the middle of the series<\/h2>\n<p class=\"MsoNormal\">The following code deletes the period added in the previous example.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\nDECLARE @changes ResponsibilitiesChangesList ;\nINSERT&#160;&#160;INTO @changes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Michelle' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101126' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS PreviousFinishedAt,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Y' AS DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;UNION ALL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Andrew' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101126' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101127' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS PreviousFinishedAt,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'N' AS DeleteThisRow ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;\nEXEC dbo.MergeResponsibilities @changes = @changes ; &#160;\n\n<\/pre>\n<p class=\"MsoNormal\">Here is the data after this modification:<\/p>\n<p class=\"MsoNormal\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1191-clip_image008.jpg\" width=\"469\" height=\"93\" alt=\"1191-clip_image008.jpg\" \/><\/p>\n<h2>Inserting two periods in the middle, and adjusting an exaisting period to make room for them.<\/h2>\n<p class=\"MsoNormal\">This is the last and most complex example involving our stored procedure:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\nDECLARE @changes ResponsibilitiesChangesList ;\nINSERT&#160;&#160;INTO @changes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;( TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;)\nSELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Alex' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101120' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101122' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101103' AS PreviousFinishedAt,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'N' AS DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;UNION ALL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Michelle' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101122' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101123' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101122' AS PreviousFinishedAt,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'N' AS DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;UNION ALL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT&#160;&#160;&#160;&#160;1 AS TaskId ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'Alex' AS PersonInCharge ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101123' AS StartedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101125' AS FinishedAt ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'20101123' AS PreviousFinishedAt,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;'N' AS DeleteThisRow\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;\nEXEC dbo.MergeResponsibilities @changes = @changes ; &#160;\n<\/pre>\n<p class=\"MsoNormal\">Here is the data after running this script, with modifications in red rectangles:<\/p>\n<p class=\"MsoNormal\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1191-clip_image010.jpg\" width=\"469\" height=\"124\" alt=\"1191-clip_image010.jpg\" \/><\/p>\n<h2>Getting by on SQL Server 2005, without MERGE<\/h2>\n<p class=\"MsoNormal\">We do not have MERGE on SQL Server 2005, so we have to use more complex ways to modify, such as delete and reinsert the whole series, or use more than one command to implement the change. The following operations are available:<\/p>\n<ul>\n<li>Inserting periods at the end  <\/li>\n<li>Deleting periods at the end  <\/li>\n<li>Updating periods from one valid state to another <\/li>\n<\/ul>\n<p class=\"MsoNormal\">For example, to delete the first period, we have to use an update to move it to the end, and then delete it, as follows:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\nSET XACT_ABORT ON ;\nBEGIN TRAN ;\n\nUPDATE&#160;&#160;dbo.Responsibilities\nSET&#160;&#160;&#160;&#160; FinishedAt = CASE WHEN StartedAt = '20090301' THEN '20990909'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ELSE FinishedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;StartedAt = CASE WHEN StartedAt = '20090301' THEN '20990908'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE StartedAt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;END ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;PreviousFinishedAt = CASE WHEN StartedAt = '20090301' THEN '20101127'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ELSE NULL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END\nWHERE&#160;&#160; TaskId = 1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;AND StartedAt IN ( '20090301', '20101002' ) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;\nSELECT&#160;&#160;*\nFROM&#160;&#160;&#160;&#160;dbo.Responsibilities\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;\nDELETE&#160;&#160;FROM dbo.Responsibilities\nWHERE&#160;&#160; TaskId = 1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;AND StartedAt = '20990908' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;\nSELECT&#160;&#160;*\nFROM&#160;&#160;&#160;&#160;dbo.Responsibilities ;\nROLLBACK ; &#160;\n\n<\/pre>\n<p class=\"MsoNormal\">Selects were added to the script so that we can see the intermediate and final state of the data. In the intermediate state, the first row is moved to the end, and the second one is updated to become the first:<\/p>\n<p class=\"MsoNormal\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1191-clip_image012.jpg\" width=\"469\" height=\"125\" alt=\"1191-clip_image012.jpg\" \/><\/p>\n<p class=\"MsoNormal\">In the final state, the row is gone:<\/p>\n<p class=\"MsoNormal\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1191-clip_image014.jpg\" width=\"469\" height=\"107\" alt=\"1191-clip_image014.jpg\" \/><\/p>\n<p class=\"MsoNormal\">We shall not re-implement all the previously discussed examples &#8211; that is left as an advanced exercise. <\/p>\n<p class=\"MsoNormal\">Good luck!<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Alex Kuznetsov is credited with a clever technique for creating a history table for SQL that is designed to store contiguous time periods and check that these time periods really are contiguous, using nothing but constraints. This is now increasingly useful with the DATE data type in SQL Server.  The modification of data in this type of table isn&#8217;t always entirely intuitive so Alex is on hand to give a brief explanation of how to do it.&hellip;<\/p>\n","protected":false},"author":6776,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4150,4151,4252],"coauthors":[],"class_list":["post-1041","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-sql","tag-sql-server","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1041","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/6776"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1041"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1041\/revisions"}],"predecessor-version":[{"id":92542,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1041\/revisions\/92542"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1041"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1041"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1041"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1041"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}