web-dev-qa-db-fra.com

SQL - Sous-requête dans la fonction d'agrégation

J'utilise la base de données northwind pour actualiser mes compétences en SQL en créant des requêtes plus ou moins complexes. Malheureusement, je n'ai pas trouvé de solution à mon dernier cas d'utilisation: "Obtenez la somme des cinq plus grosses commandes pour toutes les catégories de l'année 1997."

Les tables impliquées sont:

Orders(OrderId, OrderDate)
Order Details(OrderId, ProductId, Quantity, UnitPrice)
Products(ProductId, CategoryId)
Categories(CategoryId, CategoryName)

J'ai essayé la requête suivante

SELECT c.CategoryName, SUM(
  (SELECT TOP 5 od2.UnitPrice*od2.Quantity 
   FROM [Order Details] od2, Products p2
   WHERE od2.ProductID = p2.ProductID
   AND c.CategoryID = p2.CategoryID
   ORDER BY 1 DESC))
FROM [Order Details] od, Products p, Categories c, Orders o 
WHERE od.ProductID = p. ProductID
AND p.CategoryID = c.CategoryID
AND od.OrderID = o.OrderID
AND YEAR(o.OrderDate) = 1997
GROUP BY c.CategoryName

Eh bien ... Il s'est avéré que les sous-requêtes ne sont pas autorisées dans les fonctions d'agrégation. J'ai lu d'autres articles sur ce problème mais je n'ai pas trouvé de solution à mon cas d'utilisation spécifique. J'espère que vous pourrez m'aider ...

8
Thomas

Les sous-requêtes ne sont généralement pas autorisées dans les fonctions d'agrégation. Au lieu de cela, déplacez l'agrégat inside la sous-requête. Dans ce cas, vous aurez besoin d'un niveau supplémentaire de sous-requête en raison du top 5:

SELECT c.CategoryName,
  (select sum(val)
   from (SELECT TOP 5 od2.UnitPrice*od2.Quantity as val
         FROM [Order Details] od2, Products p2
         WHERE od2.ProductID = p2.ProductID
         AND c.CategoryID = p2.CategoryID
         ORDER BY 1 DESC
        ) t
  )
FROM [Order Details] od, Products p, Categories c, Orders o 
WHERE od.ProductID = p. ProductID
AND p.CategoryID = c.CategoryID
AND od.OrderID = o.OrderID
AND YEAR(o.OrderDate) = 1997
GROUP BY c.CategoryName, c.CategoryId
22
Gordon Linoff

Utilisez CTE avec ROW_NUMBER fonction de classement au lieu d'une sous-requête excessive. 

 ;WITH cte AS
 (
  SELECT c.CategoryName, od2.UnitPrice, od2.Quantity,
         ROW_NUMBER() OVER(PARTITION BY c.CategoryName ORDER BY od2.UnitPrice * od2.Quantity DESC) AS rn
  FROM [Order Details] od JOIN Products p ON od.ProductID = p.ProductID
                          JOIN Categories c ON p.CategoryID = c.CategoryID
                          JOIN Orders o ON od.OrderID = o.OrderID
  WHERE o.OrderDate >= DATEADD(YEAR, DATEDIFF(YEAR, 0, '19970101'), 0)
    AND o.OrderDate < DATEADD(YEAR, DATEDIFF(YEAR, 0, '19970101')+1, 0)
  )
  SELECT CategoryName, SUM(UnitPrice * Quantity) AS val
  FROM cte
  WHERE rn < 6
  GROUP BY CategoryName
4

C'est certainement un problème de sous-requête ici est un excellent article à ce sujet (écrit à l'origine pour Access mais la syntaxe est identique), orderdate = 1997 donnera la date de commande pour le 1 er janvier 1997 '- il vous faut datepart (année, orderdate) = 1997, une fois que vous avez renvoyé (jusqu'à cinq) lignes pour chaque catégorie, vous pouvez ensuite encapsuler les lignes renvoyées et les regrouper. 

3
Ian P

J'ai rencontré un problème très similaire avec une sous-requête Access où les enregistrements ont été triés par date. Lorsque j'ai utilisé la fonction d'agrégation "Last", j'ai constaté qu'elle passait dans toutes les sous-requêtes et récupérait la dernière ligne de données de la table Access, et non la requête triée comme prévu. Bien que j'aurais pu réécrire la requête pour utiliser la fonction d'agrégation dans le premier ensemble de parenthèses (comme cela avait été suggéré précédemment), il m'a été plus facile d'enregistrer les résultats de la requête sous forme de table dans la base de données triée dans l'ordre que je souhaitais, puis d'utiliser le " "fonction agrégée pour récupérer les valeurs que je voulais. J'exécuterai une requête de mise à jour à l'avenir pour garder les résultats à jour. Pas efficace mais efficace. 

0
Peter Cleary