Question : How to maintain the dates of a history table aligned with no gaps?

Hi,

I have to mantain a history table, where given a new entry or a record is updated, the table is recreated or realigned (only for records with the same ID). There should be no gaps between the records. Each record has a StartDate and EndDate. Below is the schema of the table:

CREATE TABLE [dbo].[History] (
  [HistoryID] bigint IDENTITY(1, 1) NOT NULL,
  [ID] int NOT NULL,
  [StartDate] datetime NOT NULL,
  [EndDate] datetime NULL,
  [Status] tinyint NULL,
  CONSTRAINT [History_pk] PRIMARY KEY CLUSTERED ([HistoryID]),
  CONSTRAINT [History_uq] UNIQUE ([ID], [StartDate])
)
ON [PRIMARY]
GO

Here is a sample data with the dates aligned:

The DateFormat is dd/mm/yyyy

HistoryID      ID      StartDate      EndDate      Status
1      1      01/01/2010      10/01/2010      1
2      1      11/01/2010      15/01/2010      2
3      1      16/01/2010      20/01/2010      3

Rules:
1.Get rid of any rows that are entirely within the range of the new row being inserted or updated.
example:

INSERT INTO History(ID,StartDate,EndDate,Status)
VALUES(1,'01/01/2010','17/01/2010',2)

The end ResultSet should be as follows:
      
HistoryID      ID      StartDate      EndDate      Status
6      1      01/01/2010      17/01/2010      2
3      1      18/01/2010      20/01/2010      3

Note that the record with HistoryID=3 have the StartDate increased by 1 because of the EndDate of the previous record(6) are greater than the StartDate of the record below(3), which range was from 16/01/2010 to 20/01/2010.

The same behavior should apply if the user makes an update on any record on any field (StartDate,EndDate,Status).

Following the previous resultset, lets INSERT more data into the table.

HistoryID      ID      StartDate      EndDate      Status
6      1      01/01/2010      17/01/2010      2
3      1      18/01/2010      20/01/2010      3

INSERT INTO History(ID,StartDate,EndDate,Status)
VALUES(1,'05/01/2010','10/01/2010',1)

ResultSet after the insert:

HistoryID      ID      StartDate      EndDate      Status
6      1      01/01/2010      04/01/2010      2
7      1      05/01/2010      10/01/2010      1
8      1      11/01/2010      17/01/2010      2
3      1      18/01/2010      20/01/2010      3

Now the user changes the record with HistoryID=3, with the folowing UPDATE:
UPDATE
  SET StartDate='17/01/2010', EndDate='19/01/2010', Status=1
WHERE HistoryID=3

Resultset:

HistoryID      ID      StartDate      EndDate      Status
6      1      01/01/2010      04/01/2010      2
7      1      05/01/2010      10/01/2010      1
8      1      11/01/2010      16/01/2010      2
3      1      17/01/2010      19/01/2010      1
9      1      20/01/2010      20/01/2010      3


Another INSERT:

INSERT INTO History(ID,StartDate,EndDate,Status)
VALUES(1,'01/01/2010','18/01/2010',1)

Resultset:

HistoryID      ID      StartDate      EndDate      Status
10      1      01/01/2010      19/01/2010      1
9      1      20/01/2010      20/01/2010      3

Note that the records 7,8,3 were deleted. The new added record takes the EndDate modified to 19/01/2010 because of the EndDate of record no 3 had the same status, that means, that there is no need to keep or modify the record no 3 either, just take the EndDate of it and put it on the newly inserted record.

I dont know how to achive this result together with the behavior exposed here. I dont know if its better to do it as a trigger or to write a procedure for inserting records and do all the job in it.

That´s it. If there is any doubt about the rules, please, let me know.

Thanks!

Best Regards,

Marco André

Answer : How to maintain the dates of a history table aligned with no gaps?

hi Marco,

The huge number of records may introduce performance problems in both scenarios. The basic problem about performance is always the same: Test all scenarios and compare the results.

Finding your correct history record is simple:

SELECT TOP 1 * FROM [HistoryTable] WHERE [Date] <= [EventDate] ORDER BY [Date] DESC ;

You can use a user defined table-value function to calculate this record, but I cannot guess about its performance. Maybe there is a cool join, but I can't imagine one right now ;)

The multiple user problems are the same in both solutions, it is the same problem about atomicity as in the well-known bank account transfer problem. You need an explicit transaction control encapsulating your logic and an isolation level of read commit or better.

mfG
--> stefan <--
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
SELECT  *
FROM    [EventTable] E
        CROSS APPLY [dbo].[FindHistory](E.[ID], E.[EventDate]) H ;

-- with

CREATE FUNCTION [FindHistory]
    (
      @Id AS INT,
      @EventDate AS DATETIME
    )
RETURNS TABLE
AS
RETURN
    ( SELECT TOP 1
                H.*
      FROM      [HistoryTable] H
      WHERE     H.ID = @ID
                AND H.[HistoryDate] = @EventDate
      ORDER BY  H.[HistoryDate] DESC
    )
Random Solutions  
 
programming4us programming4us