【问题标题】:Selecting max() of multiple columns选择多列的 max()
【发布时间】:2011-12-14 17:06:23
【问题描述】:

好的,这是我的桌子:

product_id  version_id  update_id  patch_id
  1           1           0          0
  1           1           1          0
  1           1           1          1
  1           1           2          0
  1           1           2          1
  2           1           0          0
  2           2           0          0
  2           3           0          0
  2           3           0          1
  3           1           0          0
  3           1           0          1

现在我想选择一个产品的最新版本,所以是update_id和patch_id最高的版本。

例如最新版本

  • 产品 1 应返回 1、2、1
  • 产品 2 应该返回 3, 0, 1
  • 产品 3 应返回 1、0、1

我尝试了各种使用 GROUP BY 和 HAVING 的东西,尝试了子查询,但我仍然无法找到实现此目的的方法。

谁能帮我找到正确的查询,或者我应该考虑为此编写一个 php 函数吗?

编辑

一些附加信息: - 列在一起是主键(有更多列,但对于这个问题,它们无关紧要) - 没有一列是自动递增的

这是桌子:

CREATE  TABLE IF NOT EXISTS `db`.`patch` (
`product_id` INT NOT NULL ,
`version_id` INT NOT NULL ,
`update_id` INT NOT NULL ,
`patch_id` INT NOT NULL
PRIMARY KEY (`product_id`, `version_id`, `update_id`, `patch_id`) ,
INDEX `fk_patch_update1` (`product_id` ASC, `version_id` ASC, `update_id` ASC) )

编辑 2

标记为重复,但不是:另一个问题查找高于三个不同列中任何一个值的记录。

在这个问题中,我们查找按 product_id 分组的最高版本号。

编辑 3

rgz 的回答再次告诉我这是重复的。首先:这个问题比较老。其次,我不认为答案是一样的。

rgz 建议使用以下查询:

SELECT product_id, GREATEST(version_id, update_id, patch_id) AS latest_version FROM patch.

GREATEST(1,2,3) 返回 3,对吗?如果我们有这些值:

  product_id  version_id  update_id  patch_id
  1           1           0          0
  1           1           2          8
  1           3           0          0

据我了解,此查询将返回:

  product_id  latest_version
  1                 1
  1                 8
  1                 3

但它应该返回:

  product_id  version_id update_id  patch_id
  1           3          0          0

我不认为 GREATEST 可以提供帮助。如果你认为会,请证明我错了。

【问题讨论】:

  • 我知道这是一个老问题,但你也可以这样做:SELECT * FROM db.patch ORDER BY UPDATE_ID DESC, PATCH_ID DESC LIMIT 1;
  • @rgz 这不是重复的。查看我的编辑。
  • 澄清一下,返回行的条件是每个product_id的最高version_id的最高update_id的最高patch_id吗?
  • 是的,就是这个条件。

标签: mysql sql


【解决方案1】:

这是唯一标识符派上用场的一个例子。

假设您有一个自动递增的 ID 字段,然后您可以使用相关的子查询为每个产品找到所需的 ID...

SELECT
  *
FROM
  yourTable
WHERE
  id = (
        SELECT   id
        FROM     yourTable AS lookup
        WHERE    lookup.product_id = yourTable.product_id
        ORDER BY version_id DESC, update_id DESC, patch_id DESC
        LIMIT    1
       )


等价的没有唯一标识符需要多个相关的子查询...

SELECT
  *
FROM
  yourTable
WHERE
     version_id = (
                   SELECT   MAX(version_id)
                   FROM     yourTable AS lookup
                   WHERE    lookup.product_id = yourTable.product_id
                  )
  AND update_id = (
                   SELECT   MAX(update_id)
                   FROM     yourTable AS lookup
                   WHERE    lookup.product_id = yourTable.product_id
                     AND    lookup.version_id = yourTable.version_id
                  )
  AND patch_id  = (
                   SELECT   MAX(patch_id)
                   FROM     yourTable AS lookup
                   WHERE    lookup.product_id = yourTable.product_id
                     AND    lookup.version_id = yourTable.version_id
                     AND    lookup.update_id  = yourTable.update_id
                  )

这将比在具有唯一标识符列的表上慢得多。


另一种选择(没有唯一标识符)是在不同的聚合级别上自联接。

SELECT
  yourTable.*
FROM
  (SELECT product_id, MAX(version_id) AS max_version_id FROM yourTable GROUP BY product_id) AS version
INNER JOIN
  (SELECT product_id, version_id, MAX(update_id) AS max_update_id FROM yourTable GROUP BY product_id, version_id) AS update
    ON  update.product_id = version.product_id
    AND update.version_id = version.max_version_id
INNER JOIN
  (SELECT product_id, version_id, updatE_id, MAX(patch_id) AS max_patch_id FROM yourTable GROUP BY product_id, version_id) AS patch
    ON  patch.product_id = update.product_id
    AND patch.version_id = update.version_id
    AND patch.update_id  = update.max_update_id
INNER JOIN
  yourTable
    ON  yourTable.product_id = patch.product_id
    AND yourTable.version_id = patch.version_id
    AND yourTable.update_id  = patch.update_id
    AND yourTable.patch_id   = patch.max_patch_id

【讨论】:

  • patch_id btw 之前缺少一个逗号(它的变化不够大,所以我可以编辑)
  • 如果这一行在表 (1,1,1,3) 中会发生什么?
  • @fred - 使用自动递增 ID 字段,每一行都有一个唯一标识符。然后,子查询查找关联 product_id 的所有记录。然后,只选择第一个(最高版本、更新、补丁)。然后外部查询获取具有该唯一 ID 的记录。简而言之,不管你有什么价值观,它仍然有效。
  • 忘了说,没有一列是自动递增的。主键是product_id、version_id、update_id 和patch_id 一起。所以,就我而言,这没有帮助,不是吗?
  • @ScubaKay - 正确,您需要将这样的列添加到您的数据中。这是一个唯一标识符(而不是复合键)非常有用的示例。
【解决方案2】:

试试:

Select product_id, version_id, update_id, patch_id
from (select * from MyTable 
      order by product_id, version_id desc, update_id desc, patch_id desc) as v
group by product_id

对不起,我是@RolandoMySQLDBA。恕我冒昧,但我将针对示例数据发布您的查询及其输出:

mysql> Select product_id, version_id, update_id, patch_id
    -> from (select * from prod
    ->       order by product_id, version_id desc, update_id desc, patch_id desc) as v
    -> group by product_id  ;
+------------+------------+-----------+----------+
| product_id | version_id | update_id | patch_id |
+------------+------------+-----------+----------+
|          1 |          1 |         2 |        1 |
|          2 |          3 |         0 |        1 |
|          3 |          1 |         0 |        1 |
+------------+------------+-----------+----------+
3 rows in set (0.00 sec)

您的查询如图所示。它也比我的回答简单。好戏!!!

【讨论】:

  • 这在不同线程可以处理表的不同部分的并行环境中是否有效?和/或您是否有任何引文表明 ORDER 得到维护并与外部查询相关? [整个 MySQL 允许这样做让我有点害怕]
  • 这似乎行得通,@Dems 在新手语言中,您对这种方法有什么问题?
  • @Dems:我以为我已经读过 MySQL 从组中的第一条记录中获取值(如果没有指定覆盖排序顺序,则按索引排序;在组中或之后按子句排序仅分组之后应用),但我找不到任何引用。相比之下,MySQL 参考手册在与“GROUP BY 和 HAVING with Hidden Columns”相关的部分中,明确指出非分组列“结果是不确定的”。
  • @Scuba,Dems 担心这种方法可能不可靠,即使它在测试条件下有效。
  • 我很惊讶。我针对我的问题中的示例数据尝试了您的查询,并且您的查询有效! + 1 !!!
【解决方案3】:

我没有测试它,但我相信你可以做这样的事情

SELECT val.product_id, MAX(val.myVersion)
FROM (SELECT product_id, ((version_id * 10 + update_id) * 10 + patch_id) as myVersion
      FROM myTable) as val
GROUP by product_id;

对不起,我是@RolandoMySQLDBA。恕我冒昧,但我将您的查询及其输出针对示例数据发布:

mysql> SELECT val.product_id, MAX(val.myVersion)
    -> FROM (SELECT product_id, ((version_id * 10 + update_id) * 10 + patch_id) as myVersion
    ->       FROM prod) as val
    -> GROUP by product_id;
+------------+--------------------+
| product_id | MAX(val.myVersion) |
+------------+--------------------+
|          1 |                121 |
|          2 |                301 |
|          3 |                101 |
+------------+--------------------+
3 rows in set (0.01 sec)

虽然显示方式不同,但您的查询逻辑上适用于原始问题。向我 +1 !!!

【讨论】:

    【解决方案4】:

    您可以按max(update_id * maxpatchidvalue + patch_id) 分组以获得您想要的。

    【讨论】:

    • 我认为更新 2,补丁 0 比更新 1,补丁 1 更新。
    • 假设 max_patch_id 为 100 : (2 * 100 + 0) > (1 * 100 + 1) => 只要不会导致溢出错误,这种机制就可以工作。
    【解决方案5】:

    使用问题中的输入数据和没有键的表,这是一种相当糟糕的方法:

    首先,你需要的查询是这样的:

    select A.* from prod A LEFT JOIN
    (select product_id,max(tagnum) maxtag from
    (select *,(version_id*10000+update_id*100+patch_id) tagnum from prod) AA
    group by product_id) B
    USING (product_id)
    WHERE (version_id*10000+update_id*100+patch_id) = B.maxtag;
    

    这是一些使用所有整数、没有索引和您的示例数据的示例代码:

    DROP TABLE IF EXISTS prod;
    CREATE TABLE prod
    (
    product_id INT,
    version_id INT,
    update_id INT,
    patch_id INT
    ) ENGINE=MyISAM;
    INSERT INTO prod VALUES
    ( 1,  1,  0, 0 ),
    ( 1,  1,  1, 0 ),
    ( 1,  1,  1, 1 ),
    ( 1,  1,  2, 0 ),
    ( 1,  1,  2, 1 ),
    ( 2,  1,  0, 0 ),
    ( 2,  2,  0, 0 ),
    ( 2,  3,  0, 0 ),
    ( 2,  3,  0, 1 ),
    ( 3,  1,  0, 0 ),
    ( 3,  1,  0, 1 );
    select * from prod;
    select *,(version_id*10000+update_id*100+patch_id) tagnum from prod;
    select A.* from prod A LEFT JOIN
    (select product_id,max(tagnum) maxtag from
    (select *,(version_id*10000+update_id*100+patch_id) tagnum from prod) AA
    group by product_id) B
    USING (product_id)
    WHERE (version_id*10000+update_id*100+patch_id) = B.maxtag;
    

    结果如下:

    mysql> DROP TABLE IF EXISTS prod;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> CREATE TABLE prod
        -> (
        -> product_id INT,
        -> version_id INT,
        -> update_id INT,
        -> patch_id INT
        -> ) ENGINE=MyISAM;
    Query OK, 0 rows affected (0.04 sec)
    
    mysql> INSERT INTO prod VALUES
        -> ( 1,  1,  0, 0 ),
        -> ( 1,  1,  1, 0 ),
        -> ( 1,  1,  1, 1 ),
        -> ( 1,  1,  2, 0 ),
        -> ( 1,  1,  2, 1 ),
        -> ( 2,  1,  0, 0 ),
        -> ( 2,  2,  0, 0 ),
        -> ( 2,  3,  0, 0 ),
        -> ( 2,  3,  0, 1 ),
        -> ( 3,  1,  0, 0 ),
        -> ( 3,  1,  0, 1 );
    Query OK, 11 rows affected (0.00 sec)
    Records: 11  Duplicates: 0  Warnings: 0
    
    mysql> select * from prod;
    +------------+------------+-----------+----------+
    | product_id | version_id | update_id | patch_id |
    +------------+------------+-----------+----------+
    |          1 |          1 |         0 |        0 |
    |          1 |          1 |         1 |        0 |
    |          1 |          1 |         1 |        1 |
    |          1 |          1 |         2 |        0 |
    |          1 |          1 |         2 |        1 |
    |          2 |          1 |         0 |        0 |
    |          2 |          2 |         0 |        0 |
    |          2 |          3 |         0 |        0 |
    |          2 |          3 |         0 |        1 |
    |          3 |          1 |         0 |        0 |
    |          3 |          1 |         0 |        1 |
    +------------+------------+-----------+----------+
    11 rows in set (0.00 sec)
    
    mysql> select *,(version_id*10000+update_id*100+patch_id) tagnum from prod;
    +------------+------------+-----------+----------+--------+
    | product_id | version_id | update_id | patch_id | tagnum |
    +------------+------------+-----------+----------+--------+
    |          1 |          1 |         0 |        0 |  10000 |
    |          1 |          1 |         1 |        0 |  10100 |
    |          1 |          1 |         1 |        1 |  10101 |
    |          1 |          1 |         2 |        0 |  10200 |
    |          1 |          1 |         2 |        1 |  10201 |
    |          2 |          1 |         0 |        0 |  10000 |
    |          2 |          2 |         0 |        0 |  20000 |
    |          2 |          3 |         0 |        0 |  30000 |
    |          2 |          3 |         0 |        1 |  30001 |
    |          3 |          1 |         0 |        0 |  10000 |
    |          3 |          1 |         0 |        1 |  10001 |
    +------------+------------+-----------+----------+--------+
    11 rows in set (0.01 sec)
    
    mysql> select A.* from prod A LEFT JOIN
        -> (select product_id,max(tagnum) maxtag from
        -> (select *,(version_id*10000+update_id*100+patch_id) tagnum from prod) AA
        -> group by product_id) B
        -> USING (product_id)
        -> WHERE (version_id*10000+update_id*100+patch_id) = B.maxtag;
    +------------+------------+-----------+----------+
    | product_id | version_id | update_id | patch_id |
    +------------+------------+-----------+----------+
    |          1 |          1 |         2 |        1 |
    |          2 |          3 |         0 |        1 |
    |          3 |          1 |         0 |        1 |
    +------------+------------+-----------+----------+
    3 rows in set (0.00 sec)
    
    mysql>
    

    试试看!!!

    警告

    我的回答将支持每个产品

    • 多达 99 个版本
    • 最多 99 个更新
    • 最多 99 个补丁

    【讨论】:

    • 公式不同。我使用version_id*10000+update_id*100+patch_id。 @fge 使用 update_id * maxpatchidvalue + patch_id
    • 我的答案会根据问题中提供的输入数据产生所需的输出。
    【解决方案6】:

    试试这个。

    SELECT
      version_id,
      max_update.update_id,
      max_patch.patch_id
    FROM
      TABLE
        OUTER APPLY
        (
          SELECT TOP 1
            update_id
          FROM
            TABLE t1
          WHERE
            TABLE.version_id = t1.version_id
            AND TABLE.product_id = t1.product_id
          ORDER BY update_id DESC
        ) max_update
        OUTER APPLY
        (
          SELECT TOP 1
            patch_id 
          FROM
            TABLE t1
          WHERE
            TABLE.version_id = t1.version_id
            AND TABLE.product_id = t1.product_id
            AND max_update.update_id = t1.update_id
          ORDER BY patch_id DESC
        ) max_patch
    

    【讨论】:

    • 您还需要加入 product_id。使用 MAX() 时不需要 ORDER BY。那么,如果你有(2,0)和(1,1)呢?你的机制给出 (2,1) 作为结果,即使它不存在。
    【解决方案7】:

    MySQL 有 GREATEST 函数,所以你应该像这样使用它:

    SELECT product_id, GREATEST(version_id, update_id, patch_id) AS latest_version FROM patch.
    

    我将此问题标记为重复问题,因为这两个问题基本上都在问同一个问题,选择了几列的最大值。在这里,您在 SELECT 子句中使用它,在另一个问题中,您在 WHERE 子句中使用它。无论哪种情况,GREATEST 都能解决问题。

    【讨论】:

    • 顺便说一下,您将此问题标记为与小一岁的问题重复。
    • GREATEST(1,2,3) 返回 3,对吗?如果我们有这些值:product_id version_id update_id patch_id 1 1 0 0 1 1 2 8 1 3 0 0 据我了解,您的查询将返回:product_id latest_version 1 1 1 8 1 3 但它应该返回:product_id version_id update_id patch_id 1 3 0 0
    • > 顺便说一句,您将此问题标记为与小一岁的问题的重复
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-06
    • 1970-01-01
    • 1970-01-01
    • 2021-07-04
    • 2015-07-04
    • 2013-09-11
    • 1970-01-01
    相关资源
    最近更新 更多