为什么这个 UDF 在这个检查约束中不起作用?

age*_*r85 8 sql-server functions check-constraints

I\xe2\x80\x99m 尝试使用 UDF 作为在检查约束内使用 SELECT 命令的替代方法。我需要对报告表施加约束,以确保signedBy字段是人员记录的PK,该人员记录在auth表中存在一条记录,其人员编号和authType为8(这表明他们有权签署报告)。我的 UDF 应该检查这样的身份验证记录是否存在并返回一个位(如果不存在则返回 0,如果存在则返回 1)。我正在开发 SQL Server Express Edition。

\n

我几乎在SQLFiddle重新创建了我的情况:

\n
CREATE TABLE personnel(\n    personnel INT IDENTITY(1, 1) NOT NULL,\n    firstName VARCHAR(20), \n    lastName VARCHAR(20),\n    login VARCHAR(20) DEFAULT NULL,\n    title varchar(20) DEFAULT NULL,\n    initials varchar(4) NOT NULL,\n    startDate DATE DEFAULT GETDATE(),\n    CONSTRAINT PkPersonnel PRIMARY KEY(personnel),\n    CONSTRAINT UqPersonnelFirstNameLastName UNIQUE(firstName, lastName),\n    CONSTRAINT UqPersonnelInitials UNIQUE(initials)\n);\nCREATE TABLE authType(\nauthType INT NOT NULL IDENTITY(1, 1),\nauthName varchar(50)\nCONSTRAINT PkAuthTypeAuthType PRIMARY KEY(authType)\n);\nCREATE TABLE auth(\nauth INT NOT NULL IDENTITY(1, 1),\npersonnel INT NOT NULL,\nauthtype INT NOT NULL,\ndate date DEFAULT GETDATE()\nCONSTRAINT PkAuthAuth PRIMARY KEY(auth)\nCONSTRAINT FkAuthAuthType FOREIGN KEY(authtype) REFERENCES authtype(authtype)\n);\nCREATE FUNCTION checkIfAuthorized (@personnel INTEGER, @authType INTEGER)\nRETURNS Bit\nAS\nBEGIN\n    Return Case \n        When Exists (Select 1 FROM auth\n                     Where personnel = @personnel AND authtype = @authtype)\n            Then 1 Else 0\n        End\nEND\nGO\nCREATE TABLE report(\n    report INTEGER NOT NULL IDENTITY(1, 1),\n    iteminJob INTEGER NOT NULL,\n    reportDate DATE NOT NULL,\n    notes VARCHAR(200),\n    signedBy INTEGER NOT NULL,\n    reviewedBy INTEGER,\n    conformance VARCHAR(30)\n    CONSTRAINT PkReport PRIMARY KEY(report),\n    CONSTRAINT [Reports must be associated with an item in a job.] FOREIGN KEY(iteminJob) REFERENCES iteminjob(iteminJob),\n    CONSTRAINT [Must be signed by an authorized signatory.] CHECK(metrologyTesting.dbo.checkIfAuthorized(signedBy, 8))\n);\n
Run Code Online (Sandbox Code Playgroud)\n

每当在 SQL Server Express 上创建报表表时,错误消息是:

\n
\n

\'checkIfAuthorized\' 不是可识别的内置函数名称。

\n
\n

即使在 SSMS 的对象资源管理器中,它显然位于 [MyDatabase]>Programmability>Functions>Scalar-valued Functions 下

\n

SQLFiddle 上的结果:

\n
\n

在需要条件的上下文中指定的非布尔类型表达式,位于 \')\' 附近。

\n
\n

……更令我困惑。UDF 设置为返回一个位。

\n

请不要建议我使用触发器。

\n

ype*_*eᵀᴹ 17

该错误是因为约束中的表达式CHECK必须产生布尔值(尽管 SQL Server 中没有BOOLEAN数据类型)。你的返回一个bit值。

\n

您可以通过将CHECK约束更改为以下方式从技术上解决此问题:

\n
CHECK(dbo.checkIfAuthorized(signedBy, 8) = CAST(1 AS bit))\n
Run Code Online (Sandbox Code Playgroud)\n

但在大多数情况下,这整个方法(CHECK约束中的子查询)是一个坏主意

\n

在http://sqlfiddle.com/#!18/e5501/8中测试(可以定义表)

\n

测试虽然在插入表期间检查了约束report,但在更新引用的表()时不会检查约束auth,因此在某些情况下约束将无法强制执行我认为设计的目的:http://sqlfiddle。 com/#!18/eb49d3/14

\n
\n

“坏主意”的原因是CHECK约束应该只使用单行中的。它们确实是“行约束”,至少在大多数 SQL DBMS 的当前实现中是这样,当然在 SQL Server 中也是如此。

\n

这就是为什么不能将子查询放入约束中的原因CHECK。我知道,您仍然可以使用 UDF(如您所示!)和子查询来绕过此限制来访问其他表,但这并不意味着它会按预期工作。

\n

CHECK仅当在具有约束的表上插入或更新行时才检查,而不是在更新引用的表时(更新或删除引用的行时)

\n

例如,Hugo Kornelis 在\n快照隔离:对完整性的威胁?(第 4 部分)由 Hugo Kornelis撰写,他讨论了与快照隔离相关的与您的约束非常相似的约束,指出:

\n
\n

请注意,此约束仅提供部分保护:没有任何东西可以阻止您从 Customers 表中删除行,即使它们被类型 A 订单 \xe2\x80\x93 引用,您也必须采取额外的步骤来防止这种情况发生。使用此约束 \xe2\x80\x93 仅检查 Orders 表中的插入和更新,但使用快照隔离,甚至不再可靠。

\n
\n

有关 SO 和 DBA 的各种博客文章和答案中有更多详细信息:

\n\n

如果完整性问题还不够,那么 UDF 通常还会带来性能问题:

\n\n
\n

注意:我对业务需求的假设是约束无限期地存在并且不是“暂时的”:报告由某人签署并且该人需要获得授权,因此签署人应该authauthtype=8. 一旦一个人签署了一些报告,我们就不应该允许删除这个人的签署授权。
\n(请参阅下面的区别)

\n

在约束中不使用 UDF 解决此问题的一种方法CHECK是使用FOREIGN KEY如下约束:

\n
    \n
  • 在表中添加一authtype列,其硬编码值为8(或任何表示授权签署人员的值)
  • \n
  • 添加一个FOREIGN KEY参考auth
  • \n
  • 上面还需要UNIQUEauth表进行额外的约束
  • \n
\n

您的定义(仅需要更改)
\n并在dbfiddle.uk中进行了测试

\n
CREATE TABLE auth(\n    auth INT NOT NULL IDENTITY(1, 1),\n    personnel INT NOT NULL,\n    authtype INT NOT NULL,\n    -- ...\n\n    -- UNIQUE constraint added\n    CONSTRAINT person_authtype \n        UNIQUE (personnel, authtype),\n    -- ...\n);\n\nCREATE TABLE report(\n    report INTEGER NOT NULL IDENTITY(1, 1),\n    iteminJob INTEGER NOT NULL,\n    reportDate DATE NOT NULL,\n    notes VARCHAR(200),\n    signedBy INTEGER NOT NULL,\n    -- ...\n\n    -- column added\n    authorized_to_sign_authtype AS CAST(8 AS INT) PERSISTED,\n    -- FOREIGN KEY added\n    CONSTRAINT must_be_signed_by_an_authorized_signatory \n        FOREIGN KEY (signedBy, authorized_to_sign_authtype)\n            REFERENCES auth (personnel, authtype)\n);\n
Run Code Online (Sandbox Code Playgroud)\n
\n

现在,如果此约束的目的和业务要求是检查签署报告的人在报告时是否获得授权,并且只有在那时我们才不介意该人后来是否从授权签署人中删除,那么您可能会遇到一个用例,其中此CHECK约束有意义(而我上面的建议肯定不会)。

\n

如果这是正确记录的(约束仅在插入或更新表时有效report)并且将来的验证并不意味着通过,并且我们意识到所有潜在的附带问题(性能 - 并行性、完整性)快照隔离等),那么好吧,这不是一个太糟糕的主意;)

\n

我仍然更愿意使用(更复杂的)RI 约束或存储过程来实现此要求和类似的要求。

\n