如何创建参数化递归 CTE 以展平标量函数中的层次结构?

Posted

技术标签:

【中文标题】如何创建参数化递归 CTE 以展平标量函数中的层次结构?【英文标题】:How does one Create a Parameterized Recursive CTE to flatten a heirarchy within a Scalar Function? 【发布时间】:2019-08-14 12:01:30 【问题描述】:

我正在尝试创建一个标量函数来确定提供 ID 的用户或其任何下属是否在提供的订单 ID 集合下有订单。

请注意,我正在使用我自己的用户定义表类型 IntegerIdTableType 来接收 OrderIds 的集合。

CREATE FUNCTION DoOrdersExistUnderUserOrUsersSubordinates
(
    @orderIds dbo.IntegerIdTableType READONLY,
    @userId INT
)
RETURNS BIT
AS
BEGIN
    RETURN 
    (
        WITH GetUserIds(ordinateUserId)
        AS
        (
            SELECT ordinateUserId UserId
            UNION ALL
            SELECT GetUserIds(Subordinate.Id)
            FROM UsersAccounts.Users Subordinates
            WHERE Subordinates.SupervisorId = @ordinateUserId
        )
        SELECT CASE WHEN EXISTS
        (
            SELECT 1
            FROM Orders
            WHERE Orders.Id IN
            (
                SELECT Id
                FROM @orderIds
            )
            AND Orders.UserId IN
            (
                SELECT UserId
                FROM GetUserIds(@userId)
            )
        )
        THEN CAST(1 AS BIT)
        ELSE CAST(0 AS BIT)
        END
    )
END

以下是我的 OrdersUsers 表的一些示例数据。

用户

订单

预期结果

当使用以下值调用 DoOrdersExistUnderUserOrUsersSubordinates 时,我期望得到以下结果。

我在使用此功能时遇到 2 个问题:

    语法错误:

    关键字“WITH”附近的语法不正确。

    ')' 附近的语法不正确。

    'GetUserIds' 不是可识别的内置函数名称

    即使没有包装在函数中,上述情况似乎也会发生。

我不知道将参数传递给递归 CTE 的正确方法是什么,但我已经看到 CTE 的声明在括号中具有我认为是参数的名称的示例

我尝试在 WITH 之前立即放置一个分号,即使它是函数中唯一的语句,我只是得到 ';' 附近的语法不正确。 而不是 关键字“WITH”附近的语法不正确。

我也尝试过去掉BEGINEND,这给了我'RETURN' 附近的语法错误。,以及关键字'with' 附近的语法错误.如果此语句是公用表表达式、xmlnamespaces 子句或更改跟踪上下文子句,则前面的语句必须以分号结束。如果我不包括多余的分号。

如何解决所有这些问题?

当然,递归 CTE 必须能够接受一个参数,或者它们会在什么上递归?

更新:

在与 Zohar_Peled 链接的文档的 Example F 进行斗争之后,我最终发现参数并没有像这样传递到 CTE 中,而是加入到它然后通过括号保留在其中其声明。然后在相应的SELECTs 中定义的任何内容都通过参数输出到称为 CTE 的任何内容(在这种情况下,外部 SELECT Id FROM UserNodes 语句或 CTE 本身(用于递归))。

我将函数内的 SQL 语句更改为以下内容,它在函数外按预期工作。

WITH UserNodes([Root User ID], Id, SupervisorId)
AS
(
    SELECT Users.Id, Users.Id, Users.SupervisorId
    FROM UsersAccounts.Users
    WHERE Users.SupervisorId IS NULL
    UNION ALL
    SELECT [Root User ID],
        Users.Id,
        Users.SupervisorId
    FROM UsersAccounts.Users
    JOIN UserNodes [Subordinate Descendant Users] ON [Subordinate Descendant Users].Id = Users.SupervisorId
)
SELECT CASE WHEN EXISTS
(
    SELECT 1
    FROM Orders
    WHERE Orders.Id IN
    (
        SELECT Id
        FROM @orderIds
    )
    AND Orders.UserId IN
    (
        SELECT Id
        FROM UserNodes
        WHERE [Root User ID] = @userId
    )
)
THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT)
END

这单独工作正常(提供了所需的变量来替换缺少的函数参数),但是一旦我将它放回 CREATE FUNCTION 块,我就会面临与以前相同的语法错误(不包括 2. )。

【问题讨论】:

您似乎对如何使用公用表表达式有些困惑。 SELECT GetUserIds(Subordinate.Id) 看起来像是在尝试执行标量函数,FROM GetUserIds(@userId) 看起来像是在尝试执行表值函数。这种混乱使整个问题变得非常不清楚。 从您的Function 看来,我会说您没有正确返回函数的值。您需要正确指定返回值。请看看这个link,这会让你知道如何自定义你的函数来...... @Birel 我只是想从函数中返回一个布尔 (BIT) 值,这不是我对 THEN CAST(1 AS BIT)ELSE CAST(0 AS BIT) 部分所做的吗?跨度> @ZoharPeled 抱歉,这是我第一次尝试使用 WITH 语法和 CTE;我的印象是它们就像本地化的函数,你可以从它们后面的 SELECT 查询中多次调用它们。我想避免为GetUserIds 创建一个单独的表值函数,因为我听说过在查询中多次调用函数对性能的影响(在这种情况下,对于用户而言,从自身内部,他们的每个后代)。 嗯,TSQL 有很好的文档记录,您可以从阅读 WITH common_table_expression 开始,然后通过示例找出代码有什么问题。 【参考方案1】:

如上所述,我无法对此进行测试,但这是我建议您更改的内容:

    CREATE FUNCTION DoOrdersExistUnderUserOrUsersSubordinates
(
    @orderIds dbo.IntegerIdTableType READONLY,
    @userId INT
)
RETURNS BIT
AS
BEGIN

    declare @bln bit

    ;WITH UserNodes([Root User ID], Id, SupervisorId)
    AS
    (
        SELECT Users.Id, Users.Id, Users.SupervisorId
        FROM UsersAccounts.Users
        WHERE Users.SupervisorId IS NULL
        UNION ALL
        SELECT [Root User ID],
            Users.Id,
            Users.SupervisorId
        FROM UsersAccounts.Users
        JOIN UserNodes [Subordinate Descendant Users] ON [Subordinate Descendant Users].Id = Users.SupervisorId
    )
    SELECT @bln = CASE WHEN EXISTS
    (
        SELECT 1
        FROM Orders
        WHERE Orders.Id IN
        (
            SELECT Id
            FROM @orderIds
        )
        AND Orders.UserId IN
        (
            SELECT Id
            FROM UserNodes
            WHERE [Root User ID] = @userId
        )
    )
    THEN CAST(1 AS BIT)
    ELSE CAST(0 AS BIT)
    END

    RETURN @bln
END

让我知道它是否有效......

【讨论】:

这没什么区别;它仍然给出Incorrect syntax near the keyword 'WITH'.的语法错误 如果你现在添加分号? 如果这种情况持续存在,请向我提供样本数据和预期结果,然后我将能够更有效地提供帮助... 和之前一样:Incorrect syntax near ';'.。当我从实际数据库中更改示例时,我必须创建一些示例数据,以免在网络上公开任何公司定制的数据结构。 太好了,我会等待示例数据,我现在也注意到 @ordinateUserId 字段没有声明?

以上是关于如何创建参数化递归 CTE 以展平标量函数中的层次结构?的主要内容,如果未能解决你的问题,请参考以下文章

CTE递归获取树层次结构

使用MySQL 8.0递归CTE查找层次结构表中的直接后代并传播给父级

如何在T-SQL中使用递归CTE获得完整的层次结构?

如何获得递归 CTE 中生成的最后一条记录?

在 CTE 中实现标量函数

SQL Server临时表表标量和CTE