Sql server 2008 r2 SQL计算列,递归地汇总层次结构
迄今为止的[简化]故事: 在Visual Studio 2010下的.mdf DB中,我有一个如下表: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
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'。我通过递归平均直接子对象的“完整性”来实现这一点,直接子对象为其子对象这样做,依此类推,直到子对象离开
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)