2

これまでの [簡略化された] ストーリー:

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

4

1 に答える 1

0

選択したルートに関係なく、おそらくかなりの費用がかかる操作になるでしょう。しかし、ここに役立つかもしれないいくつかのアイデアがあります:

まず、ビューの使用について考えたことはありますか? 計算列をテーブルにドロップしてビューに追加すると、おそらく計算列の制約を回避できます。ビューを (代わりにトリガーを介して) 更新可能にすることもできます。これにより、アプリケーションにとってテーブルのように動作します。

次に、ストアド プロシージャを使用してこれを行うことができます。カーソルを使用して、一度に 1 行ずつベース テーブルを反復処理し、percentComplete列の値を計算して、結果をテーブル変数に格納します。(ベース テーブルの各行に 1 回だけアクセスする必要があるように、これを記述することもできます。) 次に、テーブル変数から結果を返す (つまり、SELECT) だけです。

percentComplete3 番目に、2 番目と同様に、計算列を使用する代わりに、挿入/更新/削除後に各行を再計算するトリガーを記述します。これにより、読み取りは非常に高速になりますが、書き込みはおそらく非常に遅くなります。

第 4 に、おそらく CLR 関数を介してこれを行うことができます (つまり、C# で記述してサーバーにインポートします)。CLR 関数を使用した関数に対する SQL Server の (間抜けな) 規則の多くを破ることで問題を解決できます。(ただし、それが常に良いアイデアであるとは限りません。)

5 つ目は、おそらく最も複雑な方法ですが、テーブルから行を読み取り ( を使用せずに)、列を計算して結果セットにpercentComplete追加するCLR テーブル関数を作成できます。percentComplete次に、これをビューの基礎として使用し (つまりSELECT * FROM dbo.GetTheTree())、代わりにトリガを使用してビューを更新可能にします (2 番目のオプションと同様)。

それがあなたにいくつかのアイデアを与えることを願っています!

于 2013-03-16T02:35:20.933 に答える