【问题标题】:group_concat with multiple joins in MySQL在 MySQL 中具有多个连接的 group_concat
【发布时间】:2016-09-22 18:13:21
【问题描述】:

数据库架构

create table `questions` (
  `id` int not null auto_increment,
  `title` varchar(45) not null,
  primary key (`id`));

create table `tags` (
  `id` int not null auto_increment,
  `question_id` int not null,
  `name` varchar(45) not null,
  primary key (`id`));

create table `comments` (
  `id` int not null auto_increment,
  `question_id` int not null,
  `body` varchar(45) not null,
  primary key (`id`));

insert into questions (title) values
("title1"), ("title2"), ("title3");

insert into tags (question_id, name) values
(1, "javascript"), (1, "php"), (1, "c#"), (2, "mysql"), (2, "php"), (3, "c#");

insert into comments (question_id, body) values
(1, "comment1"), (1, "comment1"), (1, "comment2"), (3, "comment3");

这就是它的视觉效果:

questions

| id |  title |
|----|--------|
|  1 | title1 |
|  2 | title2 |
|  3 | title3 |

tags

| id | question_id |       name |
|----|-------------|------------|
|  1 |           1 | javascript |
|  2 |           1 |        php |
|  3 |           1 |         c# |
|  4 |           2 |      mysql |
|  5 |           2 |        php |
|  6 |           3 |         c# |

comments

| id | question_id |     body |
|----|-------------|----------|
|  1 |           1 | comment1 |
|  2 |           1 | comment1 |
|  3 |           1 | comment2 |
|  4 |           3 | comment3 |

每个问题必须至少有一个标签。它也可以有 0 个或多个 cmets。一个问题可以有两个 cmets 具有相同的身体。

期望的输出

我要选择所有问题,即他们的id、title、tags和cmets。

输出应该是这样的:

| id |  title |       tags        |          comments          |
|----|--------|-------------------|----------------------------|
|  1 | title1 | c#,php,javascript | comment1,comment1,comment2 |
|  2 | title2 | php,mysql         | (null)                     |
|  3 | title3 | c#                | comment3                   |

尝试解决问题

我尝试了以下查询:

select questions.id, questions.title,
  group_concat(tags.name), group_concat(comments.body)
from questions
join tags on questions.id = tags.question_id
left join comments on questions.id = comments.question_id
group by questions.id

不幸的是,它没有按预期工作。它产生以下输出:

| id |  title | group_concat(distinct tags.name) |                                                      group_concat(comments.body) |
|----|--------|----------------------------------|----------------------------------------------------------------------------------|
|  1 | title1 |                c#,php,javascript | comment1,comment1,comment1,comment2,comment2,comment2,comment1,comment1,comment1 |
|  2 | title2 |                        php,mysql |                                                                           (null) |
|  3 | title3 |                               c# |                                                                         comment3 |

如你所见,对于第一个问题,我每条评论都得到了 3 次,因为这个问题上有三个标签。

此外,cmets 的顺序错误。它们应该与插入时的顺序相同,即comment1,comment1,comment2,而不是comment1,comment2,comment1

我不能将distinct 用于 cmets,因为一个问题可能有多个 cmets 具有相同的正文。

我知道这可能可以通过嵌套selects 来解决,但据我所知,它会对查询的性能产生巨大的负面影响。

SQL 小提琴

The SQL Fiddle 带有数据库架构和我的查询。

【问题讨论】:

    标签: mysql join group-concat


    【解决方案1】:

    您需要首先聚合并应用GROUP_CONCAT,然后然后加入:

    select questions.id, questions.title,
           tags.name, comments.body
    from questions
    join (
       select question_id, group_concat(tags.name) as name
       from tags
       group by question_id
    ) tags on questions.id = tags.question_id
    left join (
       select question_id, group_concat(comments.body) as body
       from comments
       group by question_id
    ) comments on questions.id = comments.question_id
    

    【讨论】:

    • 另外 GROUP_CONCAT 可以接受 ORDER BY,例如group_concat(comments.body order by comments.id)
    • 这样一个史诗般但简单的解决方案,击败了 mysql 中的sql_mode=only_full_group_by 问题!非常感谢分享!
    【解决方案2】:

    您可以在连接之前使用子查询进行聚合。由于您似乎有独特的标签,那么您似乎可以避免对标签使用子查询,而只需像现在一样加入这些标签:-

    SELECT questions.id, 
            questions.title,
            GROUP_CONCAT(tags.name ORDER BY tags.id), 
            comments.body
    FROM questions
    LEFT OUTER JOIN tags ON questions.id = tags.question_id
    LEFT OUTER JOIN 
    (
       SELECT question_id, 
                GROUP_CONCAT(comments.body ORDER BY id) as body
       FROM comments
       GROUP BY question_id
    ) comments ON questions.id = comments.question_id
    GROUP BY questions.id, 
            questions.title,
            comments.body
    

    您可能会逃脱相关的子查询。如果您有大量问题,这可能会更好,但会限制您对 WHERE 子句感兴趣的问题。不利的一面是,我不确定 MySQL 是否足够聪明,可以为每个问题执行一次相关子查询,而不是每次出现问题时执行一次。

    SELECT questions.id, 
            questions.title,
            GROUP_CONCAT(tags.name ORDER BY tags.id), 
            (
                SELECT GROUP_CONCAT(comments.body ORDER BY id) 
                FROM comments
                WHERE questions.id = comments.question_id
                GROUP BY question_id
            ) AS body
    FROM questions
    LEFT OUTER JOIN tags ON questions.id = tags.question_id
    GROUP BY questions.id, 
            questions.title,
            body
    

    【讨论】:

    • 我认为标签不应该是left join,因为每个问题至少有一个标签。另外,我为什么要按questions.titlequestions.body 分组?
    • 如果必须至少有一个标签,那么您可以使用 INNER JOIN。至于 GROUP BY,虽然 MySQL 通常会接受只做一个 GROUP BY questions.id ,但这不符合 SQL 标准(如果你在其他风格的 SQL 中使用它会失败),并且根据 MySQL 的配置,它会失败(这是 MySQL 可以遵守 GROUP BY 标准的选项)。
    猜你喜欢
    • 2021-06-20
    • 2017-08-08
    • 2014-05-22
    • 2020-02-05
    • 2012-11-22
    • 2012-09-10
    • 1970-01-01
    • 2015-02-25
    • 1970-01-01
    相关资源
    最近更新 更多