JOINing多对多表时避免SQL查询中的重复行

Posted

技术标签:

【中文标题】JOINing多对多表时避免SQL查询中的重复行【英文标题】:Avoid duplicated rows in SQL query when JOINing many to many tables 【发布时间】:2021-02-08 23:19:24 【问题描述】:

我有一个基本的博客系统,其中包含帖子、作者和标签的表格。

一个作者可以写一篇文章,但一篇文章只能由一个作者写(一对多关系)。一个标签可以出现在许多不同的帖子中,任何帖子都可以有多个标签(多对多关系)。在这种情况下,我创建了第 4 个表来链接帖子和标签,如下所示:

 post_id -> posts_tag
|    1     |    1    |
|    1     |    2    |
|    2     |    2    |
|    4     |    1    |

我需要一个查询来列出每个帖子及其用户及其标签(如果有的话)。我非常接近双重 JOIN 查询,但是对于具有多个标签的帖子,我得到了重复的行(该行中的所有内容都是重复的,但标签寄存器)。我使用的查询如下:

SELECT title,
     table_users.username author,
     table_tags.tagname tag
  FROM table_posts
  JOIN table_users 
    ON table_posts.user_id = table_users.id
  LEFT 
  JOIN table_posts_tags 
    ON table_posts.id = table_posts_tags.post_id
  LEFT 
  JOIN table_tags 
    ON table_tags.id = table_posts_tags.tag_id

当有多个标签与同一个帖子关联时,任何人都可以建议修改此查询或新的适当的查询来解决行重复问题*吗?类型

(*) 为了清楚起见:在上表中,查询将抛出 4 行,当它应该抛出 3 时,第 1 条帖子(带有 2 个标签)有 1 行,第 2 条帖子有 1 行,帖子有 1 行#4.

重新创建表

CREATE TABLE `table_posts` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(120) NOT NULL,
  `content` text NOT NULL,
  PRIMARY KEY (`id`),
)

CREATE TABLE `table_tags` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name_tag` varchar(18) NOT NULL,
  PRIMARY KEY (`id`)
)

CREATE TABLE `table_posts_tags` (
  `id` int NOT NULL AUTO_INCREMENT,
  `post_id` int NOT NULL,
  `tag_id` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `tag_id` (`tag_id`),
  KEY `FK_t_posts_tags_t_posts` (`post_id`),
  CONSTRAINT `FK_t_posts_tags_t_posts` FOREIGN KEY (`post_id`) REFERENCES `t_posts` (`id`),
  CONSTRAINT `FK_t_posts_tags_t_tags` FOREIGN KEY (`tag_id`) REFERENCES `t_tags` (`id`)
) 

CREATE TABLE `table_users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(16) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `banned` tinyint DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_t_users_t_roles` (`role_id`),
  CONSTRAINT `FK_t_users_t_roles` FOREIGN KEY (`role_id`) REFERENCES `t_roles` (`id`)
)

【问题讨论】:

欢迎来到 SO。请看:Why should I provide an MCRE for what seems to me to be a very simple SQL query? GROUP By 或在加入之前减少行数,这取决于您的结果是什么 嗨草莓。明天我会尝试从学校提供 CREATE 查询。泰! 添加表创建 【参考方案1】:

一个选项使用group bygroup_concat() 将标签聚合到一个CSV 列表中:

select p.title, u.username author, group_concat(t.tagname) tagnames
from table_posts p
inner join table_users u       on u.id = p.user_id
left join  table_posts_tags pt on pt.post_id = p.id
left join  table_tags t        on t.id = tp.tag_id
group by p.id, p.title, u.username

请注意,我在查询中添加了表别名,并使用它们来限定所有列;这使得查询更短,更易于编写和阅读。

【讨论】:

Ty GMB 但您的解决方案没有保留原始表结构,按照 SQL 理论,“多对多”关系应该依赖于 2 个主表和一个连接。我们已经考虑过 CSV 解决方案,但似乎没有遵循理想的数据架构。如果我错了,或者根本不可能(出于某种理论上的原因)用我们的实际表格设计来解决它,请纠正我。是的,我们在学校! :-) @hip: 那么当一个帖子有三个标签时,你想要哪个结果? Result 应该是按发布顺序排列的帖子列表(相当于 ID 顺序)。列表中的每个项目如下:帖子标题/作者姓名/标签列表(例如,由空格分隔) @hip:好吧,这就是查询的作用。以逗号分隔的标签列表,但可以轻松更改。 空格、逗号、破折号......任何都可以,这不是问题

以上是关于JOINing多对多表时避免SQL查询中的重复行的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis 多表实现多对多查询添加操作

Hibernate多表关系配置——多对多对关系映射

web2py 防止多对多表中的重复

MS SQL 使用联结表创建多对多关系

Django中ORM多对多表的操作

MySQL 基础 -- 多表关系(一对一1对多(多对一)多对多)多表查询(内连接外连接自连接子查询(嵌套查询)联合查询 union)笛卡儿积