2017-09-24 12 views
0

J'ai une requête récursive qui fonctionne comme prévu pour calculer le coût moyen pondéré pour le calcul de l'inventaire. Mon problème est que j'ai besoin de plusieurs moyennes pondérées à partir de la même requête regroupées par colonnes différentes. Je sais que je peux résoudre le problème en le calculant plusieurs fois, un pour chaque colonne clé. Mais à cause des considérations de performance de requête, je veux qu'il soit traversé une fois. Parfois, j'ai 1M + lignes.Résultat groupé dans une requête récursive (SQL Server)

J'ai simplifié les données et remplacé la moyenne pondérée par une somme simple pour rendre mon problème plus facile à suivre.

Comment obtenir le résultat ci-dessous en utilisant cte récursif? N'oubliez pas que je dois utiliser une requête récursive pour calculer le coût moyen pondéré. Je suis sur le serveur SQL 2016.

Exemple de données (Id est aussi l'ordre de tri. Le Id et la clé est unique ensemble.)

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 

Résultat attendu

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

Après quelques critiques bien méritées, je dois être beaucoup mieux dans la façon dont je pose une question.

Voici un exemple et pourquoi j'ai besoin d'une requête récursive. Dans l'exemple, j'obtiens le résultat pour Key1, mais j'en ai aussi besoin pour Key2 et Key3 dans la même requête. Je sais que je peux répéter la même requête trois fois, mais ce n'est pas préférable.

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

Qu'avez-vous essayé?C'est, où êtes-vous perdu? Veuillez consulter [Comment créer un exemple minimal, complet et vérifiable] (https://stackoverflow.com/help/mcve) et réviser votre question. – jhenderson2099

+0

Si vous utilisez SQL Server 2012 ou version ultérieure, vous obtiendrez probablement de meilleures performances avec les fonctions de fenêtrage qu'avec la récursivité. –

+0

vérifier ma dernière réponse. – KumarHarsh

Répondre

0

Pourquoi vous voulez calculer sur 1M + lignes ?

Deuxièmement, je pense que votre conception db est fausse? key1 ,key2,key3 aurait dû être unpivoted et une colonne appelée Keys et 1 autre colonne pour identifier chaque groupe de clés.

Il sera clair pour vous dans l'exemple ci-dessous.

Si je suis en mesure d'optimiser ma requête alors je peux penser à calculer de nombreuses lignes sinon j'essaie de limiter le nombre de lignes.

Aussi si possible, vous pouvez penser à garder la colonne calculée de Avg Price.i.e. Lorsque la table est remplie, vous pouvez la calculer et la stocker.

D'abord, faites-nous savoir, si la sortie est correcte ou non.

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 

Autre solution (pour Sql 2012+) et un grand merci à Jason,

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 + car mon manager n'aime pas les données calculées persistantes pour avoir un avantage sur les autres. Je vais aller chercher votre réponse, car comme vous le dites, il est probablement préférable de modifier la préparation des données plutôt que d'essayer de les calculer toutes simultanément. J'ai rêvé que je pourrais le faire en moins d'itérations car l'opération récursive est assez chère. – Senno

0

Voici comment faire dans SQL Server 2012 & plus tard ...

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; 

résultats ...

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

Merci, mais j'ai simplifié la question initiale et cela a conduit à une certaine incompréhension de mon problème. Maintenant, j'ai mis à jour la question avec un exemple qui montre pourquoi j'utilise/need récursion. – Senno