MySQL: 查询有两个多对多关系和重复项,有双重中间 table

MySQL: query with two many to many relations and duplicates, with double intermediate table

这个问题是关于 select 在 MySQL 中跨多对多关系处理数据。与另外两个问题相关,但有一些区别:

这些问题使用了具有简单多对多关系的简单模型数据库:

article
article_author
author
article_tag
tag

现在我将介绍下一层的复杂性。我们希望每位作者都能够标记每篇 他们的 文章。因此,我们将 tags 连接到中间 table article_author 而不是直接连接到作者。

article
article_author
author
article_author_tag
tag

这里是 MySQL:

CREATE TABLE `article` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE `author` (
  `id` INT NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
);

CREATE TABLE `tag` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE `article_author` (
  `id` int NOT NULL AUTO_INCREMENT,
  `author_id` INT NOT NULL,
  `article_id` int NOT NULL,
  `createdAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_index` (`author_id`,`article_id`),
  KEY `fk_article_author_author1_idx` (`author_id`),
  KEY `fk_article_author_article1_idx` (`article_id`),
  CONSTRAINT `fk_article_author_article1` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`),
  CONSTRAINT `fk_article_author_author1` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`)
);

CREATE TABLE `article_author_tag` (
  `article_author_id` int NOT NULL,
  `tag_id` int NOT NULL,
  PRIMARY KEY (`article_author_id`,`tag_id`),
  KEY `fk_article_author_tag_article_author1_idx` (`article_author_id`),
  KEY `fk_article_author_tag_tag1_idx` (`tag_id`),
  CONSTRAINT `fk_article_author_tag_article_author1` FOREIGN KEY (`article_author_id`) REFERENCES `article_author` (`id`),
  CONSTRAINT `fk_article_author_tag_tag1` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
); 


INSERT INTO article (id, name) VALUES (1, 'first article'), (2, 'second article');
INSERT INTO `author` (id, name) VALUES (1, 'first author'), (2, 'second author');
INSERT INTO tag (id, name) VALUES (1, 'first tag'), (2, 'second tag');
INSERT INTO article_author (author_id, article_id) VALUES (1, 1), (2, 1);
INSERT INTO article_author_tag (article_author_id, tag_id) VALUES (1, 1), (1, 2), (2, 1), (2, 2);

现在,我只想 select 文章作者用来标记它的标签,作为 JSON 数组;但我无法摆脱重复:

SELECT
  JSON_ARRAYAGG(tag.id)
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;

它在数据库<>fiddle中:https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=253f30ecd2f87b06c3894ef02b2ee35d

知道如何摆脱它们吗?


编辑: 我可以使用 CONCAT 和 GROUP_CONCAT,然后转换为 JSON。但它看起来很老套:

SELECT
   CAST(CONCAT('[', GROUP_CONCAT(DISTINCT tag.id SEPARATOR ','), ']') AS JSON) AS tags
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;

它在数据库<>fiddle中:https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=20087a9036acb00637be8d2f58747ba5

欢迎任何其他想法!

json 还没有 distinct 功能(类似于 JSON_ARRAYAGG(distinct tag.id)),但有一个通用的解决方法:

SELECT JSON_EXTRACT(JSON_OBJECTAGG(tag.id,tag.id),"$.*")
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;

JSON_OBJECTAGG 作为隐含的不同,因为 json 标签根据定义是不同的,所以添加 {"1": 1} 两次结果只剩下一个。之后,您 JSON_EXTRACT 只需值即可获得您想要的格式(例如,没有人为添加的标签)。

另一种方法是向 json 函数提供已经正确、不同的数据:

SELECT JSON_ARRAYAGG(id) 
FROM (
  SELECT distinct tag.id
  FROM article_author
  JOIN article_author_tag 
  ON article_author_tag.article_author_id = article_author.id
  JOIN tag ON article_author_tag.tag_id = tag.id
  WHERE article_author.article_id = 1
) subquery; 

您首先按照您想要的方式准备数据(例如不同的标签 ID),然后使用 JSON_ARRAYAGG 格式化您的输出。