#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
. Вы также можете добавить тестовые данные, чтобы иметь достаточное количество строк, и тогда время будет другим. Я не знаю, является ли мой запрос более эффективным, но для меня главное преимущество в том, что он более понятен. Я субъективен?, может быть: D3. В конце концов я выбрал ваш запрос. Но я выяснил, что ваш не включает теги, которые не существуют в таблицах 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».