【问题标题】:How to select single row based on the max value in multiple rows [duplicate]如何根据多行中的最大值选择单行[重复]
【发布时间】:2012-02-02 03:36:08
【问题描述】:

可能重复:
SQL: Find the max record per group

我有一个这样的四列表格:

name   major    minor  revision
p1     0        4      3
p1     1        0      0
p1     1        1      4
p2     1        1      1
p2     2        5      0
p3     3        4      4

这基本上是一个包含每个版本的程序记录的 ca 表。我想进行选择以获取所有程序及其最新版本,因此结果如下所示:

name   major    minor  revision
p1     1        1      4
p2     2        5      0
p3     3        4      4

我不能只按名称分组并获得每列的最大值,因为这样我最终会得到每列中的最高数字,而不是具有最高版本的特定行。我该如何设置?

【问题讨论】:

  • 您要在哪一列基础上归档数据?? majorminorrevision
  • 这是同一个“greatest-n-per-group”问题,在 SE 上已被多次询问(stackoverflow.com/questions/2657482/… 只是其中的一个示例)
  • @LordScree:不要这么认为,这只是一个正常的最大值,但会超过多列。 Greatest-n-per-group 是当您在 col1 上分组时,想要找到 col3 最高的 col2 的值。
  • 我不知道为什么它被关闭为完全重复。它与建议的副本完全不同。该问题需要基于一列最大值的行。我要求从三列中确定最大值的记录。我想没关系。我在这里得到了一些很好的答案。谢谢。

标签: mysql sql greatest-n-per-group


【解决方案1】:

Update3 变量 group_concat_max_len 的最小值 = 4,所以我们不能使用它。但 你可以:

select 
  name, 
  SUBSTRING_INDEX(group_concat(major order by major desc),',', 1) as major, 
  SUBSTRING_INDEX(group_concat(minor order by major desc, minor desc),',', 1)as minor, 
  SUBSTRING_INDEX(group_concat(revision order by major desc, minor desc, revision desc),',', 1) as revision
from your_table
group by name;

这是经过here 测试的,没有,以前的版本没有提供错误的结果,它只有连接值的数量问题。

【讨论】:

  • 这将返回不存在的版本,例如p1 1 4 4
  • @Andomar 你是对的,我尝试修改我的查询
  • @Andomar 你喜欢这个新版本吗?
  • 新版本还是一样的问题--you can test it here,如果你喜欢
  • 我已经测试了我的代码,并且通过一个小的更新(子字符串技巧),它是好的
【解决方案2】:

只有我一个人认为最好的版本是最高版本的吗?

所以,

select a.name, a.major, a.minor, a.revision
from table a
where a.revision = (select max(b.revision) from table b where b.name = a.name)

【讨论】:

  • 您可能不是唯一一个,但同样不是每个人都在版本控制方面使用相同的约定。在我的情况下(我猜 OP 也是一样)我需要所有四个字段的 MAX,而不仅仅是一个。
  • 另外,这是否需要对每条记录进行选择查询...这非常低效?
【解决方案3】:

我尝试解决 SQL 问题的方法是循序渐进。

  • 您希望每个产品的最大主要版本对应的最大次要版本的最大修订版本。

每个产品的最大主编号由下式给出:

SELECT Name, MAX(major) AS Major FROM CA GROUP BY Name;

因此,每个产品的最大主编号对应的最大次编号由下式给出:

SELECT CA.Name, CA.Major, MAX(CA.Minor) AS Minor
  FROM CA
  JOIN (SELECT Name, MAX(Major) AS Major
          FROM CA
         GROUP BY Name
       ) AS CB
    ON CA.Name = CB.Name AND CA.Major = CB.Major
 GROUP BY CA.Name, CA.Major;

因此,最大修订版(对于每个产品的最大主版本号对应的最大次版本号)由下式给出:

SELECT CA.Name, CA.Major, CA.Minor, MAX(CA.Revision) AS Revision
  FROM CA
  JOIN (SELECT CA.Name, CA.Major, MAX(CA.Minor) AS Minor
          FROM CA
          JOIN (SELECT Name, MAX(Major) AS Major
                  FROM CA
                 GROUP BY Name
               ) AS CB
            ON CA.Name = CB.Name AND CA.Major = CB.Major
         GROUP BY CA.Name, CA.Major
       ) AS CC
    ON CA.Name = CC.Name AND CA.Major = CC.Major AND CA.Minor = CC.Minor
 GROUP BY CA.Name, CA.Major, CA.Minor;

经过测试 - 它可以工作并产生与 Andomarquery 相同的答案。


性能

我创建了更大的数据量(11616 行数据),并针对我的查询运行了 Andomar 的基准时间 - 目标 DBMS 是在 MacOS X 10.7.2 上运行的 IBM Informix Dynamic Server (IDS) 版本 11.70.FC2。我使用了 Andomar 的两个查询中的第一个,因为 IDS 不支持第二个查询中的比较符号。我加载了数据,更新了统计数据,并运行了我的查询,然后是 Andomar,然后是 Andomar,然后是我的。我还记录了 IDS 优化器报告的基本成本。两个查询的结果数据相同(因此查询都准确 - 或同样不准确)。

表未索引:

Andomar's query                           Jonathan's query
Time: 22.074129                           Time: 0.085803
Estimated Cost: 2468070                   Estimated Cost: 22673
Estimated # of Rows Returned: 5808        Estimated # of Rows Returned: 132
Temporary Files Required For: Order By    Temporary Files Required For: Group By

具有唯一索引的表(名称、主要、次要、修订):

Andomar's query                           Jonathan's query
Time: 0.768309                            Time: 0.060380
Estimated Cost: 31754                     Estimated Cost: 2329
Estimated # of Rows Returned: 5808        Estimated # of Rows Returned: 139
                                          Temporary Files Required For: Group By

如您所见,索引显着提高了 Andomar 查询的性能,但在这个系统上它似乎仍然比我的查询更昂贵。该索引为我的查询节省了 25% 的时间。我很想看到两个版本的 Andomar 查询的可比较数据,可比较的数据量,有和没有索引。 (如果需要我的测试数据可以提供;一共有132个产品——问题中列出的3个和新的129个;每个新产品有(相同的)90个版本条目。)

产生差异的原因是Andomar的查询中的子查询是一个相关的子查询,这是一个相对昂贵的过程(在缺少索引的情况下更是如此)。

【讨论】:

  • 在 IDS 中是否有与我的第一个查询相当的内容?
  • @ypercube:不容易,不。 IDS 不支持您要加入的那种“隐式”行 - 既不等于您的查询中的相等,也不低于 Andemar 的第二个查询中的相等。有模糊等效(但非标准)的符号;我必须弄清楚如何让它们发挥作用(我怀疑它会比 IDS 不支持的标准表示法更冗长)。 OTOH,我相信我的查询应该毫无问题地转换为 MySQL。
  • 是的,我测试了你的并且工作正常。如果表有一个人工主键并且连接被重写为:ON cam.Pk = ( SELECT FIRST 1 Pk FROM ... )
【解决方案4】:
SELECT cam.*
FROM 
      ( SELECT DISTINCT name
        FROM ca 
      ) AS cadistinct
  JOIN 
      ca AS cam
    ON ( cam.name, cam.major, cam.minor, cam.revision )
     = ( SELECT name, major, minor, revision
         FROM ca
         WHERE name = cadistinct.name
         ORDER BY major DESC
                , minor DESC
                , revision DESC
         LIMIT 1
       )

这适用于 MySQL(当前版本),但我不推荐它:

SELECT *
FROM 
    ( SELECT name, major, minor, revision
      FROM ca
      ORDER BY name
             , major DESC
             , minor DESC
             , revision DESC
    ) AS tmp
GROUP BY name

【讨论】:

  • +1 不错。这与我的第二个查询中的想法相同:)
  • +1 不确定它是否可以工作... MySQL 是否允许在子查询中使用 limit 1
  • 我不明白你的第二个问题。它会从子查询中获得主要、次要、修订的第一行??? MySQL 很奇怪
  • @Florin:第二个查询之所以有效,是因为 MySQL 引擎将首先对子查询中的行进行排序,然后在外部 GROUP BY 中使用该顺序,获取它找到的第一行。这不是 ANSI SQL。
  • @Andomar:是的。允许LIMIT,但不允许在IN/ALL/ANY/SOME 子查询中
【解决方案5】:

您可以使用not exists 子查询来过滤掉较旧的记录:

select  *
from    YourTable yt
where   not exists
        (
        select  *
        from    YourTable older
        where   yt.name = older.name and 
                (
                    yt.major < older.major or
                    yt.major = older.major and yt.minor < older.minor or
                    yt.major = older.major and yt.minor = older.minor and
                        yt.revision < older.revision
                )
        )

在 MySQL 中也可以写成:

select  *
from    YourTable yt
where   not exists
        (
        select  *
        from    YourTable older
        where   yt.name = older.name and 
                  (yt.major,    yt.minor,    yt.revision) 
                < (older.major, older.major, older.revision)
        )

【讨论】:

  • and 的优先级通常高于or。如果 MySQL 是这种情况,嵌套的selectwhere 中第一个and 之后的所有内容可能应该用括号括起来。
  • +1 很好的查询 = 易于理解
  • @AndriyM:你是对的,编辑了答案
  • @Andomar:希望你不介意添加。
【解决方案6】:

版本号的每个部分最多允许三位数字。如果您想使用更多数字,请在主要乘法中添加两个零,在每个数字中添加一个零到次要乘法(我希望它很清楚)。

select  t.* 
from yourTable t
join (
    select name, max(major * 1000000 + minor * 1000  + revision) as ver
    from yourTable 
    group by name
) t1 on t1.ver = (t.major * 1000000 + t.minor * 1000  + t.revision)

结果:

name    major   minor   revision
p1      1       1       4
p2      2       5       0
p3      3       4       4

【讨论】:

    【解决方案7】:

    如果这些列中有数字,您可以想出某种公式,该公式对于主要、次要、修订值将是唯一且有序的。例如。如果数字小于 10,您可以将它们附加为字符串,然后比较它们,例如:

    select name, major, minor, revision, 
           concat(major, minor, revision) as version
    from versions
    

    如果它们是不大于 100 的数字,您可以执行以下操作:

    select name, major, minor, revision, 
           (major * 10000 + minor * 100 + revision) as version
    from versions
    

    您不仅可以将version 中的max 按名称分组,如下所示:

    select name, major, minor, revision 
    from (
        select name, major, minor, revision, 
               (major * 10000 + minor * 100 + revision) as version
        from versions) v1
    where version = (select max (major * 10000 + minor * 100 + revision) 
                     from versions v2 
                     where v1.name = v2.name)
    

    【讨论】:

    • 这将如何过滤掉旧行?
    • 对不起,这只是部分查询,然后分组/过滤不显示,将编辑
    猜你喜欢
    • 1970-01-01
    • 2021-09-12
    • 2014-01-23
    • 1970-01-01
    • 1970-01-01
    • 2020-09-22
    • 2021-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多