sql只选择列上具有最大值的行

8nuwlpux  于 2021-06-19  发布在  Mysql
关注(0)|答案(14)|浏览(318)

**想改进这篇文章吗?**提供这个问题的详细答案,包括引文和解释为什么你的答案是正确的。没有足够细节的答案可能会被编辑或删除。
这个问题在这里已经有答案了

检索每个组中的最后一条记录-mysql(29个答案)
去年关门了。
我有这个文件表(这里是简化版):

+------+-------+--------------------------------------+
| id   | rev   | content                              |
+------+-------+--------------------------------------+
| 1    | 1     | ...                                  |
| 2    | 1     | ...                                  |
| 1    | 2     | ...                                  |
| 1    | 3     | ...                                  |
+------+-------+--------------------------------------+

如何为每个id选择一行,并且只选择最大的rev?
对于上述数据,结果应包含两行: [1, 3, ...] 以及 [2, 1, ..] . 我正在使用mysql。
目前我使用支票 while 循环从结果集中检测和重写旧的rev。但是,这是实现这一结果的唯一方法吗?没有sql解决方案吗?
更新
正如答案所示,这里有一个sql解决方案,这里有一个sqlfiddle演示。
更新2
我注意到在添加上述SQLFIDLE之后,问题的得票率已经超过了答案的得票率。这不是我的本意!小提琴以答案为基础,尤其是公认的答案。

ukxgm1gy

ukxgm1gy1#

我倾向于使用尽可能少的代码。。。
你可以用 IN 试试这个:

SELECT * 
FROM t1 WHERE (id,rev) IN 
( SELECT id, MAX(rev)
  FROM t1
  GROUP BY id
)

在我看来没那么复杂。。。更易于阅读和维护。

yeotifhr

yeotifhr2#

我感到震惊的是,没有答案提供sql窗口函数解决方案:

SELECT a.id, a.rev, a.contents
  FROM (SELECT id, rev, contents,
               ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
          FROM YourTable) a
 WHERE a.rank = 1

添加在sql标准ansi/iso标准中sql:2003 and 后来扩展为ansi/iso标准sql:2008,窗口(或窗口)功能可与所有主要供应商现在。有更多类型的秩函数可用于处理平局问题: RANK, DENSE_RANK, PERSENT_RANK .

vyu0f0g1

vyu0f0g13#

不是mysql,而是对于其他发现这个问题并使用sql的人来说,另一种解决最大的n-per-group问题的方法是使用 Cross Apply 在ms sql中

WITH DocIds AS (SELECT DISTINCT id FROM docs)

SELECT d2.id, d2.rev, d2.content
FROM DocIds d1
CROSS APPLY (
  SELECT Top 1 * FROM docs d
  WHERE d.id = d1.id
  ORDER BY rev DESC
) d2

下面是sqlfiddle中的一个示例

rm5edbpk

rm5edbpk4#

我想,你想要这个?

select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)

sql fiddle:检查这里

kdfy810k

kdfy810k5#

因为这是关于这个问题最流行的问题,所以我也会在这里重新发布另一个答案:
看起来有更简单的方法可以做到这一点(但仅限于mysql):

select *
from (select * from mytable order by id, rev desc ) x
group by id

请回答信用卡问题

pw136qt2

pw136qt26#

另一种解决方案是使用相关子查询:

select yt.id, yt.rev, yt.contents
    from YourTable yt
    where rev = 
        (select max(rev) from YourTable st where yt.id=st.id)

在(id,rev)上有一个索引会将子查询呈现为一个简单的查找。。。
以下是与@adriancarneiro的答案(subquery,leftjoin)中的解决方案的比较,这些解决方案是基于mysql对innodb表的测量,innodb表有约100万条记录,组大小为:1-3。
而对于全表扫描,当涉及到直接查找或批处理时,子查询/leftjoin/correlated计时相互关联为6/8/9( id in (1,2,3) ),子查询比其他查询慢得多(因为重新运行子查询)。但是,我无法区分leftjoin和相关解决方案的速度。
最后要注意的是,由于leftjoin在组中创建n*(n+1)/2个连接,因此其性能会受到组大小的严重影响。。。

camsedfj

camsedfj7#

像这样的?

SELECT yourtable.id, rev, content
FROM yourtable
INNER JOIN (
    SELECT id, max(rev) as maxrev
    FROM yourtable
    GROUP BY id
) AS child ON (yourtable.id = child.id) AND (yourtable.rev = maxrev)
h22fl7wq

h22fl7wq8#

我喜欢使用 NOT EXIST -基于此问题的解决方案:

SELECT 
  id, 
  rev
  -- you can select other columns here
FROM YourTable t
WHERE NOT EXISTS (
   SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)

这将选择组中具有最大值的所有记录,并允许您选择其他列。

3lxsmp7m

3lxsmp7m9#

我不能保证它的性能,但这里有一个技巧是受microsoftexcel的局限性启发的。它有一些很好的特点
好东西
它应该强制只返回一个“最大记录”,即使有平局(有时有用)
它不需要连接
方法
这有点难看,需要了解rev列的有效值范围。假设rev列是一个介于0.00和999之间的数字,包括小数点,但小数点右边只有两位数(例如,34.17将是一个有效值)。
要点是,通过字符串连接/打包主比较字段以及所需的数据来创建一个合成列。通过这种方式,可以强制sql的max()聚合函数返回所有数据(因为它已打包到单个列中)。然后你得把数据解包。
下面是上面用sql编写的示例的外观

SELECT id, 
       CAST(SUBSTRING(max(packed_col) FROM 2 FOR 6) AS float) as max_rev,
       SUBSTRING(max(packed_col) FROM 11) AS content_for_max_rev 
FROM  (SELECT id, 
       CAST(1000 + rev + .001 as CHAR) || '---' || CAST(content AS char) AS packed_col
       FROM yourtable
      ) 
GROUP BY id

打包首先强制rev列是一个已知字符长度的数字,而不考虑rev的值,例如
3.2变为1003.201
57变为1057.001
923.88变为1923.881
如果操作正确,两个数字的字符串比较应该产生与两个数字的数字比较相同的“max”,并且使用substring函数很容易将其转换回原始数字(几乎任何地方都可以使用一种或另一种形式)。

vuktfyat

vuktfyat10#

SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
mm9b1k5b

mm9b1k5b11#

最干净的解决方案
小提琴
mysql的更新版本随附 ONLY_FULL_GROUP_BY 在默认情况下启用,并且这里的许多解决方案在使用此条件进行测试时将失败。
即便如此,我们也可以简单地选择 DISTINCT 某个独一无二的领域, MAX( 还有哪些领域需要选择 ) , ( *某地 ) ,并且不必担心了解结果或查询的工作方式:

SELECT DISTINCT t1.id, MAX(t1.rev), MAX(t2.content)
FROM Table1 AS t1
JOIN Table1 AS t2 ON t2.id = t1.id AND t2.rev = (
    SELECT MAX(rev) FROM Table1 t3 WHERE t3.id = t1.id
)
GROUP BY t1.id;
``` `SELECT DISTINCT Table1.id, max(Table1.rev), max(Table2.content)` :返回 `DISTINCT` 萨默菲尔德, `MAX()` 另一个领域,最后一个 `MAX()` 是多余的,因为我知道它只是一行,但它是查询所必需的。 `FROM Employee` :在上搜索的表。 `JOIN Table1 AS Table2 ON Table2.rev = Table1.rev` :在第一个表上加入第二个表,因为我们需要得到max(table1.rev)的注解。 `GROUP BY Table1.id` :强制将每个员工的排名靠前的薪资行作为返回结果。
请注意,由于op的问题中“content”是“…”,因此无法测试它是否有效。所以,我把它改成了“.a”,“.b”,所以,我们现在可以看到结果是正确的:

id max(Table1.rev) max(Table2.content)
1 3 ..d
2 1 ..b

为什么干净? `DISTINCT()` ,  `MAX()` 等等,都很好地利用了mysql索引。这会更快。或者,如果您有索引,并将其与查看所有行的查询进行比较,则速度会快得多。
原液
与 `ONLY_FULL_GROUP_BY` 残废了,我们还可以用 `GROUP BY` ,但我们只在工资上使用它,而不是id:

SELECT *
FROM
(SELECT *
FROM Employee
ORDER BY Salary DESC)
AS employeesub
GROUP BY employeesub.Salary;
``` SELECT * :返回所有字段。 FROM Employee :在上搜索的表。 (SELECT *...) 子查询:返回所有人员,按薪资排序。 GROUP BY employeesub.Salary :强制将每个员工的排名靠前的薪资行作为返回结果。
唯一行解决方案
请注意关系数据库的定义:“表中的每一行都有自己的唯一键。”这意味着,在问题的示例中,id必须是唯一的,在这种情况下,我们可以:

SELECT *
FROM Employee
WHERE Employee.id = 12345
ORDER BY Employee.Salary DESC
LIMIT 1

希望这是一个解决问题的解决方案,可以帮助每个人更好地理解数据库中发生的事情。

wvt8vs2t

wvt8vs2t12#

我很少提到的第三种解决方案是特定于mysql的,如下所示:

SELECT id, MAX(rev) AS rev
 , 0+SUBSTRING_INDEX(GROUP_CONCAT(numeric_content ORDER BY rev DESC), ',', 1) AS numeric_content
FROM t1
GROUP BY id

是的,它看起来很糟糕(转换为字符串和返回等),但根据我的经验,它通常比其他解决方案更快。也许这只是针对我的用例,但我已经在有数百万条记录和许多唯一id的表上使用了它。可能是因为mysql在优化其他解决方案方面做得很差(至少在我提出这个解决方案的5.0年代是这样)。
最重要的一点是,groupconcat有一个它可以建立的字符串的最大长度。您可能希望通过设置 group_concat_max_len 变量。请记住,如果您有大量的行,这将是缩放的限制。
不管怎样,如果内容字段已经是文本,那么上面的方法就不能直接起作用。在这种情况下,可能需要使用不同的分隔符,例如\0。你还会遇到 group_concat_max_len 限制更快。

bkkx9g8r

bkkx9g8r13#

另一种方法是使用 MAX() 过分句中的解析函数

SELECT t.*
  FROM
    (
    SELECT id
          ,rev
          ,contents
          ,MAX(rev) OVER (PARTITION BY id) as max_rev
      FROM YourTable
    ) t
  WHERE t.rev = t.max_rev

其他的 ROW_NUMBER() 在这篇文章中已经提到的过分区解决方案是

SELECT t.*
  FROM
    (
    SELECT id
          ,rev
          ,contents
          ,ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
      FROM YourTable
    ) t
  WHERE t.rank = 1

这两个选择在oracle 10g上运行良好。
max()解决方案的运行速度肯定比 ROW_NUMBER() 解决方案因为 MAX() 复杂性是 O(n)ROW_NUMBER() 复杂性是最低限度的 O(n.log(n)) 哪里 n 表示表中的记录数!

baubqpgj

baubqpgj14#

乍一看。。。

你只需要一个 GROUP BY 带有 MAX 聚合函数:

SELECT id, MAX(rev)
FROM YourTable
GROUP BY id

从来没有这么简单,是吗?

我刚注意到你需要 content 列也是。
在sql中,这是一个非常常见的问题:根据某个组标识符,在列中查找具有某个最大值的行的整个数据。在我的职业生涯中我经常听到这样的话。实际上,这是我在当前工作的技术面试中回答的问题之一。
实际上,stackoverflow社区创建了一个标签来处理这样的问题是很常见的:greatest-n-per-group。
基本上,有两种方法可以解决这个问题:

与简单组标识符连接,组子查询中的最大值

在这种方法中,首先要找到 group-identifier, max-value-in-group (上面已经解决)在子查询中。然后将表连接到子查询,两个查询上的值相等 group-identifier 以及 max-value-in-group :

SELECT a.id, a.rev, a.contents
FROM YourTable a
INNER JOIN (
    SELECT id, MAX(rev) rev
    FROM YourTable
    GROUP BY id
) b ON a.id = b.id AND a.rev = b.rev

用self左连接,调整连接条件和过滤器

在这种方法中,您将表与其自身连接起来。平等的原则 group-identifier . 然后,两个聪明的动作:
第二个连接条件是左侧值小于右侧值
执行步骤1时,实际具有最大值的行将具有 NULL 在右边(这是一个 LEFT JOIN ,记得吗?)。然后,我们过滤连接的结果,只显示右侧所在的行 NULL .
所以你最终会得到:

SELECT a.*
FROM YourTable a
LEFT OUTER JOIN YourTable b
    ON a.id = b.id AND a.rev < b.rev
WHERE b.id IS NULL;

结论

两种方法的结果完全相同。
如果你有两排 max-value-in-group 为了 group-identifier ,两种方法的结果中都将包含这两行。
这两种方法都是sqlansi兼容的,因此,无论其“风格”如何,都可以与您喜爱的rdbms一起工作。
这两种方法对性能也很友好,但是您的里程数可能会有所不同(rdbms、db结构、索引等)。所以当你选择一种方法而不是另一种方法时,基准测试。一定要挑对你最有意义的。

相关问题