到目前为止的[简化]故事:
在 Visual Studio 2010 下的 .mdf DB 中,我有一个下表:
CREATE TABLE [dbo].[SandTable](
[id] [int] IDENTITY(1,1) NOT NULL,
[isDone] [bit] NOT NULL,
[percentComplete] AS ([dbo].[CompletePercent]([id],[isDone])),
[parentId] [int] NULL,
CONSTRAINT [PK_SandTable] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
ALTER TABLE [dbo].[SandTable] WITH CHECK ADD CONSTRAINT [FK_SandTable_SandTable] FOREIGN KEY([parentId])
这个想法是将行形成为树/森林,parentId 用作指向父节点的“指针”。
“percentComplete”计算列使用函数 CompletePercent 来计算以该行为根的子树的完整程度,如下所示:
- 如果一行的“isDone”位为 1,那么我们认为整个子树是 100% 完成的(这是用户覆盖),因此返回 1.0。
- 但是,如果“isDone”为 0,我需要计算整个子树的“完整性”。我通过递归平均直接孩子的“完整性”来做到这一点,直接孩子这样做是为了他们的孩子,依此类推,直到叶子。
起初,我试图让“CompletePercent”平均直接孩子的“percentComplete”列。但是,正如我发现的(后来在网上确认),计算列不能用作计算列计算的一部分。
目前,使用 CompletePercent 的以下实现,我总是为 'isDone'=1 行获得 1 和 0 为 'isDone'=0 行而感到沮丧:
CREATE FUNCTION [dbo].[CompletePercent]
(
@id int,
@isDone bit = 0
)
RETURNS float
AS
BEGIN
DECLARE @result float
IF @isDone = 1
SET @result = 1.0
ELSE
SET @result =
(SELECT
CASE
WHEN (COUNT(*) = 0) THEN 0.0
ELSE AVG(dbo.CompletePercent(id, isDone))
END
FROM dbo.SandTable
WHERE parentId = @id
)
RETURN @result
END
我希望这里有一些简单的东西,我只是想念它,因为盯着它看了这么久。
我的下一步是尝试使用我目前正在研究的递归 CTE。但是,我不确定如何编写所需的“特殊”条件平均。
如果有人能在我迄今为止的行动中发现错误,或指导我朝 CTE 方向发展,我将不胜感激。
[编辑:]即使在 CTE 轨道上,我也走到了死胡同,下面是疯狂的(如果可以运行的话,可能很浪费)查询:
WITH Weights AS (SELECT SandTable.id, COUNT(NULLIF (SandTable.isDone, 0)) AS isDone, 100.0 AS weight, COUNT(ST.id) AS kids
FROM SandTable INNER JOIN
SandTable AS ST ON SandTable.id = ST.parentId
WHERE (SandTable.parentId IS NULL)
GROUP BY SandTable.id
UNION ALL
SELECT SandTable_1.id, COUNT(NULLIF (SandTable_1.isDone, 0)) AS isDone, MyCTE_2.weight / MyCTE_2.kids AS weight, COUNT(ST_1.id) AS kids
FROM SandTable AS SandTable_1 INNER JOIN
MyCTE AS MyCTE_2 ON SandTable_1.parentId = MyCTE_2.id AND MyCTE_2.isDone = 0 INNER JOIN
SandTable AS ST_1 ON SandTable.id = ST_1.parentId
WHERE (SandTable_1.parentId IS NOT NULL)
GROUP BY SandTable_1.id)
SELECT SUM(weight)
FROM Weights AS Weights_1
WHERE (isDone > 0)
这个想法是沿着层次结构向下移动(目前从根开始,但我计划修改它以从特定的 id 开始),并为每个节点计算子节点的数量并测试“isDone”(在此处作为聚合完成考虑用于执行计数的 JOIN,现在如果 isDone 不为 0,则在 CTE 的结果中将其视为“真”)。每个节点的“权重”(实际上是它占总数的百分比)是它的父节点的权重除以其兄弟节点(包括它自己)的数量,根集为 100%。
对于“isDone”节点或叶子节点,向下行程停止。两者都将有下一个递归步骤返回 0 行)。
最后,将“idDone”节点的总权重相加(其他节点仅用于递归)。
但是,这无法运行,因为结果错误表明:“在递归公用表表达式的递归部分中不允许使用 GROUP BY、HAVING 或聚合函数”。
同样,我们将不胜感激任何有关在任何方向取得进展的提示。
问候,ShaiB