MySQL之前按顺序排序

时间:2013-02-08 10:38:39

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

这里有很多类似的问题,但我认为没有充分回答这个问题。

我将继续使用当前最受欢迎的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,它是该特定帖子的唯一标识符。

9 个答案:

答案 0 :(得分:330)

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

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

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

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

作为旁注,虽然MySQL允许您在子查询中使用ORDER BY并允许您将GROUP BY应用于SELECT列表中的不是每个列,但此行为不是允许在其他数据库中使用,包括SQL Server。

答案 1 :(得分:18)

您的解决方案使用extension to GROUP BY子句允许按某些字段进行分组(在本例中为post_author):

GROUP BY wp_posts.post_author

并选择非聚合列:

SELECT wp_posts.*

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

正确使用GROUP BY子句的扩展名

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

例如,假设您有一个GardensFlowers(花园的nameflower在花园中生长的表格:

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之前对行进行排序。但不幸的是,如果你仔细阅读文档,你会注意到这个假设是不正确的。

当选择并非总是相同的非聚合列时, MySql可以自由选择任何值,因此实际显示的结果值是不确定的

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

此链接(感谢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 > ID2也意味着post_date1 > post_date2,那么查询可以变得更加简单,但我不是确定是否是这种情况。)

答案 2 :(得分:9)

你要阅读的内容相当骇人听闻,所以不要在家里试试!

在SQL中,一般来说,你的问题的答案是,但由于GROUP BY的放松模式( @bluefeet 提到), MySQL中的答案是

假设您有一个BTREE索引(post_status,post_type,post_author,post_date)。索引的内容如何?

(post_status ='publish',post_type ='post',post_author ='user A',post_date ='2012-12-01') (post_status ='publish',post_type ='post',post_author ='用户A',post_date ='2012-12-31') (post_status ='publish',post_type ='post',post_author ='用户B',post_date ='2012-10-01') (post_status ='发布',post_type ='发布',post_author ='用户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 ='发布',post_type ='发布',post_author ='用户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 ='发布',post_type ='发布',post_author ='用户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;

<强> NB

这不是我为这个特定查询推荐的内容。在这种情况下,我会使用 @bluefeet 建议的略微修改版本。但这种技术可能非常有用。请看一下我的答案:Retrieving the last record in each group

陷阱:该方法的缺点是

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

优势是在困难情况下的表现。在这种情况下,查询的性能应该与@ bluefeet的查询相同,因为排序涉及的数据量(所有数据都加载到临时表中然后排序;顺便说一下,他的查询需要{{1}索引也是如此)。

我的建议

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

(post_status, post_type, post_author, post_date)

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

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';

所有这些查询及其执行计划都在SQLFiddle

答案 3 :(得分: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) 

答案 4 :(得分:3)

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

答案 5 :(得分: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;  

答案 6 :(得分:0)

只需使用max函数和组函数

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

答案 7 :(得分:-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; 

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

答案 8 :(得分:-3)

首先,不要在选择中使用*,影响它们的性能并阻碍按顺序使用组。 试试这个问题:

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中指定表时,只是别名,他们会对select的结果进行排序。