【问题标题】:SQL: Select latest thread and latest post, grouped by forum, ordered by latest postSQL:选择最新话题和最新帖子,按论坛分组,按最新帖子排序
【发布时间】:2013-06-17 19:36:36
【问题描述】:

我正在尝试获取

  • 最新线程(id、主题、时间戳、author_id)和
  • 最新帖子(id、thread_id、timestamp、author_id)
  • 每个论坛(ID、名称)
  • 按最新帖子排序,与帖子的创建日期无关。

为什么?

我希望能够显示如下详细信息:

"The latest Answer of forum $forum_id was given on Question $thread_id. Here it is: $post_id"

SELECT  f.id AS forum_id,
        f.name AS forum_name,
        t.id AS thread_id,
        t.topic AS thread_topic,
        t.ts AS thread_timestamp,
        p.id AS post_id,
        p.content AS post_content,
        p.ts AS post_timestamp

 FROM   forums f,
        threads t,
        posts p

WHERE   f.id = t.forum_id 
  AND   t.id = p.thread_id

GROUP BY f.id
ORDER BY p.ts

任何建议,如何更改 SQL 以获得尽可能高的性能?我试图避免子查询,但我很开放!

提前致谢!

【问题讨论】:

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


    【解决方案1】:

    由于 MySQL 不支持窗口函数,我认为没有子查询就没有办法做到这一点:

    SELECT  f.id AS forum_id,
        f.name AS forum_name,
        t.id AS thread_id,
        t.topic AS thread_topic,
        t.ts AS thread_timestamp,
        p.id AS post_id,
        p.content AS post_content,
        p.ts AS post_timestamp
    
    FROM   forums f
    JOIN (SELECT t2.forum_id, max(p2.ts) as ts
          FROM posts p2
          JOIN threads t2 ON p2.thread_id = t2.id
          GROUP BY t2.forum_id) max_p ON f.id = max_p.forum_id
    JOIN   posts p ON max_p.ts = p.ts
    JOIN   threads t ON f.id = t.forum_id AND p.thread_id = t.id
    ORDER BY p.ts
    

    当然,缓存最新结果可以让您在没有调用 MAX() 的性能损失的情况下执行此操作,但是使用正确的索引,这应该不是什么大问题...

    更新

    包含没有帖子的主题和没有主题的论坛的最简洁方法是使用 LEFT JOIN 而不是 INNER JOIN:

    SELECT  f.id AS forum_id,
        f.name AS forum_name,
        t.id AS thread_id,
        t.topic AS thread_topic,
        t.ts AS thread_timestamp,
        p.id AS post_id,
        p.content AS post_content,
        p.ts AS post_timestamp
    
    FROM   forums f
    LEFT JOIN (SELECT t2.forum_id, max(COALESCE(p2.ts, t2.ts)) as ts, COUNT(p2.ts) as post_count
          FROM threads t2 
          LEFT JOIN posts p2 ON p2.thread_id = t2.id
          GROUP BY t2.forum_id) max_p ON f.id = max_p.forum_id
    LEFT JOIN   posts p ON max_p.ts = p.ts
    LEFT JOIN   threads t ON f.id = t.forum_id AND (max_p.post_count = 0 OR p.thread_id = t.id)
    ORDER BY p.ts
    

    【讨论】:

    • 感谢您的回复。这几乎是我想要的,但我只想要每个论坛一排,它是(最新的)细节。你知道如何解决吗?
    • @Mr.Bombastic:再次思考这个问题——你应该只需要一个(更复杂的)子查询来获取你需要的数据。这将返回每个论坛的最新帖子以及与该帖子关联的主题。这对你有用吗?
    • 感谢您的宝贵时间!我通过使用您的代码收到此错误:#1054 - 'on 子句' 中的未知列 't.forum_id'。请注意,我更改了两个列名以获得更好的概览:threads.ts 现在是threads.timestamp_created 和posts.ts 现在是posts.timestamp_posted。
    • @Mr.Bombastic:我的错,现在怎么样?
    • 太好了,它工作得几乎完美。我需要修复的还有两个细节:1)没有列出没有任何帖子的新主题。在这种情况下,我希望能够显示类似“论坛 $forum_id 的最新问题 ($thread_id) 还没有答案”之类的内容。 2) 如果论坛没有主题,则无论如何都应该列出它(带有空白字段)。快完成了。非常感谢您的帮助!
    【解决方案2】:

    我可以想到两种“正确”的方式来做到这一点。第一种是使用连接和子查询:

    SELECT  f.id AS forum_id,
            f.name AS forum_name,
            t.id AS thread_id,
            t.topic AS thread_topic,
            t.ts AS thread_timestamp,
            p.id AS post_id,
            p.content AS post_content,
            p.ts AS post_timestamp
     FROM   forums f join
            threads t
            on f.id = t.forum_id join
            posts p
            on t.id = p.thread_id
    WHERE   t.ts = (select ts from threads t2 where t2.forum_id = t.forum_id order by ts desc limit 1) and
            p.ts = (select ts from posts p2 where p2.thread_id = p.thread_id order by ts desc limit 1)
    GROUP BY f.id
    ORDER BY max(p.ts)
    

    这种方法的问题在于它返回了最新的线程和该线程上的最新帖子。解决这个问题很麻烦(这可能是你真正想要的。)

    子查询获取threadsposts 的最新日期。性能取决于您拥有的索引。这可能是可以接受的。这是标准 SQL。

    另一个是 substring_index()/group_concat() 的技巧,这是 MySQL 特有的:

    SELECT  f.id AS forum_id,
            f.name AS forum_name,
            substring_index(group_concat(t.id order by t.ts desc separator '|'), '|', 1) AS thread_id,
            substring_index(group_concat(t.topic order by t.ts desc separator '|'), '|', 1)  AS thread_topic,
            substring_index(group_concat(t.ts order by p.ts desc separator '|'), '|', 1)  AS thread_timestamp,
            substring_index(group_concat(p.id order by p.ts desc separator '|'), '|', 1)  AS post_id,
            substring_index(group_concat(p.content order by p.ts desc separator '|'), '|', 1)  AS post_content,
            substring_index(group_concat(p.ts order by p.ts desc separator '|'), '|', 1)  AS post_timestamp
     FROM   forums f join
            threads t
            on f.id = t.forum_id join
            posts p
            on t.id = p.thread_id
    GROUP BY f.id
    ORDER BY max(p.ts);
    

    此版本的性能可能会更好(因为您已经承担了group by 的开销)。必须选择分隔符,因此它不在任何值中。否则,只会出现分隔符之前的部分。

    一个优点是线程和帖子是独立处理的,因此您可以获得最新的线程,并分别获得最新的帖子。您可以通过更改group_concat() 中的order by 条件来获取给定线程上的最新帖子。

    此外,要获得您想要的订购,您需要通过max(p.ts) 订购,而不仅仅是p.ts。后者将通过论坛上的任意时间戳进行排序;不能保证它是最新的。

    【讨论】:

    • 听起来不错,但不幸的是 SQL 只是返回一个错误:#1064 - 你的 SQL 语法有错误;检查与您的 MySQL 服务器版本相对应的手册,以了解在 'order by t.ts desc), '|', 1) AS thread_id, substring_index(group_concat(' at line 3) 附近使用的正确语法
    • @Mr.Bombastic 。 . .天哪,我一直认为separatorgroup by 的排序无关紧要。但是顺序会有所不同。我学到了一些东西。
    • 学习永无止境。 :-) 只剩下一个小问题:无论如何它连接了几列(p.content),就像这样:“post #1 的内容,post #3 的内容,post #9 的内容,......”。跨度>
    • @Mr.Bombastic 。 . .在解决一个问题时,我创造了另一个问题。该版本现在也有 separator '|' 的内容。
    • 我们又来了 :-) #1064 - 你有一个错误 [...] 靠近 'separtor '|'), '|', 1) AS post_content, substring_index(group_concat(p. ' 在第 7 行
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 1970-01-01
    • 2023-03-18
    • 2016-01-02
    相关资源
    最近更新 更多