MySQL之前按顺序排序

Rob*_*est 228 mysql group-by sql-order-by

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

我会从当前最流行的问题继续,并使用他们的例子,如果这没关系.

此实例中的任务是获取数据库中每个作者的最新帖子.

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

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
Run Code Online (Sandbox Code Playgroud)

目前接受的答案是

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
Run Code Online (Sandbox Code Playgroud)

不幸的是,这个答案简单明了,并且在很多情况下产生的结果不如原始查询那么稳定.

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

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 
Run Code Online (Sandbox Code Playgroud)

我的问题是一个简单的问题: 无论如何在分组之前订购行而不诉诸子查询?

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

Tar*_*ryn 351

使用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
Run Code Online (Sandbox Code Playgroud)

如果您有以下示例数据:

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')
;
Run Code Online (Sandbox Code Playgroud)

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

MaxPostDate | Author
2/1/2013    | Jim
Run Code Online (Sandbox Code Playgroud)

然后,由于您将该表重新加入表中,因此您将返回该帖子的完整详细信息.

请参阅SQL Fiddle with Demo.

扩展我关于使用子查询准确返回此数据的注释.

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

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

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

  • @RobForrest例如,当您将`GROUP BY`应用于一列时,无法保证其他列中的值始终正确.不幸的是,MySQL允许这种类型的SELECT/GROUPing发生在其他产品上.二,在MySQL中允许的子查询中使用`ORDER BY`的语法在包括SQL Server在内的其他数据库产品中是不允许的.您应该使用一个解决方案,每次执行时都会返回正确的结果. (7认同)
  • 我看到你在那里做了什么,但这只是返回最新帖子的日期,而不是最近一篇文章的整行. (3认同)
  • 对于缩放,化合物`INDEX(post_author,post_date)`很重要. (2认同)

fth*_*lla 19

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

GROUP BY wp_posts.post_author
Run Code Online (Sandbox Code Playgroud)

并选择非聚合列:

SELECT wp_posts.*
Run Code Online (Sandbox Code Playgroud)

未在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');
Run Code Online (Sandbox Code Playgroud)

并且你想要提取在花园里生长的所有花朵,在那里生长多种花朵.然后你必须使用子查询,例如你可以使用它:

SELECT GardensFlowers.*
FROM   GardensFlowers
WHERE  name IN (SELECT   name
                FROM     GardensFlowers
                GROUP BY name
                HAVING   COUNT(DISTINCT flower)>1);
Run Code Online (Sandbox Code Playgroud)

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

SELECT   GardensFlowers.*
FROM     GardensFlowers
GROUP BY name
HAVING   COUNT(DISTINCT flower)=1;
Run Code Online (Sandbox Code Playgroud)

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

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

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

看起来MySql总是选择它遇到的FIRST值.

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

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

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

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

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

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

对其他问题接受的答案 看起来我错了:

HAVING wp_posts.post_date = MAX(wp_posts.post_date)
Run Code Online (Sandbox Code Playgroud)

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
)
Run Code Online (Sandbox Code Playgroud)

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

(如果你确定这ID只是增加,如果ID1 > ID2也意味着post_date1 > post_date2,那么查询可以变得更简单,但我不确定是否是这种情况).

  • 它失败的一个例子:[GROUP BY技巧已被优化掉](https://kb.askmonty.org/en/group-by-trick-has-been-optimized-away/) (2认同)

new*_*ver 9

你要读的是相当hacky,所以不要在家里试试!

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

假设您有一个BTREE索引(post_status,post_type,post_author,post_date).索引如何在引擎盖下看起来像?

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

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

GROUP BY默认情况下,当您执行a时,它会按分组字段对数据进行排序(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')

但是GROUP BY在MySQL中允许您明确指定顺序.当你post_user按降序请求时,它将以相反的顺序遍历我们的索引,仍然记录实际上最后的每个组的第一条记录.

那是

...
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC
Run Code Online (Sandbox Code Playgroud)

会给我们

(post_status ='发布',post_type ='发布',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;
Run Code Online (Sandbox Code Playgroud)

NB:

这不是我为这个特定查询推荐的内容.在这种情况下,我会使用@bluefeet建议的略微修改版本.但这种技术可能非常有用.在这里看一下我的答案:检索每组中的最后一条记录

陷阱:这种方法的缺点是

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

优点是在困难情况下的性能.在这种情况下,查询的性能应该与@ 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';
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

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


san*_*a26 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) 
Run Code Online (Sandbox Code Playgroud)


Kon*_*nas 5

只需使用 max 函数和 group 函数

    select max(taskhistory.id) as id from taskhistory
            group by taskhistory.taskid
            order by taskhistory.datum desc
Run Code Online (Sandbox Code Playgroud)

  • 如果 id 最高的不是最近发布的怎么办?一个例子是,作者在发布之前将其帖子保留在草稿中很长一段时间。 (3认同)