Как получить список с отношением 1: N

#mysql #sql

#mysql #sql

Вопрос:

У меня есть таблица «статьи», и у меня есть таблица «теги». Между ними существует отношение N: N, поэтому у меня также есть таблица ‘article_tags_relations’. Я хотел бы получить список статей с тегами, однако в дополнение к имени тега мне также нужен его идентификатор. Таким образом, возвращаемые данные должны быть чем-то вроде (конечно, не в формате JSON):

 [[1,"FirstArticle",{"1":"FirstTag","2":"SecondTag"}],[2,"SecondArticle",{"3":"ThirdTag"}]]
  

Пока у меня есть что-то вроде:

 SELECT article.*, GROUP_CONCAT(tag.name SEPARATOR ', ') AS tags FROM articles AS article LEFT JOIN articles_tags_relations AS relation ON article.id = relation.article_id LEFT JOIN tags AS tag ON relation.tag_id = tag.id LIMIT 0,10;
  

Но в нем есть только имена тегов без идентификаторов. В таблице будет много запросов на чтение, поэтому важна производительность. Я не уверен, что использование GROUP_CONCAT — это правильный путь, я открыт для любых идей. Спасибо!

Ответ №1:

Обязательно ли делать это в одном запросе? Я бы выполнил один запрос, чтобы получить статьи, а затем второй, чтобы получить теги для этих идентификаторов статей. Хотя я не уверен, как сравнивается производительность. (И это, вероятно, зависит от скорости вашей сети относительно стоимости второго запроса по сравнению с левым соединением.)

Тем не менее, попытка объединить несколько фрагментов данных в sql-запросе, похоже, противоречит духу реляционной базы данных и с меньшей вероятностью будет выполняться так же хорошо, как то, в чем база данных действительно хороша.

Комментарии:

1. Нет, не важно делать это в одном запросе. Итак, вы говорите, что я должен сделать что-то вроде следующего? ВЫБЕРИТЕ статью.* ИЗ статей; ВЫБЕРИТЕ тег.* ИЗ article_tags_relations В качестве отношения СЛЕВА ПРИСОЕДИНИТЕ теги КАК тег К отношению.tag_id = tag.id ГДЕ relation.article_id В (1,2,3,4,5, …);

2. Обычно я бы сделал то, что предложил Марсель (за исключением внешнего соединения). В вашем случае я понимаю, что этого делать нельзя, поскольку вы не хотите отправлять текст статьи несколько раз из-за производительности / пропускной способности. Итак, да, я хотел бы выполнить два запроса, которые вы описали. Почти. Вам все еще нужен article_id во втором запросе, чтобы вы знали, какой тег идет с какой статьей. Цель состоит в том, чтобы извлечь текст статьи из объединенного запроса.

Ответ №2:

Насколько я знаю, GROUP_CONCAT() работает вместе с GROUB BY предложением. Однако, вот две альтернативы получения tag.id вашими данными:

 SELECT article.*, tag.id AS tags_id, GROUP_CONCAT(tag.name SEPARATOR ', ') AS tags FROM articles AS article LEFT JOIN articles_tags_relations AS relation ON article.id = relation.article_id LEFT JOIN tags AS tag ON relation.tag_id = tag.id LIMIT 0,10;
  

И 2:

 SELECT article.*, GROUP_CONCAT(tag.name SEPARATOR ', ') AS tags, GROUP_CONCAT(tag.id SEPARATOR ', ') AS tag_ids FROM articles AS article LEFT JOIN articles_tags_relations AS relation ON article.id = relation.article_id LEFT JOIN tags AS tag ON relation.tag_id = tag.id LIMIT 0,10;
  

Ответ №3:

Вы почти на месте. Добавьте, CONCAT чтобы получить идентификатор и запись вместе, или создайте отдельное GROUP_CONCAT предложение для каждого.

 SELECT article.*, 
  GROUP_CONCAT(CONCAT(tag.id,':',tag.name)) AS tags 
FROM articles AS article 
LEFT JOIN articles_tags_relations AS relation ON article.id = relation.article_id 
LEFT JOIN tags AS tag ON relation.tag_id = tag.id
GROUP BY article.id 
LIMIT 0,10
  

AFAIK нет ничего особенного в том, GROUP_CONCAT что приведет к потере производительности.


Просто для развлечения вот запрос, который вернет JSON

 SELECT CONCAT('[',GROUP_CONCAT(CONCAT('[',row_data,']')),']') as JSON
FROM
(
  SELECT CONCAT(a.id,',"',a.title,'",{',GROUP_CONCAT(CONCAT("'",tag.id,'":"',tag.name,'"')),'}') as row_data
  FROM articles AS a 
  LEFT JOIN articles_tags_relations AS relation ON a.id = relation.article_id 
  LEFT JOIN tags AS tag ON relation.tag_id = tag.id
  GROUP BY a.id 
  LIMIT 0,10 
) t1
  

Ответ №4:

Извините, я, кажется, не понимаю вашей проблемы. Для меня решения просты: select a.id, a.name, t.id, t.name from articles a, tags t, articles_tags_relations atr where a.id = atr.article_id and t.id = atr.tag_id

Конечно, это не форматирует результат так, как вы хотите, но форматирование — это не то, для чего предназначен SQL. Сначала вы получаете результат (см. Выше), затем вы используете язык программирования по вашему выбору для форматирования результирующего набора.

Комментарии:

1. Статья может иметь более одного тега. В вашем SQL-запросе извлекается только один тег.

2. Нет, конечно, нет. Результирующий набор будет выглядеть как «1 article1 1 tag1 / 1 article1 2 tag2 / 1 article1 3 tag3» и так далее. Я упоминал, что он не будет хорошо отформатирован.