MySQL: запрос с двумя отношениями многие ко многим и дубликатами, с двойной промежуточной таблицей - PullRequest
0 голосов
/ 14 июля 2020

Этот вопрос касается выбора данных по отношениям «многие ко многим» в MySQL. Относится к другим двум вопросам, но с некоторыми отличиями:

В этих вопросах использовался простой макет базы данных с простыми отношениями «многие ко многим»:

article
article_author
author
article_tag
tag

Сейчас я представлю следующий уровень сложности. Мы хотим, чтобы каждый автор мог пометить каждую из своих статей. Таким образом, мы подключим tags к промежуточной таблице 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);

И теперь я хотите просто выбрать теги, которые авторы статьи использовали для маркировки, как массив 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;

Вот он в скрипте db <>: 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;

Вот он в скрипте db <>: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=20087a9036acb00637be8d2f58747ba5

Любая другая идея будет приветствоваться!

1 Ответ

1 голос
/ 14 июля 2020

Функциональности distinct для json пока нет (что-то вроде 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; 

Сначала вы подготавливаете данные так, как вы хотите (например, отдельные идентификаторы тегов), а затем используете JSON_ARRAYAGG для форматирования вывода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...