MySQL: запрос с двумя отношениями многие-ко-многим и дубликатами - PullRequest
1 голос
/ 12 июля 2020

У меня четыре модели: articles, authors и tags. У каждой статьи может быть много авторов, а также может быть много тегов.

Итак, моя БД будет иметь следующие таблицы:

`article`
`article_author`
`author`
`article_tag`
`tags`

Здесь MySQL:

DROP TABLE IF EXISTS article_tag;
DROP TABLE IF EXISTS article_author;
DROP TABLE IF EXISTS author;
DROP TABLE IF EXISTS tag;
DROP TABLE IF EXISTS article;

CREATE TABLE IF NOT EXISTS author (
  id INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(255),
  PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS article (
  id INT(11) NOT NULL AUTO_INCREMENT,
  title VARCHAR(255),
  PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS tag (
  id INT(11) NOT NULL AUTO_INCREMENT,
  tag VARCHAR(255),
  PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS article_author (
  article_id INT(11) NOT NULL,
  author_id INT(11) NOT NULL,
  PRIMARY KEY (article_id, author_id),
  INDEX fk_article_author_article_idx (article_id ASC) VISIBLE,
  INDEX fk_article_author_author_idx (author_id ASC) VISIBLE,
  CONSTRAINT fk_article_author_article
    FOREIGN KEY (article_id)
    REFERENCES article (id),
  CONSTRAINT fk_article_author_author
    FOREIGN KEY (author_id)
    REFERENCES author (id)
);

CREATE TABLE IF NOT EXISTS article_tag (
  article_id INT(11) NOT NULL,
  tag_id INT(11) NOT NULL,
  PRIMARY KEY (article_id, tag_id),
  INDEX fk_article_tag_article_idx (article_id ASC) VISIBLE,
  INDEX fk_article_tag_tag_idx (tag_id ASC) VISIBLE,
  CONSTRAINT fk_article_tag_article
    FOREIGN KEY (article_id)
    REFERENCES article (id),
  CONSTRAINT fk_article_tag_tag
    FOREIGN KEY (tag_id)
    REFERENCES tag (id)
);

И мы можем вставить некоторые данные в нашу базу данных:

INSERT INTO article (id, title) VALUES (1, 'first article'), (2, 'second article'), (3, 'third article');
INSERT INTO author (id, name) VALUES (1, 'first author'), (2, 'second author'), (3, 'third author'), (4, 'fourth author');
INSERT INTO tag (id, tag) VALUES (1, 'first tag'), (2, 'second tag'), (3, 'third tag'), (4, 'fourth tag'), (5, 'fifth tag');
INSERT INTO article_tag (article_id, tag_id) VALUES (1, 1), (1, 2), (1, 3), (2, 2), (2, 4), (2, 5), (3, 1), (3, 2);
INSERT INTO article_author (article_id, author_id) VALUES (1, 1), (1, 2), (1, 3), (2, 2), (2, 4), (3, 1), (3, 2), (3, 3), (3, 4);

Теперь я хочу получить статьи, и для каждой статьи мне нужны связанные идентификаторы авторов, а также идентификаторы тегов:

SELECT 
  article.id, 
  article.title,
  JSON_ARRAYAGG(author.id) AS authors,
  JSON_ARRAYAGG(tag.id) AS tags
FROM article
INNER JOIN article_author ON article.id = article_author.article_id
INNER JOIN author ON article_author.author_id = author.id
INNER JOIN article_tag ON article.id = article_tag.article_id
INNER JOIN tag ON article_tag.tag_id = tag.id
GROUP BY article.id;

Это возвращает дубликаты. Не из-за JSON_ARRAYAGG (мы можем заменить if на COUNT, и дубликаты все равно будут), а из-за двойного отношения в том же запросе: если мы удалим теги или авторов из запроса, дубликаты исчезнут . Но я действительно хотел бы иметь возможность запрашивать несколько отношений в одном запросе.

Как мне избежать этих дубликатов?

1 Ответ

2 голосов
/ 12 июля 2020

Я подозреваю, что вы имеете в виду дубликаты в полях JSON. Проблема в том, что вы объединяете по двум разным измерениям, поэтому вы получаете декартово произведение для каждой статьи.

С некоторыми функциями агрегирования вы можете просто использовать DISTINCT, чтобы обойти это. Эта опция недоступна для функций JSON. Вместо этого вы можете использовать подзапросы:

SELECT a.id, a.title,
       (SELECT JSON_ARRAYAGG(aa.author_id)
        FROM article_author aa 
        WHERE a.id = aa.article_id 
       ) as authors,
       (SELECT JSON_ARRAYAGG(art.tag_id)
        FROM article_tag art
        WHERE a.id = art.article_id 
       ) as tags
FROM article a;

Обратите внимание, что, поскольку вы включаете только идентификаторы, вам не нужно присоединяться к базовым таблицам - authors и tags. Конечно, вы можете сделать это в подзапросе, если хотите, но в этом нет необходимости.

Здесь - скрипт db <>.

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