MySQL排序依据


243

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

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

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

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

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


2
正如您在给定答案的评论中提到的那样,可能有一些帖子具有相同的时间戳。如果是这样,请举例说明数据和预期结果。并请描述为什么您会期望获得此结果。post_authorpost_date不能获得唯一的一行,因此必须有更多的行来获得唯一的一行post_author
Rufo爵士,

@SirRufo是的,我已经为您添加了一个编辑。
罗伯·福雷斯特

There are plenty of similar questions to be found on here but I don't think that any answer the question adequately.那就是赏金的目的。
Lightness Races in Orbit

@LightnessRacesinOrbit,如果当前问题已经有我认为是错误的可接受答案,那么您建议做什么?
罗布·福雷斯特

1
想知道为什么您接受一个使用子查询的答案-当您的问题明确询问...“”无论如何,在分组之前不对子查询进行排序就可以对行进行排序吗?“ ???
TV-C-15

Answers:


373

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

获得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

为了进一步说明我有关使用子查询准确返回此数据的评论。

MySQL不会强迫您进入列表GROUP BY中的每一列SELECT。结果,如果您仅GROUP BY一列但总共返回10列,则不能保证post_author返回属于的其他列值。如果该列不在GROUP BYMySQL中,则选择应返回的值。

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

附带说明一下,虽然MySQL允许您ORDER BY在子查询中使用,并且允许您对列表GROUP BY中的并非每一列都应用,但是SELECT在包括SQL Server在内的其他数据库中则不允许这种行为。


4
我看到了您在这里所做的事情,但这只是返回最近发布的日期,而不是最新发布的整个行。
罗布·福雷斯特

1
@RobForrest就是联接的作用。您按作者在子查询中返回最新的发布日期,然后wp_posts在两列中重新加入您的表中以获取完整行。
塔林

7
@RobForrest对于一个,当您仅将GROUP BY应用于一列时,不能保证其他列中的值将始终正确。不幸的是,MySQL允许这种类型的SELECT / GROUPing发生在其他产品中。第二,ORDER BY在其他数据库产品(包括SQL Server)中,不允许在MySQL 中使用在子查询中使用an的语法。您应该使用一种解决方案,该解决方案每次执行时都会返回正确的结果。
塔林

2
对于结垢,化合物INDEX(post_author, post_date)很重要。
里克·詹姆斯

1
@ jtcotton63是的,但是如果您post_id输入内部查询,那么从技术上讲,您也应该对其进行分组,这很可能会使结果偏斜。
塔林

20

您的解决方案利用了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总是选择遇到的FIRST值。

为了确保它遇到的第一个值正是您想要的值,您需要将a GROUP BY应用于有序查询,因此需要使用子查询。否则您将无法做到。

假定MySql总是选择遇到的第一行,则您正在对GROUP BY之前的行进行正确排序。但是不幸的是,如果您仔细阅读文档,您会注意到这种假设是不正确的。

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

我看到这种获取未聚合列的第一个值的技巧已被广泛使用,并且通常/几乎总是可行,有时我也使用它(风险自负)。但是由于未记录在案,因此您不能依赖此行为。

该链接(感谢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只是在增加,并且如果ID1 > ID2也意味着post_date1 > post_date2,则可以使查询变得更加简单,但是我不确定是否是这种情况)。


extension to GROUP By是一个有趣的阅读,感谢。
罗布·福雷斯特


在默认情况下,带有GROUP BY的选择表达式中的非聚合列在默认情况下不再适用于MySQL 5.7:stackoverflow.com/questions/34115174/…。哪种恕我直言更安全,并迫使某些人编写更有效的查询。
rink.attendant.17年

这个答案不使用子查询吗?原始海报不是在寻求不使用子查询的解决方案吗?
TV-C-15

1
@ TV-C-15问题在于子查询的重新排序,我正在解释为什么子查询的重新排序无法正常工作。甚至被接受的答案也使用子查询,但是它开始解释为什么重新使用不是一个好主意(在子查询中使用ORDER BY并不是解决此问题的最佳方法
fthiella

9

您将要阅读的内容很骇人,因此请不要在家中尝试!

通常,在SQL中,您问题的答案为NO,但由于GROUP BY(表示@bluefeet)的宽松模式,因此在MySQL中答案为YES

假设您有一个BTREE索引(post_status,post_type,post_author,post_date)。索引看起来如何?

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

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

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

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

注意

对于该特定查询,我不建议这样做。在这种情况下,我将使用@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上的执行计划。


那是一种有趣的技术。两件事:您说不要在家尝试,潜在的陷阱是什么?其次,您提到bluefeet答案的稍微修改的版本,那是什么?
罗伯·福雷斯特

谢谢,很高兴看到有人以不同的方式攻击问题。由于我的数据集离您的1800万行以上不远,因此我认为性能没有可维护性那么重要,因此我认为以后的选择可能更合适。我喜欢子查询内部限制的想法。
罗布·福雷斯特

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) 

@Rob Forrest,检查我的解决方案。希望它能解决您的问题!
sanchitkhanna13年

1
抱歉,我认为这不起作用。例如,如果作者1和作者2都在13年2月1日发布了某个内容,然后作者2在13年2月8日发布了新内容,则将返回所有3个帖子。是的,datetime字段包含时间,因此不太可能出现这种情况,但决不能保证在足够大的数据集上。
罗布·福雷斯特

+1以使用post_date IN (select max(...) ...)。这比在子做一组通过选择更有效,请参阅dev.mysql.com/doc/refman/5.6/en/subquery-optimization.html
Seaux

只是为了澄清一下,只有在索引了post_author的情况下,这才是最佳选择。
Seaux

1
IN ( SELECT ... )比等效的JOIN效率要低得多。
里克·詹姆斯

3

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


丹尼斯(Dennish),您将如何回应Bluefeet的评论,即此类查询不是正确的SQL语法,因此不能跨数据库平台移植?也有人担心不能保证每次都会产生正确的结果。
罗伯·福雷斯特

2

只需使用max函数和group函数

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

3
如果ID最高的人不是最近发布的人怎么办?例如,作者在草稿中保留了很长一段时间后才发布。
罗布·福雷斯特

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;  

当您说古代版本时,它将在什么版本的MySQL上运行?抱歉,在我的示例中,数据集非常大。
罗布·福雷斯特

它将在任何版本上运行(缓慢)。旧版本不能使用子查询。
草莓2013年

是的,方法2(我尝试过的版本是从这里开始的)将不适用于大型数据集(数百万行),并会丢失连接错误。方法1需要大约15秒才能执行查询。我最初想避免使用嵌套查询,但这使我重新考虑。谢谢!
aexl

@TheSexiestManinJamaica是的。3.5年没有太大变化。假设查询本身是有效的,那么执行查询所花费的时间在很大程度上取决于数据集的大小,索引的排列以及可用的硬件。
草莓

-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列排序。


1
确实,这将返回每个作者的最新post_date,但是不能保证返回的其余数据与具有最新post_date的帖子有关。
罗伯·福雷斯特

@RobForrest->我不明白为什么?详细说明您的答案并仅提出要求是一个好主意。据我了解,由于我使用where子句来过滤相关数据,因此保证数据是相关的。
guykaplan 2014年

1
在某种程度上,您是完全正确的,您选择的4个字段中的每一个都将与该最大post_date有关,但这不能回答所提出的问题。例如,如果添加了post_id或帖子的内容,则不能保证这些列与最长日期来自同一记录。为了使您的查询上方返回该帖子的其余详细信息,您必须运行第二个查询。如果问题是关于查找最新帖子的日期,那么您的回答就可以了。
罗伯·福雷斯特

@guykaplan,子查询并不慢。数据集的大小无关紧要。这取决于您如何使用它。参见percona.com/blog/2010/03/18/when-the-subselect-runs-faster
Pacerier,2015年

@Pacerier:文章的确显示了如何从子查询中获得性能收益,但是我很乐意看到您将给定方案转换为更好的性能。并且数据大小很重要,在您发布的给定文章中,您再次假设只使用一个表。数据大小不是按行大小,而是复杂度大小。话虽如此,如果您使用的是非常大的表(涉及的表不多),子查询的性能可能会更好。
guykaplan 2015年

-4

首先,不要在选择中使用*,这会影响其性能,并阻碍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中指定表时,仅指定别名,它们将对选择结果进行排序。


忽略选择*,在此示例中为简洁起见。您的答案与我给出的第一个示例完全相同。
罗布·福雷斯特

别名不影响返回哪一行,也不影响结果的排序。
罗布·福雷斯特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.