如何创建参数化递归 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
以下是我的 Orders 和 Users 表的一些示例数据。
用户
订单
预期结果
当使用以下值调用 DoOrdersExistUnderUserOrUsersSubordinates
时,我期望得到以下结果。
我在使用此功能时遇到 2 个问题:
语法错误:
关键字“WITH”附近的语法不正确。
')' 附近的语法不正确。
'GetUserIds' 不是可识别的内置函数名称
即使没有包装在函数中,上述情况似乎也会发生。
我不知道将参数传递给递归 CTE 的正确方法是什么,但我已经看到 CTE 的声明在括号中具有我认为是参数的名称的示例
我尝试在 WITH
之前立即放置一个分号,即使它是函数中唯一的语句,我只是得到 ';' 附近的语法不正确。 而不是 关键字“WITH”附近的语法不正确。
我也尝试过去掉BEGIN
和END
,这给了我'RETURN' 附近的语法错误。,以及关键字'with' 附近的语法错误.如果此语句是公用表表达式、xmlnamespaces
子句或更改跟踪上下文子句,则前面的语句必须以分号结束。如果我不包括多余的分号。
如何解决所有这些问题?
当然,递归 CTE 必须能够接受一个参数,或者它们会在什么上递归?
更新:
在与 Zohar_Peled 链接的文档的 Example F 进行斗争之后,我最终发现参数并没有像这样传递到 CTE 中,而是加入到它然后通过括号保留在其中其声明。然后在相应的SELECT
s 中定义的任何内容都通过参数输出到称为 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 以展平标量函数中的层次结构?的主要内容,如果未能解决你的问题,请参考以下文章