【问题标题】:MySQL order by before group byMySQL order by before group by
【发布时间】:2013-01-24 02:33:50
【问题描述】:

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

我会从当前最流行的question 继续,如果可以的话,使用他们的例子。

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

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

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 是该特定帖子的唯一标识符。

【问题讨论】:

  • 正如您在给定答案的 cmets 中提到的,可能会有一些具有相同时间戳的帖子。如果是这样,请举例说明数据和预期结果。请描述一下,为什么你期望这个结果。 post_authorpost_date 不足以获得唯一的行,因此每个post_author 必须有更多才能获得唯一的行
  • @SirRufo 你是对的,我已经为你添加了一个编辑。
  • There are plenty of similar questions to be found on here but I don't think that any answer the question adequately. 这就是赏金的用途。
  • @LightnessRacesinOrbit,如果当前问题已经有一个我认为错误的公认答案,你会建议做什么?
  • 想知道为什么您接受了使用子查询的答案 - 当您的问题明确提出时......““无论如何在分组之前对行进行排序而不诉诸子查询?”???

标签: mysql group-by sql-order-by


【解决方案1】:

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

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的条件最大值的分组。

【讨论】:

    【解决方案2】:

    不确定这是否已经被建议,但您现在可以使用SQL window functions

    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
    

    所有行都得到“排名”,然后你只需要选择每一行。

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

    【讨论】:

      【解决方案3】:

      只用max函数和group函数

          select max(taskhistory.id) as id from taskhistory
                  group by taskhistory.taskid
                  order by taskhistory.datum desc
      

      【讨论】:

      • 如果 id 最高的不是最近发布的怎么办?这方面的一个例子可能是作者在发布之前很长一段时间都在草稿中保留了他的帖子。
      【解决方案4】:

      在子查询中使用ORDER 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

      在我的 cmets 上扩展有关使用子查询准确返回此数据的信息。

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

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

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

      【讨论】:

      • 我看到你在那里做了什么,但它只是返回最近发布的日期,而不是最近发布的整行。
      • @RobForrest 这就是加入的作用。您在子查询中按作者返回最近的发布日期,然后在两列上加入您的 wp_posts 以获取整行。
      • @RobForrest 首先,当您将GROUP BY 应用于仅一列时,无法保证其他列中的值始终正确。不幸的是,MySQL 允许这种类型的 SELECT/GROUPing 发生其他产品不允许。第二,在子查询中使用ORDER BY 而在 MySQL 中允许的语法在包括 SQL Server 在内的其他数据库产品中是不允许的。您应该使用每次执行时都会返回正确结果的解决方案。
      • 对于缩放,复合 INDEX(post_author, post_date) 很重要。
      • @jtcotton63 是的,但如果您将post_id 放在您的内部查询中,那么从技术上讲,您也应该按它进行分组,这很可能会扭曲您的结果。
      【解决方案5】:

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

      GROUP BY wp_posts.post_author
      

      并选择非聚合列:

      SELECT wp_posts.*
      

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

      正确使用 GROUP BY 子句的扩展

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

      例如,假设您有一张桌子GardensFlowers(花园的name,花园里的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 总是选择它遇到的 FIRST 值。

      为了确保它遇到的第一个值正是您想要的值,您需要将GROUP 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 trick has been optimized away 显示了同一查询在 MySql 和 MariaDB 之间返回不同结果的情况,可能是因为优化引擎不同。

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

      accepted answer on the other question 在我看来是错误的:

      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 只会增加,并且如果ID1 &gt; ID2 也意味着post_date1 &gt; post_date2,那么查询可以变得更简单,但我不确定是否是这种情况)。

      【讨论】:

      • extension to GROUP By 读起来很有趣,谢谢。
      • 使用 GROUP BY 的选择表达式中的非聚合列默认情况下不再适用于 MySQL 5.7:stackoverflow.com/questions/34115174/…。恕我直言,哪个更安全,并迫使一些人编写更有效的查询。
      • 这个答案不使用子查询吗?原始海报不是要求不使用子查询的解决方案吗?
      • @TV-C-15 问题在于使用子查询,我正在解释为什么使用子查询不起作用。即使接受的答案使用子查询,但它开始解释为什么诉诸是一个坏主意(在子查询中使用 ORDER BY 不是解决此问题的最佳解决方案
      【解决方案6】:

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

      原始查询

      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; 
      

      因为我在select clause ==> max(p.post_date) 中使用了max,所以可以避免子选择查询并按分组后的最大列排序。

      【讨论】:

      • 这确实返回每个作者最近的 post_date,但不能保证返回的其余数据与最近 post_date 的帖子有关。
      • @RobForrest -> 我不明白为什么?详细说明您的答案并抛出索赔是个好主意。据我了解,数据保证是相关的,因为我使用 where 子句过滤相关数据。
      • 在某种程度上,您是完全正确的,您选择的 4 个字段中的每一个都将与最大 post_date 相关,但这并不能回答所提出的问题。例如,如果您添加了 post_id 或帖子的内容,则无法保证这些列与最大日期来自同一记录。要让您的查询返回帖子的其余详细信息,您必须运行第二个查询。如果问题是关于查找最新帖子的日期,那么是的,你的答案就可以了。
      • @guykaplan,子查询并不慢。数据集的大小无关紧要。这取决于你如何使用它。见percona.com/blog/2010/03/18/when-the-subselect-runs-faster
      • @Pacerier:这篇文章确实展示了如何从子查询中获得性能优势,但我希望看到您将给定的场景转换为更好的性能。并且数据大小很重要,再次在您发布的给定文章中,您假设只有一个表可以使用。数据大小不是按行大小,而是按复杂度大小。话虽如此,如果您使用的是非常大的表(涉及的表不多),子查询可能会执行得更好。
      【解决方案7】:

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

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

      假设,您在 (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='user B', post_date='2012-12-01')

      即数据按所有这些字段升序排序。

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

      (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')

      但是 MySQL 中的 GROUP BY 允许您明确指定顺序。而当您按降序请求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='用户 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 建议的稍微修改的版本。但这种技术可能非常有用。看看我的回答:Retrieving the last record in each group

      陷阱:该方法的缺点是

      • 查询的结果依赖于索引,这违背了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 上的执行计划。

      【讨论】:

      • 这是一个有趣的技术,你必须去那里。两件事:你说不要在家里尝试这个,潜在的陷阱是什么?其次,您提到了 bluefeet 答案的略微修改版本,那会是什么?
      • 感谢您,看到有人以不同的方式解决问题很有趣。由于我的数据集远不及您的 18M+ 行,我认为性能不如可维护性重要,所以我认为您以后的选择可能更合适。我喜欢子查询内部限制的想法。
      【解决方案8】:

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

      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;  
      

      【讨论】:

      • 当你说古老的版本时,它会在什么版本的 MySQL 上运行?抱歉,我的示例中的数据集非常大。
      • 它可以(缓慢地)在任何版本上工作。旧版本不能使用子查询。
      • 是的,方法#2(我尝试过的版本来自here)不适用于大型数据集(数百万行),会引发连接丢失 i> 错误。方法 #1 需要大约 15 秒来执行查询。我最初想避免使用嵌套查询,但这让我重新考虑。谢谢!
      • @TheSexiestManinJamaica 是的。 3.5年没有太大变化。假设查询本身是高效的,那么执行查询所需的时间很大程度上取决于数据集的大小、索引的排列和可用的硬件。
      【解决方案9】:

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

      【讨论】:

      • Dennish,您如何回应 Bluefeet 的 cmets,即这种类型的查询不是正确的 SQL 语法,因此不能跨数据库平台移植?还有人担心,不能保证每次都能产生正确的结果。
      【解决方案10】:

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

      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) 
      

      【讨论】:

      • @Rob Forrest ,检查我的解决方案。希望它能解决您的问题!
      • 对不起,我认为这行不通。例如,如果作者 1 和作者 2 在 2013 年 1 月 2 日发布了一些内容,然后作者 2 在 2013 年 8 月 2 日发布了新内容,则所有 3 个帖子都将被退回。是的,日期时间字段包含时间,因此这种情况不太可能发生,但绝不保证在足够大的数据集上。
      • +1 用于使用post_date IN (select max(...) ...)。这比在子选择中进行分组更有效,请参阅dev.mysql.com/doc/refman/5.6/en/subquery-optimization.html
      • 只是为了澄清,如果你有 post_author 索引,那只会更优化。
      • IN ( SELECT ... ) 的效率远低于等效的 JOIN。
      【解决方案11】:

      首先,不要在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 中指定表时,只指定别名,他们将对选择的结果进行排序。

      【讨论】:

      • 忽略选择 *,在本例中它们是为了简洁。你的答案和我给出的第一个例子完全一样。
      • 别名对返回的行和结果的排序没有影响。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-06
      • 2021-02-22
      • 2011-07-18
      • 1970-01-01
      相关资源
      最近更新 更多