【问题标题】:SQL: selecting the second-lowest value (via subquery) with GROUP BY clauseSQL:使用 GROUP BY 子句选择第二低的值(通过子查询)
【发布时间】:2014-02-19 03:05:58
【问题描述】:

给定一个十进制值数据库,按位置分组...

CREATE TABLE ItemPrices
    (`Company` varchar(11), `LocationID` char(5), `ProductLevel` varchar(11), `LowPrice` decimal(5,2))
;

INSERT INTO ItemPrices
    (`Company`, `LocationID`, `LowPrice`)
VALUES
    ('Company 1', 10001, 100.00),
    ('Company 2', 10001, 200.00),
    ('Company 3', 10001, 300.00),
    ('Company 4', 10001, 400.00),
    ('Company 1', 10002, 10.00),
    ('Company 2', 10002, NULL),
    ('Company 3', 10002, 30.00),
    ('Company 4', 10002, 40.00)
;

选择位置的最小值很容易:

SELECT LocationID,min(LowPrice) FROM ItemPrices GROUP BY LocationID;

LOCATIONID  MIN(LOWPRICE)
10001       100
10002       10

但是使用子查询过滤出最小值并返回下一个最小值并不能按预期工作:

SELECT LocationID,min(LowPrice) FROM ItemPrices
WHERE LowPrice >
(SELECT min(LowPrice)
    FROM ItemPrices)
GROUP BY LocationID;

LOCATIONID  MIN(LOWPRICE)
10001       100
10002       30

结果应显示 LocationID 10001 的第二低价格为 200,而不是 100。

似乎子查询在所有情况下都只返回最低的LowPrice (10)。正确的逻辑是找到特定 LocationID (100) 的最低价格,然后为 LocationID (200) 找到更大的值并返回更大的值。

如何让子查询知道它应该只评估价格的子集?

只需将GROUP BY 放在那里 (LowPrice > (SELECT min(LowPrice) FROM ItemPrices GROUP BY LocationID)) 就会告诉我Subquery returns more than 1 row -- 是的,当然,它会为每个LocationID 返回一行。

【问题讨论】:

    标签: mysql sql group-by correlated-subquery


    【解决方案1】:

    在输入这个问题时,我突然想到我需要一个相关的子查询。第一步是为表命名ItemPrices。我将i 用于外部查询,s 用于子查询。

    SELECT i.LocationID,min(LowPrice) FROM ItemPrices i
    WHERE
    LowPrice >
    (SELECT min(LowPrice)
     FROM ItemPrices s
    WHERE i.LocationID = s.LocationID)
    GROUP BY LocationID;
    

    让我们运行它!

    LOCATIONID  MIN(LOWPRICE)
    10001       200
    10002       30
    

    看起来不错!你怎么看?

    SQLFiddle:http://sqlfiddle.com/#!2/341ce/5

    【讨论】:

      【解决方案2】:

      使用 > 的问题是两个或多个位置的最便宜价格捆绑在一起。因此,即使在技术上与第一个价格相同,也可能希望的行为是显示第二个最便宜的,但是这将显示第三个最便宜的。

      另一种解决方案是创建一个临时表并用最便宜的位置填充它,然后加入它们并删除所有匹配的记录。

      #create a temp table to hold the cheapest
      create temporary table t (ID int, MINPRICE int, LOCATIONID int);
      
      #fill it with the very cheapest prices
      insert into t select ID, min(LOWPRICE) as MINPRICE, LOCATIONID from `ItemPrices` group by LOCATIONID;
      
      #select the second cheapest records by EXCLUDING the very cheapest
      select ItemPrices.ID, min(ItemPrices.LOWPRICE) as MINPRICE, ItemPrices.LOCATIONID 
      from ItemPrices
      left join t 
             on t.ID = ItemPrices.ID
      where t.ID is null
      group by LOCATIONID;
      

      这种方法的另一个优点是,通过将结果迭代到临时表中 n-1 次,然后将最后一次迭代写出作为结果,更容易扩展以提取第 N 个最便宜的。

      免责声明:我没有检查示例代码的语法,但理论是合理的。我在一个记录超过 50M 的表上遇到了这个问题,它既快速又准确。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多