【问题标题】:Need help with a multiple table query in mysql需要有关 mysql 中的多表查询的帮助
【发布时间】:2009-09-20 16:25:46
【问题描述】:

我正在与 kohana 建立一个论坛。我知道那里已经有很好的免费论坛软件,但它是针对家庭网站的,所以我想我会将它用作学习体验。我也没有使用 Kohana 内置的 ORM,因为我想在构建论坛的过程中了解有关 SQL 的更多信息。

对于我的论坛,我有 4 个主要表格:

  • USERS
  • TOPICS
  • POSTS
  • COMMENTS

TOPICS 表:id(自动递增),主题行。

USERS 表:用户名、电子邮件、名字和姓氏以及其他一些不相关的行

POSTS 表:id(自动递增)、post-title、post-body、topic-id、user-id、post-date、updated-date、update-by(将包含最近发表评论的人的用户 ID)

COMMENTS 表:id(自动递增)、post-id、user-id 和评论


在主论坛页面上我想要:

  • 所有主题的列表
  • 每个主题的帖子数
  • 最后更新的帖子,以及更新者
  • 最近更新的主题位于顶部,很可能是“ORDER BY updated-date”

这是我目前的查询:

SELECT topics.id AS topic-id, 
       topics.topic, 
       post-user.id AS user-id, 
       CONCAT_WS(' ', post-user.first-name, post-user.last-name) AS name, 
       recent-post.id AS post-id, 
       post-num.post-total, 
       recent-post.title AS post-title, 
       recent-post.update_date AS updated-date, 
       recent-post.updated-by AS updated-by
  FROM topics
  JOIN (SELECT posts.topic-id,
               COUNT(*) AS post-total                 
          FROM POSTS
         WHERE posts.topic-id = topic-id 
      GROUP BY posts.topic-id) AS post-num ON topics.id = post-num.topic-id
  JOIN (SELECT posts.* 
          FROM posts 
      ORDER BY posts.update-date DESC) AS recent-post ON topics.id = recent-post.topic-id 
  JOIN  (SELECT users.*, 
                posts.user-id 
           FROM users, posts 
          WHERE posts.user-id = users.id) as post-user ON recent-post.user_id = post-user.id 
GROUP BY topics.id

此查询几乎可以正常工作,因为它会获取包含帖子的主题的所有信息。 但它不会返回没有任何帖子的主题

我确信该查询效率低下且错误,因为它对帖子表进行了两次子选择,但这是我能达到我现在的目的的唯一方法。

【问题讨论】:

  • 我现在明白为什么格式化 SQL 查询也很重要
  • @rexem - 感谢您为我清理这些内容,这是我的第一篇 SO 帖子,我不确定我可以添加多少 html 标记。

标签: php sql mysql


【解决方案1】:
  • 短划线不是 SQL 标识符中的有效字符,但您可以改用“_”。
  • 您不一定要从单个 SQL 查询中获取所有内容。事实上,这样做会使编码变得更加困难,有时还会使 SQL 优化器更难执行。
  • 在子查询中使用ORDER BY 是没有意义的。
  • 将您的主键列命名为 topic_iduser_id 等(而不是在每个表中使用“id”),您不必在选择列表中为它们设置别名。

我会这样解决这个问题:

首先获取每个主题的最新帖子,以及相关的用户信息:

SELECT t.topic_id, t.topic,
  u.user_id, CONCAT_WS(' ', u.first_name, u.last_name) AS full_name,
  p.post_id, p.title, p.update_date, p.updated_by
FROM topics t
INNER JOIN 
  (posts p INNER JOIN users u ON (p.updated_by = u.user_id))
  ON (t.topic_id = p.topic_id)
LEFT OUTER JOIN posts p2
  ON (p.topic_id = p2.topic_id AND p.update_date < p2.update_date)
WHERE p2.post_id IS NULL;

然后在一个单独的、更简单的查询中获取每个主题的帖子数。

SELECT t.topic_id, COUNT(*) AS post_total
FROM topics t LEFT OUTER JOIN posts p USING (topic_id)
GROUP BY t.topic_id;

合并应用程序中的两个数据集。

【讨论】:

  • MySQL 允许您将inner join 放在inner join 中?谁编写了那个数据库,Xzibit?虽然,这是一个相当巧妙的技巧。
  • 是的,带括号的连接实际上是标准 SQL,而不仅仅是 MySQL 主义。
  • 当您想要外连接到与内连接相关的一对表时,该技术特别有用,即X LEFT OUTER JOIN (Y INNER JOIN Z)
  • 我会的,所以它是(我很怀疑,所以在我的本地 SQL Server 实例上尝试了它)。这相当于select * from a inner join (select * from b inner join c on b.id = c.id) on a.id = b.id,对吗?显然,这与没有括号的情况相同,但我假设外部连接行为,如果你不小心,它会给你一些相当粗糙的结果。
  • 是的,它产生了类似的结果,但没有使用子查询。括号是括号,您可以使用它们来控制评估顺序,就像在算术表达式中一样。是的,如果你不小心的话,你可以通过外部连接得到粗糙的结果,但这总是正确的! :-)
【解决方案2】:

为确保您获得没有帖子的主题的结果,您需要使用 LEFT JOIN 而不是 JOIN 来首次连接主题和下一个表。 LEFT JOIN 的意思是“总是为左表中的每一行返回一个结果集行,即使没有与右表匹配。”

现在得走了,但我稍后会尝试看看效率问题。

【讨论】:

  • 工作就像一个魅力!谢谢。这个网站的规模非常小,正如我所说的,它只适用于我的家人。我们只有大约 30 个人,我怀疑我会因为查询而遇到任何减速。但是您可以建议的任何其他清理工作都很棒,谢谢
【解决方案3】:

这是一个非常复杂的查询。您应该注意,JOIN 语句会将您的主题限制为有帖子的主题。如果主题没有帖子,则 JOIN 语句会将其过滤掉。

尝试以下查询。

SELECT * 
FROM
(
  SELECT T.Topic, 
         COUNT(AllTopicPosts.ID) NumberOfPosts, 
         MAX(IFNULL(MostRecentPost.Post-Title, '') MostRecentPostTitle,
         MAX(IFNULL(MostRecentPostUser.UserName, '') MostRecentPostUser
         MAX(IFNULL(MostRecentPost.Updated_Date, '') MostRecentPostDate
  FROM TOPICS
  LEFT JOIN POSTS AllTopicPosts ON AllTopicPosts.Topic_Id = TOPICS.ID
  LEFT JOIN 
     (
       SELECT * 
       FROM Posts P
       WHERE P.Topic_id = TOPICS.id
       ORDER BY P.Updated_Date DESC
       LIMIT 1
     ) MostRecentPost ON MostRecentPost.Topic_Id = TOPICS.ID
  LEFT JOIN USERS MostRecentPostUser ON MostRecentPostUser.ID = MostRecentPost.User_Id
  GROUP BY T.Topic
)
ORDER BY MostRecentPostDate DESC

【讨论】:

  • 你为什么要在标题和用户上使用max?如果您有一个主题“ZOMG!小马!”还有一个“ABC123”,“ZOMG!小马!”无论“ABC123”是否是最后一个更新的,都会返回...
  • 你可能是对的。我只按主题分组,而且我知道在其他系统中,如果您不将列包含在 GROUP BY 子句中或在 SELECT 语句中聚合,您将失败。我知道 MySQL 在这方面的工作方式有所不同,但是对于我的一生,我无法编写我知道在其他系统中不会工作的东西。
【解决方案4】:

我会在子查询中使用left join 来拉回正确的主题,然后您可以在此之外做一些跑腿工作以获取一些用户信息。

select
    s.topic_id,
    s.topic,
    u.user_id as last_updated_by_id,
    u.user_name as last_updated_by,
    s.last_post,
    s.post_count
from
    (
        select
            t.id as topic_id,
            t.topic,
            t.user_id as orig_poster,
            max(coalesce(p.post_date, t.post_date)) as last_post,
            count(*) as post_count --would be p.post_id if you don't want to count the topic
        from
            topics t
            left join posts p on
                t.id = p.topic_id
        group by
            t.topic_id,
            t.topic,
            t.user_id
    ) s
    left join posts p on
        s.topic_id = p.topic_id
        and s.last_post = p.post_date
        and s.post_count > 1 --0 if you're using p.post_id up top
    inner join users u on
        u.id = coalesce(p.user_id, s.orig_poster)
order by 
    s.last_post desc

这个查询确实引入了coalesceleft join,它们是非常值得研究的概念。对于两个参数(如这里使用的),您也可以在 MySQL 中使用 ifnull,因为它在功能上是等效的。

请记住,这是 MySQL 独有的(如果您需要移植此代码)。其他数据库有其他功能(SQL Server 中的isnull,Oracle 中的nvl 等)。我使用了coalesce,这样我就可以让这个查询全部符合ANSI。

【讨论】:

    猜你喜欢
    • 2011-04-18
    • 2011-06-30
    • 2011-04-23
    • 1970-01-01
    • 2014-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多