これまでの [簡略化された] ストーリー:
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 を取得し、'isDone'=0 行に対して 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 を考慮し、0 でない場合、CTE の結果で isDone が「true」と見なされるようになりました)。各ノードの「重み」(実際には合計に占める割合) は、親の重みをその兄弟 (それ自体を含む) の数で割ったもので、ルートは 100% に設定されています。
トリップ ダウンは、「isDone」ノードまたはリーフで停止します。どちらも次の再帰ステップで 0 行を返します)。
最後に、「idDone」ノードの合計重みが合計されます (他のノードは再帰のためだけに存在していました)。
ただし、結果のエラーが次のように述べているため、これは実行に失敗します。
繰り返しになりますが、あらゆる方向に前進するためのヒントは大歓迎です。
よろしく、ShaiB