所以,我有一个元数据表:
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)
当检查中的条件应该为真时,为什么约束会触发?
答案 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触发器中。