2017-09-24 23 views
0

在庫計算のための加重平均コストを計算するために意図したとおりに動作する再帰的クエリがあります。私の問題は、異なる列でグループ化された同じクエリから複数の加重平均が必要なことです。問題をキー列ごとに1回ずつ計算して解決できることは分かっています。しかし、クエリのパフォーマンス上の考慮事項のために、私は一度それを横断したい。時には1M +行があります。再帰的クエリ(SQL Server)でグループ化された結果

私はデータを簡略化し、加重平均を単純な和に置き換えて、私の問題をより簡単に従わせました。

どのように再帰的なcteを使用して結果を得ることができますか?加重平均コストを計算するには再帰クエリを使用する必要があることを忘れないでください。 I(Idはまた、ソート順である。同上、キーが一緒にユニークである。)は、SQLサーバー上で午前2016

例データ

Id Key1 Key2 Key3 Value 
1 1  1  1  10 
2 1  1  1  10 
3 1  2  1  10 
4 2  2  1  10 
5 1  2  1  10 
6 1  1  2  10 
7 1  1  1  10 
8 3  3  1  10 

期待される結果

Id Key1 Key2 Key3 Value Key1Sum Key2Sum Key3Sum 
1 1  1  1  10  10  10  10 
2 1  1  1  10  20  20  20 
3 1  2  1  10  30  10  30 
4 2  2  1  10  10  20  40 
5 1  2  1  10  40  30  50 
6 1  1  2  10  50  30  10 
7 1  1  1  10  60  40  60 
8 3  3  1  10  10  10  70 

EDIT

よく批判を受けなければならないのですが、どうやって質問をするのかがはるかに良くなければなりません。

ここに例があります。なぜ再帰的なクエリが必要なのですか。この例ではKey1の結果が得られますが、同じクエリでKey2とKey3も必要です。私は同じクエリを3回繰り返すことができますが、それは好ましくありません。

DECLARE @InventoryItem AS TABLE (
    IntentoryItemId INT NULL, 
    InventoryOrder INT, 
    Key1 INT NULL, 
    Key2 INT NULL, 
    Key3 INT NULL, 
    Quantity NUMERIC(22,9) NOT NULL, 
    Price NUMERIC(16,9) NOT NULL 
); 

INSERT INTO @InventoryItem (
    IntentoryItemId, 
    InventoryOrder, 
    Key1, 
    Key2, 
    Key3, 
    Quantity, 
    Price 
) 
VALUES 
(1, NULL, 1, 1, 1, 10, 1), 
(2, NULL, 1, 1, 1, 10, 2), 
(3, NULL, 1, 2, 1, 10, 2), 
(4, NULL, 2, 2, 1, 10, 1), 
(5, NULL, 1, 2, 1, 10, 5), 
(6, NULL, 1, 1, 2, 10, 3), 
(7, NULL, 1, 1, 1, 10, 3), 
(8, NULL, 3, 3, 1, 10, 1); 


--The steps below will give me the cost "grouped" by Key1 
WITH Key1RowNumber AS (
    SELECT 
     IntentoryItemId, 
     ROW_NUMBER() OVER (PARTITION BY Key1 ORDER BY IntentoryItemId) AS RowNumber 
    FROM @InventoryItem 
) 

UPDATE @InventoryItem 
    SET InventoryOrder = Key1RowNumber.RowNumber 
FROM @InventoryItem InventoryItem 
INNER JOIN Key1RowNumber 
ON Key1RowNumber.IntentoryItemId = InventoryItem.IntentoryItemId; 

WITH cte AS (
    SELECT 
     IntentoryItemId, 
     InventoryOrder, 
     Key1, 
     Quantity, 
     Price, 
     CONVERT(NUMERIC(22,9), InventoryItem.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price)/NULLIF(InventoryItem.Quantity, 0)) AS AvgPrice 
    FROM @InventoryItem InventoryItem 
    WHERE InventoryItem.InventoryOrder = 1 
    UNION ALL 
    SELECT 
     Sub.IntentoryItemId, 
     Sub.InventoryOrder, 
     Sub.Key1, 
     Sub.Quantity, 
     Sub.Price, 
     CONVERT(NUMERIC(22,9), Main.CurrentQuantity + Sub.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9), 
       ((Main.CurrentQuantity) * Main.AvgPrice + Sub.Quantity * Sub.price) 
        /
       NULLIF((Main.CurrentQuantity) + Sub.Quantity, 0) 
     ) AS AvgPrice 
    FROM CTE Main 
    INNER JOIN @InventoryItem Sub 
    ON Main.Key1 = Sub.Key1 
    AND Sub.InventoryOrder = main.InventoryOrder + 1 
) 

SELECT cte.IntentoryItemId, cte.AvgPrice 
FROM cte 
ORDER BY IntentoryItemId 
+0

あなたは何を試してみましたか?つまり、どこに失われていますか? [最小限で完全で検証可能なサンプルを作成する方法](https://stackoverflow.com/help/mcve)を確認し、質問を修正してください。 – jhenderson2099

+0

SQL Server 2012以降を使用している場合は、ウィンドウ関数を使用した方が再帰よりもパフォーマンスが向上する可能性があります。 –

+0

最新の回答を確認してください。 – KumarHarsh

答えて

0

... 2012 &以降のSQL Serverでそれを行う方法です?

第2に、私はあなたのDBデザインが間違っていると思いますか? key1 ,key2,key3unpivotedとなり、1つの列はKeysとなり、各キーグループを識別する列が1つ増えるはずです。

以下の例では明らかです。

私は私のクエリを最適化することができる場合、私は多くの行を計算すると思うことができますが、私は行数を制限しようとします。

また、可能な場合は、平均列の計算列を保持すると考えることができます。テーブルに値が設定されている場合は、それを計算して保存することができます。

出力が正しいかどうかを最初にお知らせください。

DECLARE @InventoryItem AS TABLE (
    IntentoryItemId INT NULL, 
    InventoryOrder INT, 
    Key1 INT NULL, 
    Key2 INT NULL, 
    Key3 INT NULL, 
    Quantity NUMERIC(22,9) NOT NULL, 
    Price NUMERIC(16,9) NOT NULL 
); 

INSERT INTO @InventoryItem (
    IntentoryItemId, 
    InventoryOrder, 
    Key1, 
    Key2, 
    Key3, 
    Quantity, 
    Price 
) 
VALUES 
(1, NULL, 1, 1, 1, 10, 1), 
(2, NULL, 1, 1, 1, 10, 2), 
(3, NULL, 1, 2, 1, 10, 2), 
(4, NULL, 2, 2, 1, 10, 1), 
(5, NULL, 1, 2, 1, 10, 5), 
(6, NULL, 1, 1, 2, 10, 3), 
(7, NULL, 1, 1, 1, 10, 3), 
(8, NULL, 3, 3, 1, 10, 1); 
--select * from @InventoryItem 
--return  
;with cte as 
(
select * 
, ROW_NUMBER() OVER (PARTITION BY Key1 ORDER BY IntentoryItemId) AS rn1 
, ROW_NUMBER() OVER (PARTITION BY Key2 ORDER BY IntentoryItemId) AS rn2 
, ROW_NUMBER() OVER (PARTITION BY Key3 ORDER BY IntentoryItemId) AS rn3 
from @InventoryItem 
) 
,cte1 AS (
     SELECT 
     IntentoryItemId, 

     Key1 keys, 
     Quantity, 
     Price 
     ,rn1 
     ,rn1 rn 
     ,1 pk 
    FROM cte c 

    union ALL 

    SELECT 
     IntentoryItemId, 

     Key2 keys, 
     Quantity, 
     Price 
     ,rn1 
     ,rn2 rn 
     ,2 pk 
    FROM cte c 

    union ALL 

    SELECT 
     IntentoryItemId, 

     Key3 keys, 
     Quantity, 
     Price 
     ,rn1 
     ,rn3 rn 
     ,3 pk 
    FROM cte c 

) 

, cte2 AS (
    SELECT 
     IntentoryItemId, 
     rn, 
     Keys, 
     Quantity, 
     Price, 
     CONVERT(NUMERIC(22,9), InventoryItem.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price)) a, 
      CONVERT(NUMERIC(22,9), InventoryItem.Price) b, 

     CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price)/NULLIF(InventoryItem.Quantity, 0)) AS AvgPrice 
     ,pk 
    FROM cte1 InventoryItem 
    WHERE InventoryItem.rn = 1 
    UNION ALL 
    SELECT 
     Sub.IntentoryItemId, 
     sub.rn, 
     Sub.Keys, 
     Sub.Quantity, 
     Sub.Price, 
     CONVERT(NUMERIC(22,9), Main.CurrentQuantity + Sub.Quantity) AS CurrentQuantity, 
     CONVERT(NUMERIC(22,9),Main.CurrentQuantity * Main.AvgPrice), 
     CONVERT(NUMERIC(22,9),Sub.Quantity * Sub.price), 

     CONVERT(NUMERIC(22,9), 
       ((Main.CurrentQuantity * Main.AvgPrice) + (Sub.Quantity * Sub.price)) 
        /
       NULLIF(((Main.CurrentQuantity) + Sub.Quantity), 0) 
     ) AS AvgPrice 
     ,sub.pk 
    FROM CTE2 Main 
    INNER JOIN cte1 Sub 
    ON Main.Keys = Sub.Keys and main.pk=sub.pk 
    AND Sub.rn = main.rn + 1 
    --and Sub.InventoryOrder<=2 
) 
select * 
,(select AvgPrice from cte2 c1 where pk=2 and c1.IntentoryItemId=c.IntentoryItemId) AvgPrice2 
,(select AvgPrice from cte2 c1 where pk=2 and c1.IntentoryItemId=c.IntentoryItemId) AvgPrice3 
from cte2 c 

where pk=1 
ORDER BY pk,rn 

(2012+ SQLの)代替ソリューションとジェイソンに多くのおかげで、

SELECT * 
,CONVERT(NUMERIC(22,9),avg((Quantity * Price)/NULLIF(Quantity, 0)) 
OVER(PARTITION BY Key1 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey1Price 
,CONVERT(NUMERIC(22,9),avg((Quantity * Price)/NULLIF(Quantity, 0)) 
OVER(PARTITION BY Key2 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey2Price 
,CONVERT(NUMERIC(22,9),avg((Quantity * Price)/NULLIF(Quantity, 0)) 
OVER(PARTITION BY Key3 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey3Price 
from @InventoryItem 
order by IntentoryItemId 
+0

1M +私のマネージャーは、計算されたデータを他の人とのエッジを維持するのが好きではないためです。あなたが言うように、データの準備を同時に変更するのではなく、データの準備を変更することが最善の方法なので、私はあなたの答えを求めます。再帰的操作が非常にコストがかかるので、私は反復回数を減らしてこれを行うことができるという夢がありました。 – Senno

0

はここ

IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL 
DROP TABLE #TestData; 

CREATE TABLE #TestData (
    Id INT, 
    Key1 INT, 
    Key2 INT, 
    Key3 INT, 
    [Value] INT 
    ); 
INSERT #TestData(Id, Key1, Key2, Key3, Value) VALUES 
    (1, 1, 1, 1, 10), 
    (2, 1, 1, 1, 10), 
    (3, 1, 2, 1, 10), 
    (4, 2, 2, 1, 10), 
    (5, 1, 2, 1, 10), 
    (6, 1, 1, 2, 10), 
    (7, 1, 1, 1, 10), 
    (8, 3, 3, 1, 10); 

--============================================================= 

SELECT 
    td.Id, td.Key1, td.Key2, td.Key3, td.Value, 
    Key1Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key1 ORDER BY td.Id ROWS UNBOUNDED PRECEDING), 
    Key2Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key2 ORDER BY td.Id ROWS UNBOUNDED PRECEDING), 
    Key3Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key3 ORDER BY td.Id ROWS UNBOUNDED PRECEDING) 
FROM 
    #TestData td 
ORDER BY 
    td.Id; 

結果...あなたは1M +行に計算することになるでしょうなぜ

Id   Key1  Key2  Key3  Value  Key1Sum  Key2Sum  Key3Sum 
----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- 
1   1   1   1   10   10   10   10 
2   1   1   1   10   20   20   20 
3   1   2   1   10   30   10   30 
4   2   2   1   10   10   20   40 
5   1   2   1   10   40   30   50 
6   1   1   2   10   50   30   10 
7   1   1   1   10   60   40   60 
8   3   3   1   10   10   10   70 
+0

ありがとう、私は元の質問を簡略化し、それは私の問題が何かを誤解させました。今、私はなぜ再利用を必要とするかを示す例を使って質問を更新しました。 – Senno

関連する問題