我可以用开始日期和结束日期限制 SQL 表,使其仅允许一个“活动”行

问题描述 投票:0回答:1

问题:对于给定的 SQL Server 表,是否可以“约束”或使用“唯一索引”,以便它只允许用户输入基于开始和结束日期只有一个“活动/实时”行的行?

示例数据库架构:

CREATE TABLE [dbo].[Service]
(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [ServiceName] varchar(50) NOT NULL,

    CONSTRAINT [PK_Service] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[ServiceRate]
(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [ServiceId] [int] NOT NULL,
    [StartDate] [datetime] NOT NULL,
    [EndDate] [datetime] NULL,
    [CountryId] [int] NOT NULL,

    CONSTRAINT [PK_ServiceRate] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[ServiceRate] WITH CHECK 
    ADD CONSTRAINT [FK_ServiceRate_Service] 
        FOREIGN KEY([ServiceId]) REFERENCES [dbo].[Service] ([Id])
GO

ALTER TABLE [dbo].[ServiceRate] CHECK CONSTRAINT [FK_ServiceRate_Service]
GO

ALTER TABLE [dbo].[ServiceRate] WITH CHECK 
    ADD CONSTRAINT [CK_ServiceRate_StartDate_EndDate] 
        CHECK  (([StartDate] <= COALESCE([EndDate], '9999-12-31')))
GO

ALTER TABLE [dbo].[ServiceRate] CHECK CONSTRAINT [CK_ServiceRate_StartDate_EndDate]
GO

CREATE UNIQUE NONCLUSTERED INDEX [UX_ServiceId_EndDate_CountryId] 
ON [dbo].[ServiceRate] ([ServiceId] ASC, [EndDate] ASC, [CountryId] ASC)

例如,如果我用这样的数据填充

[ServiceRate]
表:

身份证 服务ID 开始日期 结束日期 国家/地区ID
2 1 2023-01-01 00:00:00.000 0
3 1 2023-01-01 00:00:00.000 53
5 1 2023-01-01 00:00:00.000 70
6 2 2023-01-01 00:00:00.000 0
7 2 2023-01-01 00:00:00.000 53
9 1 2023-02-01 00:00:00.000 2023-02-28 00:00:00.000 0<--- THIS SHOULD ERROR (conflict row 1)
11 1 2023-02-02 00:00:00.000 2023-02-27 00:00:00.000 0<--- THIS SHOULD ERROR (conflict row 9)

注意:

CountryId
0 表示“所有国家/地区”

然后,只要

([ServiceId], [CountryId])
的列是唯一的,但也具有来自
[StartDate]
[EndDate]
的单独“实时”日期范围,那么一切都很好。

但是我希望 SQL Server 在尝试添加第 9 行时抛出错误,因为这与第 1 行相同的“活动/实时”日期范围冲突(以及

ServiceId
CountryId
的重复)

此外,如果第 1 行不存在 - 那么这也应该在第 11 行引发错误,因为这与第 9 行相同的“活动/实时”日期范围冲突(以及

ServiceId
CountryId 的重复) 
)。

因此解释这一点的另一种方法是说 - 我可以限制表中的数据,以便该查询在任何日期运行时都不会返回大于 1 的

[rec]
值吗?

DECLARE @dt datetime = '2023-02-14'

SELECT ServiceId, CountryId, COUNT(*) rec
FROM [dbo].[ServiceRate]
WHERE @dt >= StartDate
  AND @dt <  COALESCE(EndDate, '9999-12-31')
GROUP BY ServiceId, CountryId

到目前为止,我已经有了

CONSTRAINT
UNIQUE NONCLUSTERED INDEX
,但我不太明白这一点。

我看过其他文章描述使用 LAG 函数的查询 - 但这仅在前面的

[EndDate]
与该
[StartDate]
组的下一个
(ServiceId, CountryId)
完全匹配时才有效。

任何帮助或提示,我们将不胜感激。

sql sql-server t-sql constraints temporal-tables
1个回答
0
投票

虽然我不太喜欢此类约束,因为这意味着您无法控制输入,但您可以创建一个函数检查约束:

-- Test data
SELECT  id, ServiceId, cast(startdate AS datetime) AS startdate, cast(enddate AS datetime) AS enddate, CountryId
INTO    tblRanges
FROM
(
    VALUES  (2, 1, N'2023-01-01 00:00:00.000', NULL, N'0')
    ,   (3, 1, N'2023-01-01 00:00:00.000', NULL, N'53')
    ,   (5, 1, N'2023-01-01 00:00:00.000', NULL, N'70')
    ,   (6, 2, N'2023-01-01 00:00:00.000', NULL, N'0')
    ,   (7, 2, N'2023-01-01 00:00:00.000', NULL, N'53')
) t (ID,ServiceId,StartDate,EndDate,CountryId)


go

-- Check function
CREATE FUNCTION dbo.fn_tblranges (@id int)
    RETURNS int AS
    BEGIN
        RETURN (
            CASE WHEN EXISTS(
                SELECT  1
                FROM    dbo.tblRanges t
                INNER JOIN dbo.tblRanges t2
                    ON  t2.CountryId = t.CountryId
                    AND t2.ServiceId = t.ServiceId
                    AND ISNULL(t2.EndDate, CONVERT(datetime, '99990101', 112)) >= t.StartDate
                    AND ISNULL(t2.StartDate, CONVERT(datetime, '99990101', 112)) <=ISNULL(t.EndDate, CONVERT(datetime, '99990101', 112))
                WHERE   t.id = @id
                AND t2.id <> t.id
            ) THEN 1 ELSE 0 END)

    END
go

-- Check constraint
ALTER TABLE tblranges 
ADD CONSTRAINT chkDupes 
CHECK (dbo.fn_tblranges(id) = 0);

go

一些测试代码:

-- Fails
INSERT INTO tblRanges
VALUES  (9, 1, N'2023-02-01 00:00:00.000', N'2023-02-28 00:00:00.000', N'0');

go

-- Fails
INSERT INTO tblRanges
VALUES  (11, 1, N'2023-02-02 00:00:00.000', N'2023-02-27 00:00:00.000', N'0');

go

-- Succeeds
INSERT INTO tblranges
VALUES  (8, 1, N'2022-01-01 00:00:00.000', '20221230', N'0')
,   (12, 1, N'20221231', '20221231', N'0')
go

另一种方法是创建一个执行相同检查的触发器。

请注意,函数检查对于性能而言可能相对较差,尤其是使用标量函数来检查表数据的函数检查

© www.soinside.com 2019 - 2024. All rights reserved.