What is the correct way to ensure unique entries in a temporal database design?












9















I'm having trouble with the design of a temporal database. I need to know how to make sure I have only one active record for any given timeframe for a store. I have read this answer, but I'm afraid I can't wrap my head around how the trigger would work. Particularly, how I would work that trigger into my existing one that prevents updates to records, and inserts a new record instead. My real problem is that I do not know how to prevent a Store from having more than one effective date when the finished date is null. (i.e. prevent 2 active records for a store).



This is what I have, but it allows me to insert a new record for a store with a different effective date.



Table Definition:



/****** Object:  Table [PCR].[Z_STORE_TEAM]    Script Date: 05/09/2014 13:05:57 ******/
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
DROP TABLE [Z_STORE_TEAM]
GO

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
BEGIN
CREATE TABLE [Z_STORE_TEAM](
[STORENUM] [int] NOT NULL,
[TEAM] [varchar](10) NULL,
[EFFECTIVE] [date] NOT NULL,
[FINISHED] [date] NULL,
PRIMARY KEY CLUSTERED
(
[STORENUM] ASC,
[EFFECTIVE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
END
GO


Sample Data:



INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date))
INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'2', CAST(0x81380B00 AS Date), NULL)
INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'1', CAST(0x01380B00 AS Date), NULL)
INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'2', CAST(0x20380B00 AS Date), NULL)


Instead of Update Trigger:



CREATE TRIGGER [tr_ZStoreTeam_update] 
ON [Z_STORE_TEAM]
INSTEAD OF UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

-- Insert statements for trigger here
INSERT INTO PCR.Z_STORE_TEAM(STORENUM,TEAM,EFFECTIVE)
SELECT I.STORENUM,I.TEAM,GETDATE() AS EFFECTIVE
FROM inserted I
INNER JOIN PCR.Z_STORE_TEAM ST
ON I.STORENUM = ST.STORENUM

UPDATE ST
SET FINISHED = GETDATE()
FROM PCR.Z_STORE_TEAM ST
INNER JOIN inserted I
ON ST.STORENUM = I.STORENUM
AND ST.EFFECTIVE = I.EFFECTIVE
END

GO









share|improve this question





























    9















    I'm having trouble with the design of a temporal database. I need to know how to make sure I have only one active record for any given timeframe for a store. I have read this answer, but I'm afraid I can't wrap my head around how the trigger would work. Particularly, how I would work that trigger into my existing one that prevents updates to records, and inserts a new record instead. My real problem is that I do not know how to prevent a Store from having more than one effective date when the finished date is null. (i.e. prevent 2 active records for a store).



    This is what I have, but it allows me to insert a new record for a store with a different effective date.



    Table Definition:



    /****** Object:  Table [PCR].[Z_STORE_TEAM]    Script Date: 05/09/2014 13:05:57 ******/
    IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
    DROP TABLE [Z_STORE_TEAM]
    GO

    IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
    BEGIN
    CREATE TABLE [Z_STORE_TEAM](
    [STORENUM] [int] NOT NULL,
    [TEAM] [varchar](10) NULL,
    [EFFECTIVE] [date] NOT NULL,
    [FINISHED] [date] NULL,
    PRIMARY KEY CLUSTERED
    (
    [STORENUM] ASC,
    [EFFECTIVE] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    END
    GO


    Sample Data:



    INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date))
    INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'2', CAST(0x81380B00 AS Date), NULL)
    INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'1', CAST(0x01380B00 AS Date), NULL)
    INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'2', CAST(0x20380B00 AS Date), NULL)


    Instead of Update Trigger:



    CREATE TRIGGER [tr_ZStoreTeam_update] 
    ON [Z_STORE_TEAM]
    INSTEAD OF UPDATE
    AS
    BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for trigger here
    INSERT INTO PCR.Z_STORE_TEAM(STORENUM,TEAM,EFFECTIVE)
    SELECT I.STORENUM,I.TEAM,GETDATE() AS EFFECTIVE
    FROM inserted I
    INNER JOIN PCR.Z_STORE_TEAM ST
    ON I.STORENUM = ST.STORENUM

    UPDATE ST
    SET FINISHED = GETDATE()
    FROM PCR.Z_STORE_TEAM ST
    INNER JOIN inserted I
    ON ST.STORENUM = I.STORENUM
    AND ST.EFFECTIVE = I.EFFECTIVE
    END

    GO









    share|improve this question



























      9












      9








      9


      3






      I'm having trouble with the design of a temporal database. I need to know how to make sure I have only one active record for any given timeframe for a store. I have read this answer, but I'm afraid I can't wrap my head around how the trigger would work. Particularly, how I would work that trigger into my existing one that prevents updates to records, and inserts a new record instead. My real problem is that I do not know how to prevent a Store from having more than one effective date when the finished date is null. (i.e. prevent 2 active records for a store).



      This is what I have, but it allows me to insert a new record for a store with a different effective date.



      Table Definition:



      /****** Object:  Table [PCR].[Z_STORE_TEAM]    Script Date: 05/09/2014 13:05:57 ******/
      IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
      DROP TABLE [Z_STORE_TEAM]
      GO

      IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
      BEGIN
      CREATE TABLE [Z_STORE_TEAM](
      [STORENUM] [int] NOT NULL,
      [TEAM] [varchar](10) NULL,
      [EFFECTIVE] [date] NOT NULL,
      [FINISHED] [date] NULL,
      PRIMARY KEY CLUSTERED
      (
      [STORENUM] ASC,
      [EFFECTIVE] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      END
      GO


      Sample Data:



      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date))
      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'2', CAST(0x81380B00 AS Date), NULL)
      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'1', CAST(0x01380B00 AS Date), NULL)
      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'2', CAST(0x20380B00 AS Date), NULL)


      Instead of Update Trigger:



      CREATE TRIGGER [tr_ZStoreTeam_update] 
      ON [Z_STORE_TEAM]
      INSTEAD OF UPDATE
      AS
      BEGIN
      -- SET NOCOUNT ON added to prevent extra result sets from
      -- interfering with SELECT statements.
      SET NOCOUNT ON;

      -- Insert statements for trigger here
      INSERT INTO PCR.Z_STORE_TEAM(STORENUM,TEAM,EFFECTIVE)
      SELECT I.STORENUM,I.TEAM,GETDATE() AS EFFECTIVE
      FROM inserted I
      INNER JOIN PCR.Z_STORE_TEAM ST
      ON I.STORENUM = ST.STORENUM

      UPDATE ST
      SET FINISHED = GETDATE()
      FROM PCR.Z_STORE_TEAM ST
      INNER JOIN inserted I
      ON ST.STORENUM = I.STORENUM
      AND ST.EFFECTIVE = I.EFFECTIVE
      END

      GO









      share|improve this question
















      I'm having trouble with the design of a temporal database. I need to know how to make sure I have only one active record for any given timeframe for a store. I have read this answer, but I'm afraid I can't wrap my head around how the trigger would work. Particularly, how I would work that trigger into my existing one that prevents updates to records, and inserts a new record instead. My real problem is that I do not know how to prevent a Store from having more than one effective date when the finished date is null. (i.e. prevent 2 active records for a store).



      This is what I have, but it allows me to insert a new record for a store with a different effective date.



      Table Definition:



      /****** Object:  Table [PCR].[Z_STORE_TEAM]    Script Date: 05/09/2014 13:05:57 ******/
      IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
      DROP TABLE [Z_STORE_TEAM]
      GO

      IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Z_STORE_TEAM]') AND type in (N'U'))
      BEGIN
      CREATE TABLE [Z_STORE_TEAM](
      [STORENUM] [int] NOT NULL,
      [TEAM] [varchar](10) NULL,
      [EFFECTIVE] [date] NOT NULL,
      [FINISHED] [date] NULL,
      PRIMARY KEY CLUSTERED
      (
      [STORENUM] ASC,
      [EFFECTIVE] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      END
      GO


      Sample Data:



      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date))
      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (1, N'2', CAST(0x81380B00 AS Date), NULL)
      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'1', CAST(0x01380B00 AS Date), NULL)
      INSERT [Z_STORE_TEAM] ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED]) VALUES (2, N'2', CAST(0x20380B00 AS Date), NULL)


      Instead of Update Trigger:



      CREATE TRIGGER [tr_ZStoreTeam_update] 
      ON [Z_STORE_TEAM]
      INSTEAD OF UPDATE
      AS
      BEGIN
      -- SET NOCOUNT ON added to prevent extra result sets from
      -- interfering with SELECT statements.
      SET NOCOUNT ON;

      -- Insert statements for trigger here
      INSERT INTO PCR.Z_STORE_TEAM(STORENUM,TEAM,EFFECTIVE)
      SELECT I.STORENUM,I.TEAM,GETDATE() AS EFFECTIVE
      FROM inserted I
      INNER JOIN PCR.Z_STORE_TEAM ST
      ON I.STORENUM = ST.STORENUM

      UPDATE ST
      SET FINISHED = GETDATE()
      FROM PCR.Z_STORE_TEAM ST
      INNER JOIN inserted I
      ON ST.STORENUM = I.STORENUM
      AND ST.EFFECTIVE = I.EFFECTIVE
      END

      GO






      sql-server database-design t-sql trigger






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Apr 13 '17 at 12:43









      Community

      1




      1










      asked May 9 '14 at 17:12









      RubberDuckRubberDuck

      1586




      1586






















          1 Answer
          1






          active

          oldest

          votes


















          12














          The safest way to do this is to enforce your business rules using the built-in referential integrity constraints, as Alexander Kuznetsov describes in his article, "Storing intervals of time with no overlaps".



          Applying the techniques listed there to your sample table results in the following script:



          CREATE TABLE [Z_STORE_TEAM](
          [STORENUM] [int] NOT NULL,
          [TEAM] [varchar](10) NULL,
          [EFFECTIVE] [date] NOT NULL,
          [FINISHED] [date] NULL,
          PRIMARY KEY CLUSTERED
          (
          [STORENUM] ASC,
          [EFFECTIVE] ASC
          )
          ) ON [PRIMARY];

          INSERT [Z_STORE_TEAM]
          ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
          VALUES
          (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date)),
          (1, N'2', CAST(0x81380B00 AS Date), NULL),
          (2, N'1', CAST(0x01380B00 AS Date), NULL);


          Modifications:



          -- New column to hold the previous finish date
          ALTER TABLE dbo.Z_STORE_TEAM
          ADD PreviousFinished date NULL;
          GO
          -- Populate the previous finish date
          UPDATE This
          SET PreviousFinished = Previous.FINISHED
          FROM dbo.Z_STORE_TEAM AS This
          CROSS APPLY
          (
          SELECT TOP (1)
          Previous.FINISHED
          FROM dbo.Z_STORE_TEAM AS Previous
          WHERE
          Previous.STORENUM = This.STORENUM
          AND Previous.FINISHED <= This.EFFECTIVE
          ORDER BY
          Previous.FINISHED DESC
          ) AS Previous;
          GO
          ALTER TABLE dbo.Z_STORE_TEAM
          ADD CONSTRAINT UQ_STORENUM_PreviousFinished
          UNIQUE (STORENUM, PreviousFinished);
          GO
          ALTER TABLE dbo.Z_STORE_TEAM
          ADD CONSTRAINT CK_PreviousFinished_NotAfter_Effective
          CHECK (PreviousFinished = EFFECTIVE);
          GO
          ALTER TABLE dbo.Z_STORE_TEAM
          ADD CONSTRAINT UQ_STORENUM_FINISHED
          UNIQUE (STORENUM, FINISHED);
          GO
          ALTER TABLE dbo.Z_STORE_TEAM
          ADD CONSTRAINT FK_STORENUM_PreviousFinished
          FOREIGN KEY (STORENUM, PreviousFinished)
          REFERENCES dbo.Z_STORE_TEAM (STORENUM, FINISHED);
          GO
          ALTER TABLE dbo.Z_STORE_TEAM
          ADD CONSTRAINT CK_EFFECTIVE_Before_FINISHED
          CHECK (EFFECTIVE < FINISHED);


          An attempt to insert the fourth row of sample data now fails with an error message:



          INSERT [Z_STORE_TEAM] 
          ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
          VALUES
          (2, N'2', '20140201', NULL);


          Error message



          Please read Alex's article to understand how these constraints ensure your table data will always be valid. Having a set of constraints enforce your data integrity means no trigger code is required.



          Related article by the same author:



          Modifying Contiguous Time Periods in a History Table






          share|improve this answer


























            Your Answer








            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "182"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f64862%2fwhat-is-the-correct-way-to-ensure-unique-entries-in-a-temporal-database-design%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            12














            The safest way to do this is to enforce your business rules using the built-in referential integrity constraints, as Alexander Kuznetsov describes in his article, "Storing intervals of time with no overlaps".



            Applying the techniques listed there to your sample table results in the following script:



            CREATE TABLE [Z_STORE_TEAM](
            [STORENUM] [int] NOT NULL,
            [TEAM] [varchar](10) NULL,
            [EFFECTIVE] [date] NOT NULL,
            [FINISHED] [date] NULL,
            PRIMARY KEY CLUSTERED
            (
            [STORENUM] ASC,
            [EFFECTIVE] ASC
            )
            ) ON [PRIMARY];

            INSERT [Z_STORE_TEAM]
            ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
            VALUES
            (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date)),
            (1, N'2', CAST(0x81380B00 AS Date), NULL),
            (2, N'1', CAST(0x01380B00 AS Date), NULL);


            Modifications:



            -- New column to hold the previous finish date
            ALTER TABLE dbo.Z_STORE_TEAM
            ADD PreviousFinished date NULL;
            GO
            -- Populate the previous finish date
            UPDATE This
            SET PreviousFinished = Previous.FINISHED
            FROM dbo.Z_STORE_TEAM AS This
            CROSS APPLY
            (
            SELECT TOP (1)
            Previous.FINISHED
            FROM dbo.Z_STORE_TEAM AS Previous
            WHERE
            Previous.STORENUM = This.STORENUM
            AND Previous.FINISHED <= This.EFFECTIVE
            ORDER BY
            Previous.FINISHED DESC
            ) AS Previous;
            GO
            ALTER TABLE dbo.Z_STORE_TEAM
            ADD CONSTRAINT UQ_STORENUM_PreviousFinished
            UNIQUE (STORENUM, PreviousFinished);
            GO
            ALTER TABLE dbo.Z_STORE_TEAM
            ADD CONSTRAINT CK_PreviousFinished_NotAfter_Effective
            CHECK (PreviousFinished = EFFECTIVE);
            GO
            ALTER TABLE dbo.Z_STORE_TEAM
            ADD CONSTRAINT UQ_STORENUM_FINISHED
            UNIQUE (STORENUM, FINISHED);
            GO
            ALTER TABLE dbo.Z_STORE_TEAM
            ADD CONSTRAINT FK_STORENUM_PreviousFinished
            FOREIGN KEY (STORENUM, PreviousFinished)
            REFERENCES dbo.Z_STORE_TEAM (STORENUM, FINISHED);
            GO
            ALTER TABLE dbo.Z_STORE_TEAM
            ADD CONSTRAINT CK_EFFECTIVE_Before_FINISHED
            CHECK (EFFECTIVE < FINISHED);


            An attempt to insert the fourth row of sample data now fails with an error message:



            INSERT [Z_STORE_TEAM] 
            ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
            VALUES
            (2, N'2', '20140201', NULL);


            Error message



            Please read Alex's article to understand how these constraints ensure your table data will always be valid. Having a set of constraints enforce your data integrity means no trigger code is required.



            Related article by the same author:



            Modifying Contiguous Time Periods in a History Table






            share|improve this answer






























              12














              The safest way to do this is to enforce your business rules using the built-in referential integrity constraints, as Alexander Kuznetsov describes in his article, "Storing intervals of time with no overlaps".



              Applying the techniques listed there to your sample table results in the following script:



              CREATE TABLE [Z_STORE_TEAM](
              [STORENUM] [int] NOT NULL,
              [TEAM] [varchar](10) NULL,
              [EFFECTIVE] [date] NOT NULL,
              [FINISHED] [date] NULL,
              PRIMARY KEY CLUSTERED
              (
              [STORENUM] ASC,
              [EFFECTIVE] ASC
              )
              ) ON [PRIMARY];

              INSERT [Z_STORE_TEAM]
              ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
              VALUES
              (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date)),
              (1, N'2', CAST(0x81380B00 AS Date), NULL),
              (2, N'1', CAST(0x01380B00 AS Date), NULL);


              Modifications:



              -- New column to hold the previous finish date
              ALTER TABLE dbo.Z_STORE_TEAM
              ADD PreviousFinished date NULL;
              GO
              -- Populate the previous finish date
              UPDATE This
              SET PreviousFinished = Previous.FINISHED
              FROM dbo.Z_STORE_TEAM AS This
              CROSS APPLY
              (
              SELECT TOP (1)
              Previous.FINISHED
              FROM dbo.Z_STORE_TEAM AS Previous
              WHERE
              Previous.STORENUM = This.STORENUM
              AND Previous.FINISHED <= This.EFFECTIVE
              ORDER BY
              Previous.FINISHED DESC
              ) AS Previous;
              GO
              ALTER TABLE dbo.Z_STORE_TEAM
              ADD CONSTRAINT UQ_STORENUM_PreviousFinished
              UNIQUE (STORENUM, PreviousFinished);
              GO
              ALTER TABLE dbo.Z_STORE_TEAM
              ADD CONSTRAINT CK_PreviousFinished_NotAfter_Effective
              CHECK (PreviousFinished = EFFECTIVE);
              GO
              ALTER TABLE dbo.Z_STORE_TEAM
              ADD CONSTRAINT UQ_STORENUM_FINISHED
              UNIQUE (STORENUM, FINISHED);
              GO
              ALTER TABLE dbo.Z_STORE_TEAM
              ADD CONSTRAINT FK_STORENUM_PreviousFinished
              FOREIGN KEY (STORENUM, PreviousFinished)
              REFERENCES dbo.Z_STORE_TEAM (STORENUM, FINISHED);
              GO
              ALTER TABLE dbo.Z_STORE_TEAM
              ADD CONSTRAINT CK_EFFECTIVE_Before_FINISHED
              CHECK (EFFECTIVE < FINISHED);


              An attempt to insert the fourth row of sample data now fails with an error message:



              INSERT [Z_STORE_TEAM] 
              ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
              VALUES
              (2, N'2', '20140201', NULL);


              Error message



              Please read Alex's article to understand how these constraints ensure your table data will always be valid. Having a set of constraints enforce your data integrity means no trigger code is required.



              Related article by the same author:



              Modifying Contiguous Time Periods in a History Table






              share|improve this answer




























                12












                12








                12







                The safest way to do this is to enforce your business rules using the built-in referential integrity constraints, as Alexander Kuznetsov describes in his article, "Storing intervals of time with no overlaps".



                Applying the techniques listed there to your sample table results in the following script:



                CREATE TABLE [Z_STORE_TEAM](
                [STORENUM] [int] NOT NULL,
                [TEAM] [varchar](10) NULL,
                [EFFECTIVE] [date] NOT NULL,
                [FINISHED] [date] NULL,
                PRIMARY KEY CLUSTERED
                (
                [STORENUM] ASC,
                [EFFECTIVE] ASC
                )
                ) ON [PRIMARY];

                INSERT [Z_STORE_TEAM]
                ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
                VALUES
                (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date)),
                (1, N'2', CAST(0x81380B00 AS Date), NULL),
                (2, N'1', CAST(0x01380B00 AS Date), NULL);


                Modifications:



                -- New column to hold the previous finish date
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD PreviousFinished date NULL;
                GO
                -- Populate the previous finish date
                UPDATE This
                SET PreviousFinished = Previous.FINISHED
                FROM dbo.Z_STORE_TEAM AS This
                CROSS APPLY
                (
                SELECT TOP (1)
                Previous.FINISHED
                FROM dbo.Z_STORE_TEAM AS Previous
                WHERE
                Previous.STORENUM = This.STORENUM
                AND Previous.FINISHED <= This.EFFECTIVE
                ORDER BY
                Previous.FINISHED DESC
                ) AS Previous;
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT UQ_STORENUM_PreviousFinished
                UNIQUE (STORENUM, PreviousFinished);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT CK_PreviousFinished_NotAfter_Effective
                CHECK (PreviousFinished = EFFECTIVE);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT UQ_STORENUM_FINISHED
                UNIQUE (STORENUM, FINISHED);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT FK_STORENUM_PreviousFinished
                FOREIGN KEY (STORENUM, PreviousFinished)
                REFERENCES dbo.Z_STORE_TEAM (STORENUM, FINISHED);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT CK_EFFECTIVE_Before_FINISHED
                CHECK (EFFECTIVE < FINISHED);


                An attempt to insert the fourth row of sample data now fails with an error message:



                INSERT [Z_STORE_TEAM] 
                ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
                VALUES
                (2, N'2', '20140201', NULL);


                Error message



                Please read Alex's article to understand how these constraints ensure your table data will always be valid. Having a set of constraints enforce your data integrity means no trigger code is required.



                Related article by the same author:



                Modifying Contiguous Time Periods in a History Table






                share|improve this answer















                The safest way to do this is to enforce your business rules using the built-in referential integrity constraints, as Alexander Kuznetsov describes in his article, "Storing intervals of time with no overlaps".



                Applying the techniques listed there to your sample table results in the following script:



                CREATE TABLE [Z_STORE_TEAM](
                [STORENUM] [int] NOT NULL,
                [TEAM] [varchar](10) NULL,
                [EFFECTIVE] [date] NOT NULL,
                [FINISHED] [date] NULL,
                PRIMARY KEY CLUSTERED
                (
                [STORENUM] ASC,
                [EFFECTIVE] ASC
                )
                ) ON [PRIMARY];

                INSERT [Z_STORE_TEAM]
                ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
                VALUES
                (1, N'1', CAST(0x01380B00 AS Date), CAST(0x81380B00 AS Date)),
                (1, N'2', CAST(0x81380B00 AS Date), NULL),
                (2, N'1', CAST(0x01380B00 AS Date), NULL);


                Modifications:



                -- New column to hold the previous finish date
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD PreviousFinished date NULL;
                GO
                -- Populate the previous finish date
                UPDATE This
                SET PreviousFinished = Previous.FINISHED
                FROM dbo.Z_STORE_TEAM AS This
                CROSS APPLY
                (
                SELECT TOP (1)
                Previous.FINISHED
                FROM dbo.Z_STORE_TEAM AS Previous
                WHERE
                Previous.STORENUM = This.STORENUM
                AND Previous.FINISHED <= This.EFFECTIVE
                ORDER BY
                Previous.FINISHED DESC
                ) AS Previous;
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT UQ_STORENUM_PreviousFinished
                UNIQUE (STORENUM, PreviousFinished);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT CK_PreviousFinished_NotAfter_Effective
                CHECK (PreviousFinished = EFFECTIVE);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT UQ_STORENUM_FINISHED
                UNIQUE (STORENUM, FINISHED);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT FK_STORENUM_PreviousFinished
                FOREIGN KEY (STORENUM, PreviousFinished)
                REFERENCES dbo.Z_STORE_TEAM (STORENUM, FINISHED);
                GO
                ALTER TABLE dbo.Z_STORE_TEAM
                ADD CONSTRAINT CK_EFFECTIVE_Before_FINISHED
                CHECK (EFFECTIVE < FINISHED);


                An attempt to insert the fourth row of sample data now fails with an error message:



                INSERT [Z_STORE_TEAM] 
                ([STORENUM], [TEAM], [EFFECTIVE], [FINISHED])
                VALUES
                (2, N'2', '20140201', NULL);


                Error message



                Please read Alex's article to understand how these constraints ensure your table data will always be valid. Having a set of constraints enforce your data integrity means no trigger code is required.



                Related article by the same author:



                Modifying Contiguous Time Periods in a History Table







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited 4 mins ago

























                answered May 10 '14 at 19:38









                Paul WhitePaul White

                54.1k14287460




                54.1k14287460






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Database Administrators Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f64862%2fwhat-is-the-correct-way-to-ensure-unique-entries-in-a-temporal-database-design%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Liste der Baudenkmale in Friedland (Mecklenburg)

                    Single-Malt-Whisky

                    Czorneboh