为什么这个检查约束会触发?

时间:2014-06-13 21:33:08

标签: 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
    , @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)

现在,使用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语句都失败了:

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)

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

1 个答案:

答案 0 :(得分:2)

好的,我刚刚删除了我的评论,即在插入数据之前运行约束,因为这是不正确的。简单的事实是INSERT语句将值插入Tables表,然后运行约束检查。 (刚刚使用调试器进行了测试。我确信行为也会记录在某处。) 非常简单,您的干运行测试成功,因为表中没有数据。但是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的评论进行一些研究,我发现这篇关于Constraints calling UDF's的帖子,它设置了一个与你非常相似的场景。评论值得一读。 Adam Mechanic确切地指出了Martin对于逐行评估udf的看法。

修改建议: 你的udf基本上是一组条件唯一约束。您发布的所有规则都可以作为更简单的CHECK约束或索引视图强制执行,这些约束或索引视图将在基于集合的模式下工作,因此无需解决您遇到的意外行为并检查“第一行是否正常” : 前两个验证可以简化为简单的非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

CREATE VIEW UniqueTablesRow
WITH SCHEMABINDING
AS
SELECT schemaName, objectName, columnName, RowColAgg FROM dbo.Tables WHERE RowColAgg = 'Row';
GO    
CREATE UNIQUE CLUSTERED INDEX PK_UniqueTablesRow ON dbo.UniqueTablesRow(schemaName, objectName, columnName);
GO

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

最终验证是对现有表的模式进行检查。我想这可能会被保留为udf约束,或者更恰当地保留在UPDATE / INSERT触发器中。