Sql 为什么会触发此检查约束?

Sql 为什么会触发此检查约束?,sql,sql-server,tsql,sql-server-2008-r2,Sql,Sql Server,Tsql,Sql Server 2008 R2,因此,我有一个元数据表: CREATE TABLE Tables ( schemaName SYSNAME , objectName SYSNAME , columnName SYSNAME , RowColAgg VARCHAR(3) NOT NULL , AggFunction NVARCHAR(128) ) 和一个函数: CREATE FUNCTION ColumnValidate ( @schemaName SYSNAME

因此,我有一个元数据表:

CREATE TABLE Tables (
      schemaName SYSNAME
    , objectName SYSNAME
    , columnName SYSNAME
    , RowColAgg VARCHAR(3) NOT NULL
    , AggFunction NVARCHAR(128)
    )
和一个函数:

CREATE FUNCTION ColumnValidate (
      @schemaName SYSNAME
    , @objectName SYSNAME
    , @columnName SYSNAME
    , @RowColAgg VARCHAR(3)
    , @AggFunction NVARCHAR(128)
    )
RETURNS TINYINT
AS
BEGIN
    DECLARE @Valid TINYINT = 1
    SET @Valid = @Valid & ISNULL((SELECT 1 WHERE @RowColAgg IN ('Row','Col','Agg')),0)
    IF (@Valid = 1)
    BEGIN
        /* If this is an Aggregate Column, it must have an aggregate function attached */
        IF (@RowColAgg = 'Agg') SET @Valid = @Valid & ISNULL((SELECT 1 WHERE @AggFunction IS NOT NULL),0)
        IF (@Valid = 1)
        BEGIN
            /* If this is an Aggregate or Column header, ensure this is the only one. */
            IF (@RowColAgg IN ('Agg','Col')) SET @Valid = @Valid ^ (SELECT SIGN(COUNT(columnName)) FROM Tables WHERE schemaName =  @schemaName AND objectName  = @objectName AND RowColAgg  = @RowColAgg)
            IF (@Valid = 1)
            BEGIN
                /* If this is a row header, ensure that this is only selected once. */
                IF (@RowColAgg = 'Row') SET @Valid = @Valid ^ (SELECT SIGN(COUNT(columnName)) FROM Tables WHERE schemaName =  @schemaName AND objectName  = @objectName AND columnName  = @columnName AND RowColAgg  = @RowColAgg)
                IF (@Valid = 1)
                BEGIN
                    /* Finally, ensure the names passed bind to the schema. (anti-injection) */
                    SET @Valid = @Valid & (SELECT SIGN(COUNT(*)) FROM INFORMATION_SCHEMA.COLUMNS C WHERE C.TABLE_SCHEMA = @schemaName AND C.TABLE_NAME = @objectName AND C.COLUMN_NAME = @columnName)
                END
            END
        END
    END
    RETURN @Valid
END
在约束中使用的:

ALTER TABLE Tables ADD CONSTRAINT chkValidEntry
CHECK (ColumnValidate(schemaName, objectName, columnName, RowColAgg, AggFunction) = 1)
INSERT INTO Tables (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('Common', 'ShiftInfo', 'ShiftMonth', 'Col', NULL);
INSERT INTO Tables (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('Common', 'ShiftInfo', 'ShiftId', 'Agg', 'count');
INSERT INTO Tables (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('Common', 'ShiftInfo', 'ShiftYear', 'Row', NULL);
现在,使用select语句,我可以看到该函数将指定的行验证为=1:

SELECT ColumnValidate('Common', 'ShiftInfo', 'ShiftMonth', 'Col', NULL);--1
SELECT ColumnValidate('Common', 'ShiftInfo', 'ShiftId', 'Agg', 'count');--1
SELECT ColumnValidate('Common', 'ShiftInfo', 'ShiftYear', 'Row', NULL);--1
因此,是的,对于提供的名称,模式检查在测试环境中是成功的 但是,由于检查约束,我的insert语句全部失败:

ALTER TABLE Tables ADD CONSTRAINT chkValidEntry
CHECK (ColumnValidate(schemaName, objectName, columnName, RowColAgg, AggFunction) = 1)
INSERT INTO Tables (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('Common', 'ShiftInfo', 'ShiftMonth', 'Col', NULL);
INSERT INTO Tables (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('Common', 'ShiftInfo', 'ShiftId', 'Agg', 'count');
INSERT INTO Tables (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('Common', 'ShiftInfo', 'ShiftYear', 'Row', NULL);
有关详细信息:@@VERSION=

    Microsoft SQL Server 2008 R2 (SP1) - 10.50.2550.0 (X64) 
    Jun 11 2012 16:41:53 
    Copyright (c) Microsoft Corporation
    Standard Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1) (Hypervisor)

当检查中的条件应为真时,为什么会触发约束?

好的,我刚刚删除了我的注释,即约束在插入数据之前运行,因为这是不正确的。简单的事实是INSERT语句将值插入表中,然后运行约束检查。刚刚用调试器测试过。我敢肯定,这种行为也有记录在案。 很简单,您的试运行测试成功是因为表中没有数据。但是插入会添加行,然后在各种验证中失败,即不存在具有一个或多个相同值的前一行

要查看此行为,请向函数中添加以下行,然后调试:

/* If this is an Aggregate or Column header, ensure this is the only one. */
    DECLARE @cnt int = (SELECT COUNT(columnName) FROM Tables WHERE schemaName =  @schemaName AND objectName  = @objectName AND RowColAgg  = @RowColAgg) 
您将看到@cnt是1,因为字段已经插入,因此测试失败

我还无法验证我的声明,即约束在插入之后运行。试图对MartinSmith的评论进行一些研究,我发现这篇文章设置了一个与你非常相似的场景。这些评论值得一读。Adam Mechanical准确地指出了Martin对udf逐行评估的看法

订正建议: 您的udf基本上是一组条件唯一约束。您发布的所有规则都可以作为更简单的检查约束或索引视图来实施,这些约束或索引视图将在基于集合的模式下工作,因此无需解决遇到的意外行为,也无需检查“第一行是否正常”: 前两个验证可以简化为简单的非udf检查约束:

CREATE TABLE Tables 
(
    schemaName SYSNAME,
    objectName SYSNAME,
    columnName SYSNAME,
    RowColAgg VARCHAR(3) NOT NULL, 
    AggFunction NVARCHAR(128),
    CONSTRAINT [Chk_RowColAgg_IsValid] CHECK(RowColAgg='Row' OR RowColAgg='Agg' OR RowColAgg='Col'),
    CONSTRAINT [Chk_AggHasFn] CHECK((RowColAgg='Agg' AND AggFunction IS NOT NULL) OR (RowColAgg!='Agg'))
)
GO
接下来的两个验证可以由索引视图实施:

CREATE VIEW UniqueTablesAggregateOrColumn
WITH SCHEMABINDING
AS
SELECT schemaName, objectName, RowColAgg FROM dbo.Tables WHERE RowColAgg = 'Agg' OR RowColAgg = 'Col';
GO    
CREATE UNIQUE CLUSTERED INDEX PK_UniqueAggOrCol ON dbo.UniqueTablesAggregateOrColumn(schemaName, objectName, RowColAgg);
GO

您的插入测试现在将在第一次运行时成功,然后失败并随后重复


最后的验证是对现有表的模式进行检查。我想这可能会作为udf约束,或者更合适地放在更新/插入触发器中。

有趣的差异。不明显。您知道可以在SSMS中调试INSERT语句吗?这将允许您单步执行约束函数,您将看到哪个子句未通过验证。我将放弃这一点,并尝试以声明方式执行它。尝试在引用UDF的检查约束中执行这种类型的操作是有问题的。您需要考虑多行更新/插入的效果以及它是否在快照隔离或其他情况下运行。“你能用通俗易懂的英语说明它应该做什么,这样我们就不必区分你对位运算符和符号的模糊用法了吗?”马丁史密斯有点不同意。表约束将针对多行更新/插入中的所有行运行。posted函数基本上是一个复杂的唯一约束…唯一表约束在多行更新/插入中运行时不会出现问题。这就是说,我同意OP可能会以其他更有效或更稳健的方式完成验证。@引用UDF的mdisibio检查约束将在RBAR中进行评估,而不是在语句末尾,这意味着它们可能会使本应成功的事务失败。内置唯一约束不会发生这种情况。此外,内置的约束被正确编码,以便在不考虑隔离级别的情况下正常工作。也许OP可以通过一个唯一的过滤索引或索引视图更有效、更正确地实现他们所需的功能,但我不打算通过反向工程来发现这些功能。@mdisibio-工作正常。-没有,谢谢。我没想到订单是先插入再检查。我将我的函数调到使用直接计数和按位OR。现在,所需类型的第一次插入结果为1,并且会违反的update语句也被适当地停止。谢谢你的提示。我已经更新了我的答案,并提供了一个更加可靠和正确的验证建议。MartinSmith的评论是非常有效的,尽管你已经通过添加一个计数解决了眼前的问题,但总体方法应该避免针对你的特殊情况使用UDF。这是好的观点。虽然该表不适用于高流量,但我没有考虑视图上绑定到模式的唯一索引。这会让我用它做更多的事情。谢谢你的反馈。