【问题标题】:SQL - how to GROUP by id and identify the column with highest value?SQL - 如何按 id 分组并识别具有最高值的列?
【发布时间】:2013-01-02 02:23:07
【问题描述】:

我有一个 SQL 挑战,需要一些帮助。

下面是一个简化的示例,在我的真实案例中,我在慢速 VIEW 中有大约 500k 行。因此,如果您也有有效的解决方案,我将不胜感激。我想我必须以某种方式使用 GROUP BY,但我不确定。

假设我有一张这样的桌子

╔═════════╦══════════╦══════════╦═══════╗
║ ORDERID ║   NAME   ║   TYPE   ║ PRICE ║
╠═════════╬══════════╬══════════╬═══════╣
║       1 ║ Broccoli ║ Food     ║ 1     ║
║       1 ║ Beer     ║ Beverage ║ 5     ║
║       1 ║ Coke     ║ Beverage ║ 2     ║
║       2 ║ Beef     ║ Food     ║ 2.5   ║
║       2 ║ Juice    ║ Beverage ║ 1.5   ║
║       3 ║ Beer     ║ Beverage ║ 5     ║
║       4 ║ Tomato   ║ Food     ║ 1     ║
║       4 ║ Apple    ║ Food     ║ 1     ║
║       4 ║ Broccoli ║ Food     ║ 1     ║
╚═════════╩══════════╩══════════╩═══════╝

所以我想做的是:

在每个订单中,如果有餐饮订单行,我想要最高的饮料价格

所以在这个例子中我想要一个这样的结果集:

╔═════════╦═══════╦═══════╗
║ ORDERID ║ NAME  ║ PRICE ║
╠═════════╬═══════╬═══════╣
║       1 ║ Beer  ║ 5     ║
║       2 ║ Juice ║ 1.5   ║
╚═════════╩═══════╩═══════╝

我怎样才能以有效的方式实现这一目标?

【问题讨论】:

  • 。 .因为你说view很贵,我觉得你应该选择Bogdan的方案。我以前从未发表过这样的评论,但您确实强调了视图的缓慢性,并且该解决方案是唯一只扫描一次视图的解决方案。
  • 是的,这也是一个很好或更好的答案。在我通过 Bogdan 之前,我已经选择并实施了这个。然而,我的最终标准是解决这个问题,所以我选择了第一个也是最好的答案。但我确实理解你的意见。

标签: sql sql-server group-by


【解决方案1】:

既然您已经标记了SQL Server,请使用Common Table ExpressionWindow Functions

;WITH filteredList
AS
(
  SELECT OrderID
  FROM tableName
  WHERE Type IN ('Food','Beverage')
  GROUP BY OrderID
  HAVING COUNT(DISTINCT Type) = 2
),
greatestList
AS
(
    SELECT  a.OrderID, a.Name, a.Type, a.Price,
            DENSE_RANK() OVER (PARTITION BY a.OrderID
                                ORDER BY a.Price DESC) rn
    FROM tableName  a
          INNER JOIN filteredList b
              ON a.OrderID = b.OrderID
    WHERE a.Type = 'Beverage'
)
SELECT  OrderID, Name, Type, Price
FROM    greatestList
WHERE   rn = 1

【讨论】:

  • 所以这个会返回记录 OrderID = 3 :)
【解决方案2】:

如果您使用的是 Sql-Server 2005 或更高版本,您可以使用 CTEDENSE_RANK 函数:

WITH CTE 
     AS (SELECT orderid, 
                name, 
                type, 
                price, 
                RN = Dense_rank() 
                       OVER ( 
                         PARTITION BY orderid 
                         ORDER BY CASE WHEN type='Beverage' THEN 0 ELSE 1 END ASC 
                         , price DESC) 
         FROM   dbo.tablename t 
         WHERE  EXISTS(SELECT 1 
                       FROM   dbo.tablename t2 
                       WHERE  t2.orderid = t.orderid 
                              AND type = 'Food') 
         AND    EXISTS(SELECT 1 
                       FROM   dbo.tablename t2 
                       WHERE  t2.orderid = t.orderid 
                              AND type = 'Beverage')) 
SELECT orderid, 
       name, 
       price 
FROM   CTE
WHERE  rn = 1 

如果您想要所有订单的最高价格相同,请使用DENSE_RANK,如果您想要一个,请使用ROW_NUMBER

DEMO

【讨论】:

    【解决方案3】:

    您可以使用子查询为每个包含食品和饮料的订单获取max(price),然后将其连接回您的表以获取结果:

    select t1.orderid,
      t1.name,
      t1.price
    from yourtable t1
    inner join
    (
      select max(price) MaxPrice, orderid
      from yourtable t
      where type = 'Beverage'
        and exists (select orderid
                    from yourtable o
                    where type in ('Food', 'Beverage')
                      and t.orderid = o.orderid
                    group by orderid
                    having count(distinct type) = 2)
      group by orderid
    ) t2
      on t1.orderid = t2.orderid
      and t1.price = t2.MaxPrice
    

    SQL Fiddle with Demo

    结果是:

    | ORDERID |  NAME | PRICE |
    ---------------------------
    |       1 |  Beer |     5 |
    |       2 | Juice |   1.5 |
    

    【讨论】:

      【解决方案4】:

      这是关系划分:link 1link 2

      如果除数表(只有 foodbeverage)是静态的,那么您可以使用以下解决方案之一:

      DECLARE @OrderDetail TABLE 
          ([OrderID] int, [Name] varchar(8), [Type] varchar(8), [Price] decimal(10,2))
      ;
      
      INSERT INTO @OrderDetail
          ([OrderID], [Name], [Type], [Price])
      SELECT 1, 'Broccoli', 'Food', 1.0
      UNION ALL SELECT 1, 'Beer', 'Beverage', 5.0
      UNION ALL SELECT 1, 'Coke', 'Beverage', 2.0
      UNION ALL SELECT 2, 'Beef', 'Food', 2.5
      UNION ALL SELECT 2, 'Juice', 'Beverage', 1.5
      UNION ALL SELECT 3, 'Beer', 'Beverage', 5.0
      UNION ALL SELECT 4, 'Tomato', 'Food', 1.0
      UNION ALL SELECT 4, 'Apple', 'Food', 1.0
      UNION ALL SELECT 4, 'Broccoli', 'Food', 1.0
      
      -- Solution 1
      SELECT  od.OrderID, 
              COUNT(DISTINCT od.Type) AS DistinctTypeCount, 
              MAX(CASE WHEN od.Type='beverage' THEn od.Price END) AS MaxBeveragePrice
      FROM    @OrderDetail od
      WHERE   od.Type IN ('food', 'beverage')
      GROUP BY od.OrderID
      HAVING  COUNT(DISTINCT od.Type) = 2 -- 'food' & 'beverage'
      
      -- Solution 2: better performance
      SELECT  pvt.OrderID,
              pvt.food AS MaxFoodPrice,
              pvt.beverage AS MaxBeveragePrice
      FROM (
          SELECT  od.OrderID, od.Type, od.Price
          FROM    @OrderDetail od
          WHERE   od.Type IN ('food', 'beverage')
      ) src
      PIVOT ( MAX(src.Price) FOR src.Type IN ([food], [beverage]) ) pvt
      WHERE   pvt.food IS NOT NULL
      AND     pvt.beverage IS NOT NULL
      

      结果(针对解决方案 1 和 2):

      OrderID     DistinctTypeCount MaxBeveragePrice
      ----------- ----------------- ---------------------------------------
      1           2                 5.00
      2           2                 1.50
      
      Table 'Worktable'. Scan count 2, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table '#09DE7BCC'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      
      OrderID     MaxFoodPrice                            MaxBeveragePrice
      ----------- --------------------------------------- ---------------------------------------
      1           1.00                                    5.00
      2           2.50                                    1.50
      
      Table '#09DE7BCC'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      

      【讨论】:

      • +1 。 . .Yours 是唯一只扫描原始数据一次的解决方案。鉴于问题说来源是一个缓慢的观点,我不知道为什么不选择这作为接受的答案。
      • @GordonLinoff:第二种解决方案(PIVOT,1 次扫描)由Razvan Socol(前 SQL Server MVP)提出。我将此解决方案用于生产存储过程。
      • Razvan 是否提供了这个问题的答案?第二种解决方案的一个变体是我将如何回答这个问题。我在这里看到的所有其他答案都会导致视图的多次扫描。
      • 没有。两年前,我将此解决方案用于生产存储过程。当时,他为关系划分(relational division with a static divisor)提出了这个解决方案(PIVOT)。
      猜你喜欢
      • 1970-01-01
      • 2020-07-11
      • 2016-10-09
      • 1970-01-01
      • 1970-01-01
      • 2020-01-15
      • 1970-01-01
      • 2021-11-18
      • 2020-09-29
      相关资源
      最近更新 更多