2016-04-04 4 views
0

Я переписал этот запрос несколько раз (это понедельник) с попыткой найти наиболее эффективный способ получения требуемых данных, но я не уверен, что я даже приближаясь к нему правильно в данный момент.MYSQL Optimizing JOINED UNION SELECT query

Чтобы подвести итог данной проблеме;

Пользователи имеют два набора тегов (key_terms, project_terms), есть таблица связи между каждым из них между users и tags таблицами.

Я хотел бы вытащить всех пользователей, указавших теги в любой таблице. В идеале он также будет включать в себя «наиболее релевантный» тег для этого пользователя, но на этот раз отложил это.

пользователи

| id | name | 
| 1 | dayjo | 
| 2 | stackoverflow | 

теги

| id | tag | 
| 1 | tag1 | 
| 2 | tag2 | 

user_key_term

| user_id | tag_id | 
| 1  | 1  | 
| 1  | 2  | 
| 2  | 1  | 

project_key_term

| user_id | tag_id | 
| 1  | 3  | 
| 2  | 3  | 

Я хочу, чтобы иметь возможность запрашивать, это именованные теги, т.е. если я ищу «tag1», оба пользователя должны быть возвращены, однако если я ищу «tag2», то должен быть возвращен только пользователь 1.

Мои решения

Я попытался с помощью выбора пользователей, а также присоединение тегов дважды (по одному для каждой таблицы ссылок), это, казалось, работало нормально, но не был уверен, что это лучший способ, и не мог выяснить, как получить наиболее релевантный тег.

SELECT t1.tag, t2.tag as most_relevant_tag, users.* FROM users 
LEFT JOIN user_key_term ON user_key_term.user_id = users.id 
LEFT JOIN tags t1 ON user_key_term.tag_id = t1.id 
LEFT JOIN project_key_term ON project_key_term.user_id = users.id 
LEFT JOIN tags t2 ON project_key_term.tag_id = t2.id 
WHERE t1.tag IN ('tag1','tag2') OR t2.tag IN ('tag1','tag2') 
GROUP BY users.id; 

Моя следующая попытка была выбрана UNION, но этот человек чувствует себя грязным;

SELECT users.* FROM 
    `users` 
    INNER JOIN (
     SELECT project_key_term.user_id, tags.id, tags.tag FROM project_key_term 
     JOIN tags ON tags.id = project_key_term.tag_id AND tags.tag IN ('tag1') 
     UNION ALL 
     SELECT user_key_term.user_id,tags.id, tags.tag FROM user_key_term 
     JOIN tags ON tags.id = user_key_term.tag_id AND tags.tag IN ('tag1') 
     ) tags ON tags.user_id = users.id 
    WHERE tags.tag IN ('tag1') 
    GROUP BY users.id; 

Но

Я попытался запустить EXPLAIN на обоих запросов, чтобы увидеть, что лучше всего, но ничего особенно полезного не показывают мне. Тем более, что на данный момент в таблицах не так много данных, потенциально могут быть сотни/тысячи тегов.

Любая помощь по «правильной» или наилучшей практике для выполнения такого запроса была бы замечательной!

+0

Вы можете объединить две таблицы key_term и добавить столбец типа дифференцироваться. Если тег может отображаться только в одной из этих таблиц, вы можете добавить столбец типа вместо тега и иметь только одну таблицу key_term. – Arth

+0

Да, это, безусловно, может быть вариантом!В таблице «project» фактически есть другое поле «project_id». Я решил сохранить ORM красивым и аккуратным, они должны быть раздельными, поскольку они будут управляться (несколько) отдельно. Это сделает его намного быстрее, и у меня будет только один JOIN. Я, конечно же, подумаю об этом, вы знаете, почему бы не иметь ВСЕ таблицы ссылок в одной таблице с полем «тип» и общие поля «table1_id», «table2_id»? ;) – Joel

+0

А, ок. В заявлении UNION вы можете фактически удалить 'WHERE tags.tag IN ('tag1')' из внешнего SELECT, поскольку он является избыточным. – Arth

ответ

0

union запроса может быть упрощен:

SELECT users.* 
FROM users 
INNER JOIN (
     SELECT user_id,tag_id 
     FROM project_key_term 
     UNION ALL 
     SELECT user_id,tag_id 
     FROM user_key_term 
     ) alltags ON alltags.user_id = users.id 
INNER JOIN tags t on t.id = alltags.tag_id 
where t.tag IN ('tag1') 

Edit: Получение наиболее релевантных тегов

SELECT score, t.tag, users.* 
FROM users 
INNER JOIN (select user_id, tag_id, count(*) as score 
      from (SELECT user_id,tag_id 
        FROM project_key_term 
        UNION ALL 
        SELECT user_id,tag_id 
        FROM user_key_term 
        ) alltags 
      group by user_id,tag_id) tagcounts ON tagcounts.user_id = users.id 
INNER JOIN tags t on t.id = tagcounts.tag_id 
where t.tag IN ('tag1','tag2','tag3') 
ORDER BY score DESC 
+0

Ах да, конечно выглядит лучше спасибо :). Будет ли то, что результаты UNION будут намного больше в вашей версии до того, как они будут отфильтрованы, сильно повлияют на производительность? Любое мнение о том, является ли СОЮЗ «лучшим» способом? – Joel

+0

все, что я могу сказать, это исключает соединение двух таблиц с тегами. Производительность зависит от индексов, которые вы определили в этих таблицах. –

+0

Любые предложения по возможности вернуть «самый релевантный» тег для каждого пользователя? Я попробовал COUNT в запросах UNION, и если я группирую по тегу и идентификатор пользователя в основном запросе, он вытаскивает каждый тег и их счет для каждого пользователя, однако в идеале это будет выбирать только самую высокую релевантность. Единственный способ выяснить, как это сделать, - это другой родительский запрос по всему предмету и некоторым дополнительным группам. т.е. .; https://gist.github.com/Dayjo/873025ab31be311fc501ff14766fa79b - Тем не менее, получение этого для работы с Laravel 3 красноречивым: D – Joel