2013-09-17 5 views
1

У меня есть существующий сайт, чья БД не разработана правильно и содержит много записей, поэтому мы не можем изменить структуру БД.Пересечение MySQL

База данных по текущему выпуску в основном содержит 4 таблицы, пользователей, вопросы, варианты и ответы. Существует стандартный набор вопросов и вариантов, но для каждого пользователя есть одна строка в таблице ответов для каждого набора вопросов и опций. Структура БД и данные примера доступны по адресу SQL fiddle.

Теперь, как новое требование расширенного поиска, мне нужно найти пользователей, применяя несколько фильтров поиска. Пример ввода и ожидаемого результата приводится в комментариях к SQL Fiddle.

Я попытался применить все типы соединений, пересечение, но он всегда терпит неудачу. Может кто-то, пожалуйста, помогите мне написать правильный запрос, желательно облегченный/оптимизированный объединять, поскольку БД содержит множество записей (10000+ пользователей, 100+ вопросов, 500+ опций и 500000+ записей в таблице ответов)?

EDIT: На основе двух ответов, я использовал следующий запрос

SELECT u.id, u.first_name, u.last_name 
FROM users u 
    JOIN answers a ON a.user_id = u.id 
WHERE (a.question_id = 1 AND a.option_id IN (3, 5)) 
    OR (a.question_id = 2 AND a.option_id IN (8)) 
GROUP BY u.id, u.first_name, u.last_name 
HAVING 
    SUM(CASE WHEN (a.question_id = 1 AND a.option_id IN (3, 5)) THEN 1 ELSE 0 END) >=1 
    AND SUM(CASE WHEN (a.question_id = 2 AND a.option_id IN (8)) THEN 1 ELSE 0 END) >= 1; 

Обратите внимание: В реальной базе данных, столбцы user_id, question_id и option_id из answers таблиц проиндексированы.

Выполненный запрос, указанный на SQL Fiddle.

SQL Fiddle для ответа dnoeth.

SQL Foddle для ответа calcinai.

ответ

1

Добавить все вы п фильтров в WHERE, используя OR и повторить их в HAVING (SUM (CASE)) с помощью AND:

SELECT u.id, u.first_name, u.last_name 
FROM users u JOIN answers a 
    ON a.user_id = u.id 
JOIN questions q 
    ON a.question_id = q.id 
JOIN question_options o 
    ON a.option_id = o.id 
WHERE (q.question = 'Language known' AND o.OPTION IN ('French','Russian')) 
    OR (q.question = 'height' AND o.OPTION = '1.51 - 1.7') 
GROUP BY u.id, u.first_name, u.last_name 
HAVING 
    SUM(CASE WHEN (q.question = 'Language known' AND o.OPTION IN ('French','Russian')) THEN 1 ELSE 0 END) >=1 
AND 
    SUM(CASE WHEN (q.question = 'height'   AND o.OPTION = '1.51 - 1.7')   THEN 1 ELSE 0 END) >= 1 
; 

Я изменил ваш присоединяется в более читаемый синтаксис Стандартный SQL.

+0

Спасибо @dnoeth, +1 за рабочий запрос. Должен признаться, что на данный момент я не понимаю, что вы написали после «HAVING». Я должен пройти через документы немного. Я применим ваш и @calcinai andwer и соглашусь с тем, что когда-либо более оптимизировано.Еще раз спасибо за ответ и, что более важно, новое для меня (SUM/CASE WHEN и т. Д.) –

+0

@ Kapil Sharma: Конечно, моя версия более эффективна :-) Легко добавить дополнительные условия, добавить новое условие для WHERE/HAVING вместо другого соединения. Что касается логики: проверьте, если каждое условие имеет значение TRUE хотя бы один раз – dnoeth

+0

Я тоже чувствую себя так, как в реальном времени, может быть 30-50 фильтров и 30-50 соединений, очевидно, будут убивать сервер БД, особенно когда запрос случается слишком часто. Тем не менее нам нужно реализовать некоторый бенчмарк в реальной БД с 500000+ записями. Пожалуйста, дайте мне немного времени, это может занять пару часов. Я бы принял оба ответа, но SO не допускал этого. –

1

Для динамического фильтра потребуется немного возиться, но то, что вы действительно хотите сделать, это поиск по идентификаторам, так как это будет означать меньшее количество соединений и более быстрый запрос.

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

Несколько внутренних соединений предназначены для поддержки множества критериев И и автоматического уменьшения набора результатов.

SELECT * FROM users u 
INNER JOIN answers a ON a.user_id = u.id 
    AND (a.question_id, a.option_id) IN ((1,3),(1,5)) # q 1: Lang, answer 3/5: En/Ru 
INNER JOIN answers a2 ON a2.user_id = u.id 
    AND (a2.question_id, a2.option_id) = (2,8) # q 2: Height, answer 8: 1.71... 
GROUP BY u.id; 

Я хотел бы предложить, убедившись, что есть индекс по (user_id, question_id, option_id) для поиска:

ALTER TABLE `answers` ADD INDEX idx_search(`user_id`, `question_id`, `option_id`); 

В противном случае следует использовать первичные ключи для объединения (если правильно определена), так это будет быстро.

+0

Спасибо за ответ @calcinai +1, да, все столбцы FK индексируются, и я передаю идентификаторы, а не значения. На скрипке я стараюсь сделать сложную задачу простой. Один вопрос, что вы подразумеваете под «i» в группе? –

+0

Это была опечатка. Мой глупый компьютер автоматически корректирует «u» на «i»! – calcinai

+0

Gt8. Большое спасибо. Его работа над простым примером скрипки. Позвольте мне попробовать это в оригинальной БД. Я вернусь в случае каких-либо проблем. Еще раз спасибо. –

Смежные вопросы