【问题标题】:Sql - Column Check Constraint based on DateSql - 基于日期的列检查约束
【发布时间】:2016-05-12 08:46:35
【问题描述】:

这不是 Check constraint on date 的重复,但我可能错过了另一个类似的问题。

在 MS SQL 上,您可以创建以下约束:

ALTER TABLE [X] WITH CHECK ADD CONSTRAINT [CCCHK03_TBX] CHECK
(
    [TBX_YEAR] = DATEPART( year, GetDate() )
)

您可以很好地插入记录,但我无法完全测试它的含义,当服务器日期跳到 2017 年时会发生什么?我的印象是它将允许插入 2017 年,但理论上它会使 2016 年的所有记录无效。

这个表是一个只插入的表,所以记录永远不会是可更新的,所以这不是问题。我主要关心的是这是否可能导致服务器稳定性问题?

我似乎找不到与此相关的任何内容,但 MS 必须有理由允许这样的限制。

通常我会建议创建一个插入触发器并对其进行检查,但这让我很好奇。

编辑:目标数据答案的扩展测试:

IF ( OBJECT_ID( 'tempdb..#CheckTest' ) IS NOT NULL )
    DROP TABLE #CheckTest
GO

CREATE TABLE #CheckTest ( MN INT )
ALTER TABLE #CheckTest WITH CHECK ADD CONSTRAINT CHK_MN CHECK ( MN = DATEPART( SECOND, GETDATE() ) )
ALTER TABLE #CheckTest CHECK CONSTRAINT CHK_MN
GO

-- Control Test. This will fail with:
--Msg 547, Level 16, State 0, Line 12
--The INSERT statement conflicted with the CHECK constraint "CHK_MN". The conflict occurred in database "tempdb", table "dbo.#CheckTest", column 'MN'.
--The statement has been terminated.
INSERT INTO #CheckTest ( MN )
VALUES ( DATEPART( SECOND, DATEADD( SECOND, 5, GETDATE() ) ) )

-- Add 5 different seconds.
DECLARE @Counter int = 0;

WHILE @Counter < 5
BEGIN
    INSERT INTO #CheckTest ( MN )
    VALUES ( DATEPART( SECOND, GETDATE() ) )

    SET @Counter += 1;

    -- Delay for a second.
    WAITFOR DELAY '00:00:01';
END
GO

-- Add a different second.
-- Disabling and Enabling a check will work just fine so long as the check already exists.
ALTER TABLE #CheckTest NOCHECK CONSTRAINT CHK_MN;
INSERT INTO #CheckTest ( MN )
VALUES ( DATEPART( SECOND, DATEADD( SECOND, 5, GETDATE() ) ) )
ALTER TABLE #CheckTest CHECK CONSTRAINT CHK_MN;

-- Control Test. This will fail with:
--Msg 547, Level 16, State 0, Line 12
--The INSERT statement conflicted with the CHECK constraint "CHK_MN". The conflict occurred in database "tempdb", table "dbo.#CheckTest", column 'MN'.
--The statement has been terminated.
INSERT INTO #CheckTest ( MN )
VALUES ( DATEPART( SECOND, DATEADD( SECOND, 5, GETDATE() ) ) )
GO

-- Check table contents.
SELECT * FROM #CheckTest;
GO

-- Dropping and recreating the check constraint will result in an error:
--Msg 547, Level 16, State 0, Line 37
--The ALTER TABLE statement conflicted with the CHECK constraint "CHK_MN". The conflict occurred in database "tempdb", table "dbo.#CheckTest", column 'MN'.
ALTER TABLE #CheckTest DROP CONSTRAINT CHK_MN;
ALTER TABLE #CheckTest WITH CHECK ADD CONSTRAINT CHK_MN CHECK ( MN = DATEPART( SECOND, GETDATE() ) )
GO

DROP TABLE #CheckTest
GO

编辑 2:摘要

根据测试和反馈,虽然这确实是一个有趣的练习,而且绝对看起来 100% 完全有效,但我绝对会从“未来证明”的角度建议不要这样做,因为支票永远不会能够被改变。我个人认为基于触发器的约束是最易于维护的。

【问题讨论】:

  • 当约束失败时不会有任何系统不稳定。约束用于执行我们的预定义规则
  • 我现在在文档中找不到它,但很可能 CHECK 约束仅在插入或更新行时才被评估/验证(仅适用于插入或更新的行)。因此,您可以在今天插入 2016 年的行,并在明年插入 2017 年的行,但如果在 2017 年尝试更新在 2016 年创建的行,则更新将失败。如果您不触摸该旧行,它将按原样保留在表格中。
  • @VladimirBaranov 太好了,感谢您的支持,我也是这么看的。如果那是答案,我可以标记它。

标签: tsql sql-server-2012


【解决方案1】:

好问题!

简短的回答是你会没事的。只有在检查中插入或更新列时才会验证检查。您现有的记录将不受年份变化的影响。

但是您应该考虑一些潜在的问题。这种类型的检查会使您更难修改表设计,尤其是通过 SSMS(这往往会创建一个新表、从旧表导入数据、删除旧表并重命名新表)。导入将失败,因为旧记录与当前约束规则不匹配。当然你仍然可以使用 tSQL 进行修改。

修正表

-- Disable check.
ALTER TABLE [Schema].[Table] NOCHECK CONSTRAINT [Check];

-- Makes changes here.

-- Enable check.
ALTER TABLE [Schema].[Table] CHECK CONSTRAINT [Check];

更新表中的其他列时,您不需要禁用检查。

测试查询

多年的测试有点困难,所以我换成了几分钟。

CREATE TABLE #CheckTest 
    (
        MN  INT CONSTRAINT CHK_MN  CHECK (MN = DATEPART(MINUTE, GETDATE()))
    )
;

-- Add two different minutes.
WHILE @COUNTER < 2 
BEGIN

    INSERT INTO #CheckTest
        (
            MN
        )
    VALUES
        (DATEPART(MINUTE, GETDATE()))
    ;

    SET @COUNTER = @COUNTER + 1;

    -- Delay for a minute.
    WAITFOR DELAY '00:01:00'
END

-- Check table contents.
SELECT
    *
FROM
    #CheckTest
;

【讨论】:

  • 我真的很喜欢关于禁用和启用检查的输入,我确信它会失败但后来我意识到,这只是在尝试创建检查时。
【解决方案2】:

虽然我同意Vladimir Baranov 的评论,但我也在考虑以下几点。

查询优化器可能会在查询数据时考虑CHECK 约束 (example),但正如 documentation 中所述,这仅适用于受信任的约束

查询优化器不考虑已定义的约束 没有检查。

在创建约束时指定WITH CHECK

ALTER TABLE [X] WITH CHECK ADD CONSTRAINT [CCCHK03_TBX] CHECK ...

一年后(在添加了一些对应于明年的行之后)你将无法做到

ALTER TABLE [X] WITH CHECK CHECK CONSTRAINT [CCCHK03_TBX]

尝试这样做会产生错误消息

消息 547,级别 16,状态 0,行 ... ALTER TABLE 语句 与 CHECK 约束“CCCHK03_TBX”冲突。冲突 发生在数据库“DbName”、表“dbo.X”、列“TBX_YEAR”中。

但是在sys.check_constraints系统视图中

select is_not_trusted
from sys.check_constraints
where object_id = object_id('CCCHK03_TBX')

你仍然会看到is_not_trusted = 0,我认为它就像is_not_trusted = 1一样有效(因为某些表行不再满足检查表达式)。

我不确定,但我认为这可能会导致查询优化器生成次优计划的情况,除非它足够聪明,在某些情况下不考虑包含非确定性表达式的 CHECK 约束(我无法找到此信息,如果有人能对此有所了解,那就太好了)。而且我相信查询优化器在这种情况下足够聪明,不会做出可能导致产生错误查询结果的错误判断。

【讨论】:

  • 我真的很喜欢关于查询优化器的信息。但是,关于启用 CHECK,如果您尝试添加检查,则只会收到错误,而不是启用它。如果它已经添加,您可以根据我在问题中更新的测试安全地启用它。
  • @Storm,使用alter table [T] nocheck constraint [CK] 禁用约束并使用alter table [T] check constraint [CK] 重新启用它将使约束处于is_not_trusted = 1 状态(可以在sys.check_constraints 中检查),尽管约束将启用,以后的插入/更新将遵守约束规则。启用并不意味着受信任。使其再次受信任的唯一方法是执行alter table [T] WITH CHECK check constraint [CK]。然而,这个约束最终是不可能的。
  • 您的评论现在很有意义,您的原始答案不是很清楚,也许只是更新您的答案以使用您在评论中发布的信息详细说明一下,因为信任问题是一个游戏更换器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-28
相关资源
最近更新 更多