带有时间窗口的sql唯一约束

问题描述

我有一个表格,其中记录有一个(开始、结束)存在时间窗口(例如雇佣期限、出生和死亡、租金期限等)

如果没有界限,则开始IS NULL或结束IS NULL。

CREATE TABLE mytable(
id int primary key,value int,--UNIQUE at any point in time
begin datetime  NULL,end datetime  NULL
);

我希望列值在任何时间点都是唯一的。

INSERT INTO mytable VALUES(1,1,'2021-07-23','2021-07-24'),(2,'2021-07-25',NULL);

没问题

然而

INSERT INTO mytable VALUES(1,'2021-07-30'),NULL);

不行,因为两个记录都有 value=1 和重叠的时间窗口。

有没有办法在 SQL 中强制执行这样的约束?

解决方法

你不能在桌子上做这个,不,因为没有什么可以制作UNIQUE。 但是,您可以做的是使用 VIEW 来强制执行。

首先,让我们创建您的表。我假设列 datetime,实际上应该是 beginend;我建议不要使用这些名称,因为它们是保留关键字。因此,我称它们为 DateBeginDateEnd。我还假设它们只是日期(无时间部分)值,因此将它们定义为 date 而不是 datetime

CREATE TABLE dbo.mytable(ID int primary key,Value int,[BeginDate] date NULL,[EndEnd] date NULL);

我们将INSERT您的前两行,因为它们“正常”:

INSERT INTO dbo.mytable (ID,Value,BeginDate,EndDate)
VALUES(1,1,'20210723','20210724'),(2,'20210725',NULL);

现在我们需要创建一个 VIEW,但我们每个日期需要一行。因此,您需要创建一个日历表。我不打算在这里介绍如何创建一个,但实际上有 100 多篇文章,例如 SQL Server Central 上的文章:Bones of SQL - The Calendar TableCalendar Tables in T-SQL

拥有日历表后,您可以创建下面的 VIEW,它将表中的数据JOIN 转换为日历表。我们将使其VIEW 只返回列value 和日期。我们还将对其进行模式绑定;这意味着我们将能够向其添加 UNIQUE INDEX

CREATE VIEW dbo.MyView
WITH SCHEMABINDING
AS
    SELECT MT.[Value],CT.CalendarDate
    FROM dbo.MyTable MT
         JOIN dbo.CalendarTable CT ON MT.BeginDate <= CT.CalendarDate --I assume,despite your schema,MT.BeginDate can't be NULL
                                  AND (MT.EndDate >= CT.CalendarDate OR MT.EndDate IS NULL);

现在我们有一个 VIEW,每个日期和每个值都有一行。这意味着我们现在可以创建我们的 UNIQUE INDEX:

CREATE UNIQUE CLUSTERED INDEX MyIndex ON dbo.MyView ([Value],CalendarDate);

现在,如果我们尝试 INSERT 具有相同日期和值的行,我们会得到一个错误:

INSERT INTO dbo.MyTable (ID,EndDate)
VALUES(3,'20210720','20210723');

无法在具有唯一索引“MyIndex”的对象“dbo.MyView”中插入重复的键行。重复的键值为 (1,2021-07-23)。

,

Trigger 会帮助你,例如像这样:

CREATE TRIGGER mytable__tr_iu ON mytable
FOR INSERT,UPDATE 
AS
IF EXISTS(
  SELECT 1
  FROM mytable t
  JOIN inserted i ON t.id <> i.id AND t.value = i.value AND 
    (t.BeginDate IS NULL OR i.EndDate IS NULL OR t.BeginDate <= i.EndDate) AND
    (t.EndDate IS NULL OR i.BeginDate IS NULL OR t.EndDate >= i.BeginDate)
) 
THROW 51200,'Date range error for value',10

fiddle

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...