【问题标题】:Complex(ish) Joins Question:复杂(ish)加入问题:
【发布时间】:2011-04-12 17:23:47
【问题描述】:

我在 mySQL 数据库中有下表,(注意:这些是总结的,因此它们与这个问题相关,一些列被省略了)。

author (id, username, password etc.)
thread (id, title, content)
tag (id, name)
reply (id, content)
thread_replies (thread_id, reply_id)
author_replies (author_id, reply_id)
thread_tags (thread_id, tag_id)
author_threads (author_id, thread_id)

现在要获取某个作者的主题,我通常会这样做:

SELECT thread.title, thread.id AS thread_id, thread.content, author.username, author.id AS author_id
FROM thread
JOIN author_threads ON thread.id = author_threads.thread_id
JOIN author ON author_threads.author_id = author.id
WHERE author.id = '12'

这很好用,但是当我尝试获取与这些线程关联的标签时:

SELECT thread.title, thread.id AS thread_id, thread.content, author.username, author.id AS author_id, GROUP_CONCAT( DISTINCT tag.name
ORDER BY tag.name DESC
SEPARATOR ',' ) AS tags
FROM thread
JOIN thread_tags ON thread.id = thread_tags.thread_id
JOIN tag ON thread_tags.tag_id = tag.id
JOIN author_threads ON thread.id = author_threads.thread_id
JOIN author ON author_threads.author_id = author.id
WHERE author.id = '12'
LIMIT 0 , 30

它只在一个列中显示第一个线程,以及与该作者关联的所有标签。

我在这里做错了什么?

另外,如果我想计算作者对每个线程的回复数,该怎么做?

【问题讨论】:

  • 主题是否有多个作者?
  • 不!我没有设计它,我被告知他们这样做是因为他们想使用索引(或类似的东西)。

标签: mysql sql join


【解决方案1】:

因为您在查询中使用聚合 (GROUP_CONCAT),所以您的查询正在分组。由于您没有 group by 子句,因此您的组是整个结果集(因此可以看到作者使用的每个标签)。因为 MySQL 允许在分组语句中使用非分组列,所以您不会收到错误消息,但不会得到您想要的查询。

为了检索正确的结果,您需要将查询分组到 thread.id

select
    thread.title, 
    thread.id as thread_id, 
    thread.content, 
    author.username, 
    author.id as author_id, 
    group_concat(distinct tag.name order by tag.name desc separator ',') as tags

from thread

join thread_tags ON thread.id = thread_tags.thread_id
join tag ON thread_tags.tag_id = tag.id
join author_threads ON thread.id = author_threads.thread_id
join author ON author_threads.author_id = author.id

where author.id = '12'

group by thread.id

limit 0 , 30

这应该在 MySQL 中工作,尽管它不符合 ANSI 的 SQL,因为您在 select 子句中使用非分组列而没有任何聚合。您可以保持原样,或者您可以编写更合规的 SQL 并在除thread.id 之外的所有列周围使用类似max 的内容。这看起来不那么漂亮,但它会合规。

SELECT 
    max(thread.title) as title, 
    thread.id as thread_id, 
    max(thread.content) as content, 
    max(author.username) as username, 
    max(author.id) as author_id, 
    group_concat(distinct tag.name order by tag.name desc separator ',') as tags

from thread

join thread_tags ON thread.id = thread_tags.thread_id
join tag ON thread_tags.tag_id = tag.id
join author_threads ON thread.id = author_threads.thread_id
join author ON author_threads.author_id = author.id

where author.id = '12'

group by thread.id

LIMIT 0 , 30

回复数

上述查询(连同您的原始查询)适用于检索标签列表。您可以编写一个等效的查询来检索回复计数(假设回复不是嵌套的,在这种情况下,您必须使用 MySQL 提供的任何递归查询功能,我不熟悉),但是要检索两者在单个查询中需要子查询:

select
    thread.title, 
    thread.id as thread_id, 
    thread.content, 
    author.username, 
    author.id, 
    (select group_concat(distinct tag.name order by tag.name separator ',')

    from thread_tags

    join tag on tag.id = thread_tags.tag_id 

    where thread_tags.thread_id = thread.id) as tags,
    (select count(1) from thread_replies where thread_id = thread.id) as reply_count

from thread

join author_threads ON thread.id = author_threads.thread_id
join author ON author_threads.author_id = author.id

where author.id = '12'

LIMIT 0 , 30

我已经从这个查询中删除了group by,因为我们的聚合已经被移动到一个子选择中,这意味着外部查询不再被分组。

【讨论】:

  • +1 用于了解当 MySQL 具有聚合函数且没有 Group by 子句时会发生什么
  • @Conrad:谢谢!我希望我能够在 cmets 中很好地解释自己的答案。大家都清楚,使用聚合时的分组行为是 SQL 标准;这是一种允许用户在非标准分组查询的选择列表中包含非分组、非聚合列的做法。
  • @Adam Robinson,我想问,如果作者只能有线程,那么有一个名为 author_threads 的表(作为索引)是个好主意吗?
  • @john:不;如果作者是线程的基本元素并且线程只能有一个作者,则不需要另一个表。如果thread 表很大并且author_id 在结果或连接/where 条件中很少被引用(换句话说,它很少使用),那么可以将其删除以优化连接,但这似乎相当极端和不必要的,除非你正在处理一个非常非常大的线程表并且其他列已经得到了同样的处理。
  • 啊,所以上表不正确。线程应该包含 author_id,那么我就不必使用这些令人难以置信的连接。嗯,现在太晚了
【解决方案2】:

我会先只在里面预查询线程和标签信息...然后,这将已经有作者和有效线程加入您需要的任何其他内容...

如果您确实想应用限制,请将其放在内部“PREQUERY”上,因为这将成为连接外部级别表的基础......否则,您将获得 100 或 1000 的内部查询加上条目加入其他表并切割成 30 条记录...让 IT 在 30 处停止,您就完成了..

在限制返回的条目时,您可能还希望按大多数当前线程排序。

select STRAIGHT_JOIN
      PreQuery.*,
      Author.username,
      Thread.title,
      Thread.Content

   from 
      ( select STRAIGHT_JOIN
              author_threads.author_id,
              author_threads.thread_id,
              group_concat(distinct tag.name order by tag.name desc separator ',') as tags
           from 
              author_threads
                 join thread_tags
                    on author_threads.thread_id = thread_tags.thread_id
                    join tag
                       on thread_tags.tag_id = tag.id
           where
              author_threads.author_id = '12'
           group by
              author_threads.author_id,
              author_threads.thread_id
           limit 0, 30 ) PreQuery

      join author
         on PreQuery.Author_ID = author.id

      join thread
         on PreQuery.Thread_id = thread.id

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多