281

在这里可以找到很多类似的问题,但我认为没有人能充分回答这个问题。

如果可以的话,我将从当前最流行的问题继续,并使用他们的示例。

本例中的任务是获取数据库中每个作者的最新帖子。

示例查询会产生不可用的结果,因为它并不总是返回的最新帖子。

SELECT wp_posts.* FROM wp_posts
    WHERE wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
    GROUP BY wp_posts.post_author           
    ORDER BY wp_posts.post_date DESC

当前接受的答案是

SELECT
    wp_posts.*
FROM wp_posts
WHERE
    wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
HAVING wp_posts.post_date = MAX(wp_posts.post_date) <- ONLY THE LAST POST FOR EACH AUTHOR
ORDER BY wp_posts.post_date DESC

不幸的是,这个答案是简单明了的错误,并且在许多情况下产生的结果不如原始查询稳定。

我最好的解决方案是使用表单的子查询

SELECT wp_posts.* FROM 
(
    SELECT * 
    FROM wp_posts
    ORDER BY wp_posts.post_date DESC
) AS wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author 

那么我的问题是一个简单的问题: 无论如何在分组之前对行进行排序而不诉诸子查询?

编辑:这个问题是另一个问题的延续,我的具体情况略有不同。您可以(并且应该)假设还有一个 wp_posts.id 是该特定帖子的唯一标识符。

4

12 回答 12

421

在子查询中使用 anORDER BY并不是解决此问题的最佳方法。

获得作者的最佳解决方案max(post_date)是使用子查询返回最大日期,然后在日期和最大日期将其加入到您的表中post_author

解决方案应该是:

SELECT p1.* 
FROM wp_posts p1
INNER JOIN
(
    SELECT max(post_date) MaxPostDate, post_author
    FROM wp_posts
    WHERE post_status='publish'
       AND post_type='post'
    GROUP BY post_author
) p2
  ON p1.post_author = p2.post_author
  AND p1.post_date = p2.MaxPostDate
WHERE p1.post_status='publish'
  AND p1.post_type='post'
order by p1.post_date desc

如果您有以下示例数据:

CREATE TABLE wp_posts
    (`id` int, `title` varchar(6), `post_date` datetime, `post_author` varchar(3))
;

INSERT INTO wp_posts
    (`id`, `title`, `post_date`, `post_author`)
VALUES
    (1, 'Title1', '2013-01-01 00:00:00', 'Jim'),
    (2, 'Title2', '2013-02-01 00:00:00', 'Jim')
;

子查询将返回最大日期和作者:

MaxPostDate | Author
2/1/2013    | Jim

然后,由于您将其加入表中,因此您将在这两个值上返回该帖子的完整详细信息。

请参阅SQL Fiddle with Demo

扩展我关于使用子查询准确返回此数据的评论。

MySQL 不会强制您访问列表GROUP BY中包含的每一列SELECT。因此,如果您只GROUP BY返回一列但总共返回 10 列,则不能保证返回属于该列的其他列值post_author。如果该列不在GROUP BYMySQL 中,则选择应返回的值。

使用带有聚合函数的子查询将保证每次都返回正确的作者和帖子。

附带说明一下,虽然 MySQL 允许您ORDER BY在子查询中使用 a 并允许您将 aGROUP BY应用于列表中的并非每一列,但SELECT在包括 SQL Server 在内的其他数据库中不允许此行为。

于 2013-02-08T10:53:42.320 回答
27

您的解决方案使用了GROUP BY 子句的扩展,该子句允许按某些字段进行分组(在这种情况下,只是post_author):

GROUP BY wp_posts.post_author

并选择非聚合列:

SELECT wp_posts.*

未在 group by 子句中列出,或未在聚合函数中使用(MIN、MAX、COUNT 等)。

正确使用 GROUP BY 子句的扩展

当非聚合列的所有值对于每一行都相等时,这很有用。

例如,假设您有一张桌子GardensFlowersname在花园里,flower在花园里):

INSERT INTO GardensFlowers VALUES
('Central Park',       'Magnolia'),
('Hyde Park',          'Tulip'),
('Gardens By The Bay', 'Peony'),
('Gardens By The Bay', 'Cherry Blossom');

你想提取花园里所有的花,花园里长着多朵花。然后你必须使用一个子查询,例如你可以使用这个:

SELECT GardensFlowers.*
FROM   GardensFlowers
WHERE  name IN (SELECT   name
                FROM     GardensFlowers
                GROUP BY name
                HAVING   COUNT(DISTINCT flower)>1);

如果您需要提取花园中唯一花朵的所有花朵,您可以将 HAVING 条件更改为HAVING COUNT(DISTINCT flower)=1,但 MySql 也允许您使用它:

SELECT   GardensFlowers.*
FROM     GardensFlowers
GROUP BY name
HAVING   COUNT(DISTINCT flower)=1;

没有子查询,不是标准的 SQL,但更简单。

对 GROUP BY 子句的扩展使用不正确

但是,如果您选择每行不相等的非聚合列会发生什么?MySql 为该列选择的值是什么?

看起来 MySql 总是选择它遇到的第一个值

为了确保它遇到的第一个值正是您想要的值,您需要将 aGROUP BY应用于有序查询,因此需要使用子查询。你不能这样做。

假设 MySql 总是选择它遇到的第一行,您正确地对 GROUP BY 之前的行进行排序。但不幸的是,如果你仔细阅读文档,你会发现这个假设是不正确的。

When selecting non-aggregated columns that are not always the same, MySql is free to choose any value, so the resulting value that it actually shows is indeterminate .

我看到这个获取非聚合列的第一个值的技巧被使用了很多,它通常/几乎总是有效,我有时也会使用它(风险自负)。但由于它没有记录,你不能依赖这种行为。

此链接(感谢 ypercube!)GROUP BY 技巧已被优化掉显示了相同查询在 MySql 和 MariaDB 之间返回不同结果的情况,这可能是因为优化引擎不同。

所以,如果这个技巧有效,那只是运气问题。

另一个问题的公认答案对 我来说似乎是错误的:

HAVING wp_posts.post_date = MAX(wp_posts.post_date)

wp_posts.post_date是一个非聚合列,它的值将是官方未确定的,但它很可能是第一个post_date遇到的。但由于 GROUP BY 技巧应用于无序表,因此不确定哪个是第一个post_date遇到的。

它可能会返回作为单个作者的唯一帖子的帖子,但即使这并不总是确定的。

一个可能的解决方案

我认为这可能是一个可能的解决方案:

SELECT wp_posts.*
FROM   wp_posts
WHERE  id IN (
  SELECT max(id)
  FROM wp_posts
  WHERE (post_author, post_date) = (
    SELECT   post_author, max(post_date)
    FROM     wp_posts
    WHERE    wp_posts.post_status='publish'
             AND wp_posts.post_type='post'
    GROUP BY post_author
  ) AND wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
  GROUP BY post_author
)

在内部查询中,我将返回每位作者的最长发布日期。然后我考虑到同一个作者理论上可以同时有两个帖子的事实,所以我只得到最大的 ID。然后我返回所有具有这些最大 ID 的行。使用连接而不是 IN 子句可以使其更快。

(如果您确定它ID只会增加,并且 ifID1 > ID2也意味着post_date1 > post_date2,那么查询可以变得更加简单,但我不确定是否是这种情况)。

于 2013-02-08T11:14:48.587 回答
10

你将要阅读的内容相当老套,所以不要在家里尝试这个!

在 SQL 中,您的问题的答案通常是NO,但由于( @bluefeetGROUP BY提到的)的宽松模式,MySQL 中的答案是YES

假设,您在 (post_status, post_type, post_author, post_date) 上有一个 BTREE 索引。索引在引擎盖下的样子如何?

(post_status='publish', post_type='post', post_author='user A', post_date='2012-12-01') (post_status='publish', post_type='post', post_author='user A', post_date='2012-12-31') (post_status='publish', post_type='post', post_author='user B', post_date='2012-10-01') (post_status='publish', post_type=' post', post_author='用户 B', post_date='2012-12-01')

也就是说,数据按所有这些字段升序排序。

GROUP BY默认情况下,它会按分组字段对数据进行排序(在post_author我们的例子中;post_status,post_type 是WHERE子句需要的),如果有匹配的索引,它会按升序获取每个第一条记录的数据。也就是说,查询将获取以下内容(每个用户的第一篇文章):

(post_status='publish', post_type='post', post_author='user A', post_date='2012-12-01') (post_status='publish', post_type='post', post_author='user B', post_date='2012-10-01')

但是GROUP BY在 MySQL 中允许你显式地指定顺序。当你post_user按降序请求时,它会以相反的顺序遍历我们的索引,仍然为每个组取第一个记录,实际上是最后一个。

那是

...
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC

会给我们

(post_status='publish', post_type='post', post_author='user B', post_date='2012-12-01') (post_status='publish', post_type='post', post_author='user A', post_date='2012-12-31')

现在,当您按 post_date 对分组结果进行排序时,您将获得所需的数据。

SELECT wp_posts.*
FROM wp_posts
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC
ORDER BY wp_posts.post_date DESC;

注意

对于这个特定的查询,我不建议这样做。在这种情况下,我会使用@bluefeet建议的稍微修改的版本。但这种技术可能非常有用。在这里看看我的答案:检索每组中的最后一条记录

陷阱:该方法的缺点是

  • 查询的结果依赖于索引,这与 SQL 的精神背道而驰(索引应该只加快查询速度);
  • 索引不知道它对查询的影响(您或其他人将来可能会发现索引太耗费资源并以某种方式更改它,从而破坏查询结果,而不仅仅是它的性能)
  • 如果您不了解查询的工作原理,您很可能会在一个月内忘记解释,并且查询会使您和您的同事感到困惑。

优点是在困难情况下的性能。在这种情况下,查询的性能应该与@bluefeet 的查询相同,因为排序涉及的数据量很大(所有数据都加载到临时表中然后排序;顺便说一句,他的查询(post_status, post_type, post_author, post_date)也需要索引) .

我会建议

正如我所说,这些查询使 MySQL 浪费时间对临时表中潜在的大量数据进行排序。如果您需要分页(即涉及 LIMIT),大部分数据甚至会被丢弃。我要做的是最小化排序数据的数量:即排序并限制子查询中的最小数据,然后连接回整个表。

SELECT * 
FROM wp_posts
INNER JOIN
(
  SELECT max(post_date) post_date, post_author
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) p2 USING (post_author, post_date)
WHERE post_status='publish' AND post_type='post';

使用上述方法的相同查询:

SELECT *
FROM (
  SELECT post_id
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author DESC
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) as ids
JOIN wp_posts USING (post_id);

所有这些查询及其在SQLFiddle上的执行计划。

于 2013-02-12T15:54:52.283 回答
8

试试这个。只需从每个作者那里获取最新发布日期的列表。就是这样

SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post' AND wp_posts.post_date IN(SELECT MAX(wp_posts.post_date) FROM wp_posts GROUP BY wp_posts.post_author) 
于 2013-02-08T11:04:59.460 回答
4

只需使用 max 函数和 group 函数

    select max(taskhistory.id) as id from taskhistory
            group by taskhistory.taskid
            order by taskhistory.datum desc
于 2016-11-17T03:13:38.560 回答
2

不。在分组之前对记录进行排序是没有意义的,因为分组会改变结果集。子查询方式是首选方式。如果这太慢了,你将不得不改变你的表格设计,例如将每个作者的最后一篇文章的 id 存储在一个单独的表中,或者引入一个布尔列来指示每个作者他的哪篇文章是最后一篇一。

于 2013-02-08T10:50:53.367 回答
0

回顾一下,标准解决方案使用不相关的子查询,如下所示:

SELECT x.*
  FROM my_table x
  JOIN (SELECT grouping_criteria,MAX(ranking_criterion) max_n FROM my_table GROUP BY grouping_criteria) y
    ON y.grouping_criteria = x.grouping_criteria
   AND y.max_n = x.ranking_criterion;

如果你使用的是旧版本的 MySQL,或者相当小的数据集,那么你可以使用以下方法:

SELECT x.*
  FROM my_table x
  LEFT
  JOIN my_table y
    ON y.joining_criteria = x.joining_criteria
   AND y.ranking_criteria < x.ranking_criteria
 WHERE y.some_non_null_column IS NULL;  
于 2013-02-08T11:28:00.347 回答
0

不确定这是否已经被建议,但您现在可以使用SQL 窗口函数

SELECT * FROM (
      SELECT wp_posts.*, ROW_NUMBER() OVER (PARTITION BY wp_posts.post_author ORDER BY post_date DESC) rank
      FROM wp_posts
      WHERE wp_posts.post_status = 'publish'
      AND wp_posts.post_type = 'post'
  ) AS T
WHERE rank = 1

所有行都“排名”,然后您只需要选择每个第一行。

我承认我对性能一无所知,但据我所知,这应该是可以接受的。

于 2021-09-29T13:27:18.800 回答
0

以防万一。我做了很多次这样的事情:

select * from 
  (select max(some_quantity) over (partition by id1, id2) as max_quantity, t.*
  from table_name t) tt 
where tt.max_quantity=tt.some_quantity;

这是具有字段条件最大值的分组some_quantity

于 2021-12-19T17:38:34.607 回答
0

这是我使用用户定义变量的解决方案,即使没有 GROUP BY,也可以获得一致的结果。目标是获取整行,而不仅仅是一行中一个单元格的最大值。请参见下面的示例:

SET @product_id := 0;

SELECT
    products.order_code,
    purchases.`date`,
    purchases.price
FROM products
LEFT JOIN (       
    SELECT
        purchases.`date`,
        purchases.price,
        IF(@product_id = purchases.product_id, 0, 1) AS is_last,
        @product_id := purchases.product_id AS product_id
    FROM purchases
    ORDER BY purchases.product_id ASC, purchases.id DESC
) purchases ON products.id = purchases.product_id
WHERE purchases.is_last = 1
ORDER BY products.order_code ASC;

我不确定性能,但在 50000 行购买表上是 0.1 秒。请让我知道我是否可以进行任何性能改进。

于 2022-02-10T17:02:17.857 回答
-1

** 与大型数据集一起使用时,子查询可能会对性能产生不良影响 **

原始查询

SELECT wp_posts.*
FROM   wp_posts
WHERE  wp_posts.post_status = 'publish'
       AND wp_posts.post_type = 'post'
GROUP  BY wp_posts.post_author
ORDER  BY wp_posts.post_date DESC; 

修改后的查询

SELECT p.post_status,
       p.post_type,
       Max(p.post_date),
       p.post_author
FROM   wp_posts P
WHERE  p.post_status = "publish"
       AND p.post_type = "post"
GROUP  BY p.post_author
ORDER  BY p.post_date; 

因为我maxselect clause==>中使用max(p.post_date)它可以避免子选择查询并在 group by 之后按 max 列排序。

于 2014-11-05T01:42:08.960 回答
-4

首先,不要在select中使用*,影响它们的性能并阻碍group by和order by的使用。试试这个查询:

SELECT wp_posts.post_author, wp_posts.post_date as pdate FROM wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author           
ORDER BY pdate DESC

当您未在 ORDER BY 中指定表时,仅指定别名时,它们将对选择的结果进行排序。

于 2013-02-08T10:49:33.027 回答