Sql server 2008 r2 SQL计算列,递归地汇总层次结构

Sql server 2008 r2 SQL计算列,递归地汇总层次结构,sql-server-2008-r2,common-table-expression,recursive-query,calculated-columns,Sql Server 2008 R2,Common Table Expression,Recursive Query,Calculated Columns,迄今为止的[简化]故事: 在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, CO

迄今为止的[简化]故事:

在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来计算以行为根的子树的完整程度,如下所示:

  • 如果一行的“ISDOWN”位是1,那么我们认为整个子树是100%个完整的(这是一个用户覆盖),因此返回1。
  • 但是,如果'isDone'为0,我需要计算整个子树的'completure'。我通过递归平均直接子对象的“完整性”来实现这一点,直接子对象为其子对象这样做,依此类推,直到子对象离开
起初,我试图让“CompletePercent”平均直接儿童的“percentComplete”列。但是,正如我发现的(后来在网上确认的),计算列不能用作计算列计算的一部分

目前,我对使用CompletePercent的以下实现总是得到1表示“isDone”=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轨道上也走到了死胡同,有以下疯狂(如果可以运行的话,可能是浪费)的查询:

想法是沿着层次结构向下移动(目前是从根开始,但我计划修改它以一个特定的id开始),并为每个节点计算子节点的数量并测试“isDone”(在这里作为一个聚合来说明用于执行计数的连接,现在如果isDone不是0,则在CTE的结果中被认为是“true”)。每个节点的“权重”(实际上是它占总节点的百分比)是它的父节点的权重除以它的兄弟节点数(包括它自己),根集为100%

对于“isDone”节点或叶节点,将停止跳闸。这两个步骤都将具有下一个递归步骤(返回0行)

最后,将“idDone”节点的总权重相加(其他节点仅用于递归)

但是,此操作无法运行,因为结果错误表明: “递归公共表表达式的递归部分中不允许使用GROUP BY、HAVING或AGGRATE function”

同样,任何关于在任何方向取得任何进展的暗示都将不胜感激

问候,,
ShaiB

不管你选择哪条路线,这可能是一项相当昂贵的手术。但是,以下是一些可能有帮助的想法:

首先,您是否考虑过使用视图?您可以将计算列拖放到表中,并将其添加到视图中,这可能会使您绕过计算列约束。您还可以使视图可更新(通过而不是触发器),以便对您的应用程序来说,它的行为类似于表

其次,您可以通过存储过程来实现这一点。使用光标一次遍历一行基表,计算
percentComplete
列的值,并将结果存储在表变量中。(您可以这样写,只需访问基表中的每一行一次。)然后简单地返回(即,选择)表变量的结果

第三,与第二个类似,在插入/更新/删除后,编写一个触发器来重新计算每行的
percentComplete
,而不是使用计算列。虽然这会让你读得非常快,但写起来可能会非常慢

第四,您可能可以通过CLR函数实现这一点(即,用C#编写它并将其导入服务器)。对于带有CLR函数的函数,您可以打破许多SQL Server(愚蠢的)规则。(尽管如此,这并不意味着它总是一个好主意。)

第五,也可能是最复杂的,您可以编写一个CLR table函数来读取表中的行(不带
percentComplete
),计算并将
percentComplete
列附加到结果集中。然后,将其用作视图的基础(即,
SELECT*FROM dbo.GetTheTree()
),然后使用instead of触发器使视图可更新(类似于第二个选项)


希望能给你一些想法

您使用的是哪个SQL Server版本?(“Visual Studio 2010”只是一个客户端应用程序,它没有告诉我们有关数据库的任何信息)很抱歉没有指定此项:我的开发计算机上安装了MS SQL Server 2008 R2。我正在通过VS的服务器资源管理器编辑所有内容。
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)