web-dev-qa-db-ja.com

再帰的に子供を集約するCTEの支援

在庫にあるすべてのアイテムの「販売可能」数量を計算するクエリを作成しようとしています。この場合、アイテムは現在利用可能な在庫を持つことができますが、完成品を形成するために組み立てることができる原材料で構成されるキットにすることもできます。したがって、販売可能なのは、現在利用可能な完成品(FG)の数量とFGにできるコンポーネントの最小数です。

例:

ラップトップとキャリングバッグで構成されるラップトップキットを販売しているとします。すでに2つのキットが作成されており、ラップトップ6つとキャリングバッグ3つがある場合、これらのキットを販売できるのは合計2 + 3 = 5です。この場合、キャリーバッグが制限要因になります。ラップトップは6つありますが、バッグの制限により、キットはあと3つしか作成できません。

ここまで来て、計算は最低レベル2からレベル1まで機能しますが、レベル0は正しくありません。したがって、この場合、ラップトップキットの計算は正しいです(手元に11個+さらに4個= 15個を販売に利用できます)。しかし、トップレベルのラップトップ&バッグキットは正しくありません。トップレベルの直接の子(ラップトップ&バッグキット)の販売可能な最小のアベイルズは、そのキットの15 + 3 = 14ではなく、18です。

enter image description here

最終選択で持っている左結合の代わりに、2番目の再帰CTEを追加する必要があると思いますか?

SQLフィドル

CREATE TABLE Item (
  Id INT,
  ParentId INT,
  DisplaySeq INT,  
  DisplayText VARCHAR(30),
  OnHandQty INT
  );

INSERT INTO Item (Id, ParentId, DisplaySeq, DisplayText, OnHandQty) VALUES
(9, NULL, 0, 'Laptop & Bag Kit', 3),
(8, 9, 5, 'Laptop Kit', 11),
(7, 8, 10, 'Laptop', 5),
(6, 8, 15, 'Power Supply', 4),
(26, 9, 20, 'Bag', 23)
;

;WITH items AS (
  SELECT 
    Id 
    , 0 as ParentId
    , Id as RootId
    , 0 AS Level
    , CAST(DisplaySeq AS VARCHAR(255)) AS Path
    , CAST('---' AS varchar(100)) AS LVL
    , CAST(DisplayText as VARCHAR(255)) as DisplayText
    , OnHandQty
  FROM Item 
  WHERE ParentId IS NULL

  UNION ALL

  SELECT 
    child.Id
    , child.ParentId
    , parent.RootId
    , Level + 1
    , CAST(parent.Path + '.' + CAST(child.DisplaySeq AS VARCHAR(255)) AS VARCHAR(255)) AS Path
    , CAST('---' + parent.LVL AS varchar(100)) AS LVL
    , CAST(parent.LVL + child.DisplayText as VARCHAR(255)) as DisplayText
    , child.OnHandQty
  FROM 
    Item child
      INNER JOIN items parent 
      ON parent.Id = child.ParentId
  )

SELECT 
  t.Path
  , t.RootId
  , t.Id
  , t.ParentId
  , t.Level
  , t.DisplayText
  , t.OnHandQty
  , COALESCE(s.MaxCanMake, t.OnHandQty) as MaxCanMake
  , t.OnHandQty + COALESCE(s.MaxCanMake, 0) as AvailToSell
FROM 
  items t
    left join (
        Select 
          ParentId,
          MIN(OnHandQty) as MaxCanMake
        FROM items
        GROUP BY ParentId
      ) as s
      ON t.Id = s.ParentId

ORDER BY t.Path
6
Chad Richardson

最初に階層を一時テーブルに展開します(計算列に注意してください)。

CREATE TABLE #Items
(
    Id integer PRIMARY KEY,
    MPath varchar(255) NOT NULL,
    DisplayText varchar(30) NOT NULL,
    OnHandQty integer NOT NULL,
    [Level] integer NOT NULL,
    ParentId integer NOT NULL,
    MaxCanMake integer NULL,
    AvailToSell AS OnHandQty + MaxCanMake
);

WITH Items AS
(
    SELECT 
        I.Id, 
        MPath = CONVERT(varchar(255), I.DisplaySeq), 
        I.DisplayText, 
        I.OnHandQty, 
        0 AS [Level], 
        0 AS ParentId
    FROM dbo.Item AS I 
    WHERE I.ParentId IS NULL

    UNION ALL

    SELECT 
        I.Id, 
        CONVERT(varchar(255), Parent.MPath + '.' + CONVERT(varchar(11), I.DisplaySeq)),
        I.DisplayText, 
        I.OnHandQty, 
        Parent.[Level] + 1, 
        I.ParentId
    FROM Items AS Parent
    JOIN dbo.Item AS I WITH (FORCESEEK)
        ON I.ParentId = Parent.Id
)
INSERT #Items
    (Id, MPath, DisplayText, OnHandQty, [Level], ParentId)
SELECT
    I.Id, I.MPath, I.DisplayText, I.OnHandQty, I.[Level], I.ParentId
FROM Items AS I
OPTION (MAXRECURSION 0);

-- Useful index
CREATE INDEX i 
ON #Items (ParentId, AvailToSell);

それは私たちに与えます:

╔════╦════════╦══════════════════╦═══════════ ╦═══════╦══════════╦════════════╦═════════════╗
║Id║MPath║DisplayText║OnHandQty║Level║ParentId║MaxCanMake║AvailToSell║
╠════╬════════╬═══════════ ═══════╬═══════════╬═══════╬══════════╬═══════════ ═╬═════════════╣
║9║0║ラップトップ&バッグキット║3║0║0║NULL║NULL║
║26║0.20 ║バッグ║23║1║9║NULL║NULL║
║8║0.5║ラップトップキット║11║1║9║NULL║NULL║
║7║0.5.10║ラップトップ║ 5║2║8║NULL║NULL║
║6║0.5.15║電源║4║2║8║NULL║NULL║
╚════╩════ ════╩══════════════════╩═══════════╩═══════╩══════ ════╩════════════╩═════════════╝

次に、最も深いところから始めて、レベルごとにMaxCanMakeを計算します。

DECLARE @Level integer =
(
    SELECT MAX(I.[Level])
    FROM #Items AS I
);

WHILE @Level >= 0
BEGIN
    UPDATE I
    SET I.MaxCanMake = 
        ISNULL
        (
            (
                SELECT TOP (1)
                    I2.AvailToSell
                FROM #Items AS I2
                WHERE I2.ParentId = I.Id
                ORDER BY
                    I2.AvailToSell ASC
            ),
            0
        )
    FROM #Items AS I
    WHERE I.[Level] = @Level;

    SET @Level -= 1;
END;

一時テーブルの計算列には、AvailToSellの変更が自動的に反映されます。

最終的な表示クエリは次のとおりです。

SELECT
    DisplayText = REPLICATE('---', I.[Level]) + I.DisplayText,
    I.OnHandQty,
    I.MaxCanMake,
    I.AvailToSell 
FROM #Items AS I
ORDER BY 
    I.MPath;
╔════════════════════╦═══════════╦═══════════ ═╦═════════════╗
║DisplayText║OnHandQty║MaxCanMake║AvailToSell║
╠══════════════ ══════╬═══════════╬════════════╬═════════════╣
║ラップトップ&バッグキット║3║15║18║
║---バッグ║23║0║23║
║---ラップトップキット║11║4║15║
║------ラップトップ║5║0║5║
║------電源║4║0║4║
╚═════ ═══════════════╩═══════════╩════════════╩═════════ ════╝

dbfiddle

4
Paul White 9