如何在检查约束中使用递归 CTE?

How to use a recursive CTE in a check constraint?

我正在尝试在 table 上创建一个 check constraint,这样 ParentID 永远不会是当前记录的后代

例如,我有一个table类别,具有以下字段ID、Name、ParentID

我有以下CTE

WITH Children AS (SELECT ID AS AncestorID, ID, ParentID AS NextAncestorID FROM Categories UNION ALL SELECT Categories.ID, Children.ID, Categories.ParentID FROM Categories JOIN Children ON Categories.ID = Children.NextAncestorID) SELECT ID FROM Children where AncestorID =99

此处的结果是正确的,但是当我尝试将其作为约束添加到 table 时,如下所示:

ALTER TABLE dbo.Categories ADD CONSTRAINT CK_Categories CHECK (ParentID NOT IN(WITH Children AS (SELECT ID AS AncestorID, ID, ParentID AS NextAncestorID FROM Categories UNION ALL SELECT Categories.ID, Children.ID, Categories.ParentID FROM Categories JOIN Children ON Categories.ID = Children.NextAncestorID) SELECT ID FROM Children where AncestorID =ID))

我收到以下错误:

Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.

WITH 前添加分号没有帮助。

执行此操作的正确方法是什么?

谢谢!

根据 column constraints 上的 SQL 服务器文档:

CHECK

Is a constraint that enforces domain integrity by limiting the possible values that can be entered into a column or columns.

logical_expression

Is a logical expression used in a CHECK constraint and returns TRUE or FALSE. logical_expression used with CHECK constraints cannot reference another table but can reference other columns in the same table for the same row. The expression cannot reference an alias data type.

(以上引用自SQL Server 2017版本的文档,但总的原则也适用于所有以前的版本,你没有说明你使用的是什么版本。)

此处的重要部分是“无法引用另一个 table,但可以引用 中的其他列 table 同一行”(强调)。一个 CTE 算作另一个 table.

因此,您不能使用像 CTE 那样用于 CHECK 约束的复杂查询。 就像萨曼建议的那样,如果你想检查其他行中的现有数据,并且它必须在数据库层中,你可以将其作为触发器。

但是,触发器有其自身的缺点(例如,可发现性问题、那些不知道触发器存在的人会出乎意料的行为)。

正如 Sami 在他们的评论中所建议的那样,另一种选择是 UDF,但这不是 w/o 它自己的问题,根据 this question about this approach in SQL Server 2008 上的答案可能会兼顾性能和稳定性。它可能仍然适用于更高版本。

如果可能的话,我认为通常最好将该逻辑移至应用层。我从您的评论中看到您已经进行了“客户端”验证。如果在该客户端和数据库服务器之间有一个应用服务器(例如在 Web 应用中),我建议将额外的验证放在那里(在应用服务器中)而不是在数据库中。