Получить общее количество связанных записей в нескольких таблицах MySQL

#mysql #union #left-join #inner-join

#MySQL #Объединение #Левое соединение #внутреннее объединение

Вопрос:

У меня есть следующий запрос, который объединяет две таблицы, содержащие теги, связанные с двумя отдельными объектами: cat и dog. Я пытаюсь создать таблицу, в которой перечислены теги и количество раз, когда они появляются в двух таблицах. Это нормализованная система тегов, поэтому в таблицах cat_tags и dog_tags указывается только идентификатор тега, и именно поэтому я выполняю другое ВНУТРЕННЕЕ ОБЪЕДИНЕНИЕ, чтобы получить фактическое значение тега.

 SELECT x.tag_id, (COUNT(y.tag_id)   COUNT(z.tag_id)) AS num, tag_name AS tag
FROM (SELECT dt.tag_id FROM dog_tags dt UNION SELECT st.tag_id FROM cat_tags st) x
LEFT JOIN dog_tags y ON y.tag_id = x.tag_id
LEFT JOIN cat_tags z ON z.tag_id = x.tag_id
INNER JOIN tags t ON x.tag_id = t.tag_id
GROUP BY x.tag_id ORDER BY num DESC LIMIT 0,100
  

Проблема в том, что количество num неверно для тегов, которые появляются несколько раз в двух таблицах. Например, идентификатор тега номер 5 («ошейник») появляется дважды в cat_tags и дважды в dog_tags, однако приведенный выше запрос дает общее количество как 8 вместо 4. Другой тег, который появляется три раза, отображается как 6. Что-то умножает их на 2. Что это?

Ответ №1:

Я думаю, что ваш запрос действительно сложный, и вы могли бы попробовать что-то вроде этого:

   SELECT tag_id
       , tag_name
       , sum(num) as num
    FROM tags
    join
      (
          SELECT tag_id, count(*) as num FROM dog_tags GROUP BY tag_id
          union all
          SELECT tag_id, count(*) as num FROM cat_tags GROUP BY tag_id
      ) as AnimalsCount on AnimalsCount.tag_id = tags.tag_id
GROUP BY tag_id
       , tag_name -- you can remove this if you are 100% sure is not necessary
  

Кстати, проверьте разницу между union и union all : http://dev.mysql.com/doc/refman/5.0/en/union.html .

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

1. Спасибо. Это тоже сработало. Исходя из времени запроса, которое я получаю, я не уверен, какой из двух ответов более оптимизирован. Можете ли вы указать на преимущества вашего решения?

2. Также посмотрите, какой запрос более эффективен, который вы можете использовать EXPLAIN . Вы также можете добавить тестовые данные, чтобы иметь достаточное количество строк, и тогда время будет другим. Я не знаю, является ли мой запрос более эффективным, но для меня главное преимущество в том, что он более понятен. Я субъективен?, может быть: D

3. В конце концов я выбрал ваш запрос. Но я выяснил, что ваш не включает теги, которые не существуют в таблицах dog_tags и cat_tags (другой есть). Итак, как мы можем изменить ваш запрос, чтобы он включал теги, которые не используются?

4. В этом случае вам нужно изменить join на left join . left join будет включать все строки в левой таблице ( tags ) независимо от того, находятся они в правой таблице или нет (подзапрос AnimalsCount ).

Ответ №2:

Попробуйте это:

 select t.tag_id, t.tag_name as tag,
    ifnull(dc.dog_total, 0)   ifnull(cc.cat_total, 0) as num
from
    tags t
    left join (
        select tag_id, count(*) as dog_total
        from dog_tags
        group by tag_id
    ) as dc on t.tag_id = dc.tag_id
    left join (
        select tag_id, count(*) as cat_total
        from cat_tags
        group by tag_id
    ) as cc on t.tag_id = cc.tag_id
order by num desc
limit 0, 100
  

Ну, проблема заключалась в объединениях, которые у вас есть в вашем запросе. Потому что вы не группировали по tag_id для каждой исходной таблицы. Итак, если collar они появятся 2 раза в dog_tags и 3 в cat_tags объединении, это приведет к 6 строкам, которые имеют одинаковое tag_id значение, поэтому подсчеты будут неверными. Помните, что объединение — это декартово произведение строк каждой объединенной таблицы с определенными критериями. Итак, группируя сначала по tag_id для каждой исходной таблицы, мы гарантируем, что tag_id появляется только один раз в каждой таблице или производной таблице. И когда мы объединим таблицы, каждая tag_id из них создаст одну строку.

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

1. Спасибо. Это сработало. Я понимаю, что вы подошли к проблеме наилучшим образом, но можете ли вы указать, почему именно мой запрос возвращает неправильные результаты, и если бы нам пришлось, как бы мы исправили исходный запрос? Кроме того, я исправил псевдоним «cc» в вашем запросе на «ct».