SQL - Group By基于可选标志

时间:2015-10-06 15:46:49

标签: sql sql-server sql-server-2012

我需要根据数据创建一个销售摘要,其中包含一个可选的" Elected"标志,由用户控制。该标志确定是否对属于同一类项目的项目进行分组。

如果类中的所有项都没有将Elected标志设置为1,则这些项在摘要中显示为单独的行。如果类中的一个或多个项目的标志设置为1,则该类中的项目将根据设置了标志的第一个项目(最低item_id)进行分组。

例如:

+--------+------+-------+-------+-----+---------+
| ItemId | Item | Class | Price | Qty | Elected |
+--------+------+-------+-------+-----+---------+
|    1   |  A1  |   1   | 25.00 |  2  |    1    |
|    2   |  A2  |   1   | 20.00 |  3  |    0    |
|    3   |  A3  |   1   | 25.00 |  4  |    0    |
|    4   |  B1  |   2   | 44.00 |  6  |    0    |
|    5   |  B2  |   2   | 41.00 |  7  |    0    |
|    6   |  C1  |   3   | 44.00 |  5  |    1    |
|    7   |  D1  |   4   | 32.00 |  9  |    0    |
|    8   |  D2  |   4   | 30.00 |  8  |    1    |
|    9   |  D3  |   4   | 30.00 |  2  |    1    |
+--------+------+-------+-------+-----+---------+

应该提供以下摘要:

+-------+------+--------------+-------------+-----+
| Class | Item | Total Price* | Unit Price^ | Qty |
+-------+------+--------------+-------------+-----+
|   1   |  A1  |    70.00     |    35.00    |  2  |
|   2   |  B1  |    44.00     |     7.33    |  6  |
|   2   |  B2  |    41.00     |     5.85    |  7  |
|   3   |  C1  |    44.00     |     8.80    |  5  |
|   4   |  D2  |    92.00     |    11.50    |  8  |
+-------+------+--------------+-------------+-----+

* Total Price: SUM() within class
^ Unit Price: Total Price / Qty of elected item

在上表中,第1类通过A1汇总,第3类通过C1汇总,第4类通过D2汇总。第2类根本没有得到总结,而是完全列出。

下面的查询是通过多个自联接进行的黑客攻击,但它可以正常工作。有没有更有效的方法来解决这个问题?

SELECT Sales.Class,
       coalesce(SalesWithFlagName.Item, SalesWithoutFlag._Item) 'Item',
       SUM(Sales.Price) 'Total Price',
       SUM(Sales.Price) / coalesce(SUM(Sales.Qty), SUM(SalesWithoutFlag._Qty)) 'Unit Price', /* Ignore possible Div0 error */
       COALESCE(SUM(Sales.Qty), SUM(SalesWithoutFlag._Qty)) 'Qty'
FROM   Sales

/* Class with at least one flag set: Get first item with flag */
LEFT JOIN (
  SELECT Class _Class,
         Elected _Elected,
         MIN(ItemId) _ItemId
  FROM   Sales

  GROUP BY Class, Elected
) AS SalesWithFlag ON (
  Sales.ItemId = SalesWithFlag._ItemId
  AND Sales.Elected = 1
)

/* Get item name from first Left Join */
LEFT JOIN Sales SalesWithFlagName ON (
  SalesWithFlag._ItemId IS NOT NULL
  AND SalesWithFlag._ItemId = SalesWithFlagName.ItemId
)

/* Class with at least flag not set: Get all items without flag */
LEFT JOIN (
  SELECT Class _Class,
         ItemId _ItemId,
         Item   _Item,
         Qty    _Qty
  FROM   Sales s2

  WHERE NOT EXISTS (SELECT * FROM Sales s3 WHERE s3.Elected = 1 AND s2.Class = s3.Class)

  GROUP BY Class, ItemId, Item, Qty

) AS SalesWithoutFlag ON (
  Sales.ItemId = SalesWithoutFlag._ItemId
  AND Sales.Elected = 0
)

WHERE SalesWithFlag._ItemId IS NOT NULL 
   OR SalesWithoutFlag._ItemId IS NOT NULL

GROUP BY Sales.Class,
         COALESCE(SalesWithFlagName.Item, SalesWithoutFlag._Item)

3 个答案:

答案 0 :(得分:0)

您需要将每个类的价格SUM作为单独的实体,然后将它们连接到条件查询,并对没有标志的类执行相同的操作,而是将它们与选择联合起来语句:

;WITH CTE AS (
SELECT Class, SUM(Price) as [Total Price]
FROM Table1
GROUP BY Class)
, CTE2 AS (
SELECT DISTINCT Class 
FROM Table1 AS A
WHERE NOT EXISTS (SELECT DISTINCT Class FROM Table1 AS B WHERE A.Class=B.Class AND Elected = 1))
, CTE3 AS (
SELECT A.Class, A.ItemID, A.Item, B.[Total Price], ROUND(CAST(B.[Total Price] AS FLOAT) / Qty, 2) AS [Unit Price], Qty, ROW_NUMBER() OVER(PARTITION BY A.Class ORDER BY A.ItemID ASC) AS RN
FROM Table1 AS A
JOIN CTE AS B
ON A.Class=B.Class
WHERE A.Elected = 1
UNION ALL
SELECT Class, ItemId, Item, Price, ROUND(CAST(Price AS FLOAT) / Qty, 2), Qty, 1
FROM Table1
WHERE Class IN (SELECT Class FROM CTE2))
SELECT Class, Item, [Total Price], CAST([Unit Price] AS NUMERIC(36,2)) AS [Unit Price], Qty 
FROM CTE3
WHERE RN = 1
ORDER BY Class, Item

答案 1 :(得分:0)

您需要单独的行和聚合,因此这是Group Sum的一项任务和一些额外的逻辑:

WITH cte AS
 (
   SELECT *,
      -- either the individual Price or the Group Sum
      CASE
        WHEN Elected = 1 
          THEN SUM(Price) OVER (PARTITION BY Class) 
        ELSE Price 
      END AS TotalPrice,
      -- is there any Elected = 1 within the Class?
      MAX(Elected) OVER (PARTITION BY Class) AS maxElected,
      -- Find the first row with Elected = 1
      ROW_NUMBER() OVER (PARTITION BY Class ORDER BY Elected DESC, ItemId) AS rn
   FROM Sales
 )
SELECT
   class,
   Item,
   TotalPrice,
   TotalPrice / Qty AS UnitPrice,
   QTy
FROM cte
WHERE maxElected = 0 -- no Elected = 1
   OR rn = 1         -- first row with Elected = 1
ORDER BY 1,2
;

请参阅Fiddle

答案 2 :(得分:0)

您可以选择每个班级最小项目组合,然后进行简单分组:

;with cte as(select *, (select isnull(min(itemid), t1.itemid) 
                         from t t2 
                         where t2.class = t1.class and t2.elected = 1) as parentid
              from t t1)
select p.itemid, 
       p.item,
       p.class, 
       p.qty, 
       sum(c.price) as price,
       sum(c.price) / p.qty as unitprice
from cte c
join cte p on c.parentid = p.itemid
group by p.itemid, p.item, p.class, p.qty

在此处http://sqlfiddle.com/#!3/7aac9/6