Узгодження одного стовпця проти кількох значень без самоз'єднання таблиці в MySQL


14

У нас є таблиця, яку ми використовуємо для зберігання відповідей на запитання. Нам потрібно вміти знайти користувачів, які мають певні відповіді на конкретні запитання. Отже, якщо наша таблиця складається з таких даних:

user_id     question_id     answer_value  
Sally        1               Pooch  
Sally        2               Peach  
John         1               Pooch  
John         2               Duke

і ми хочемо знайти користувачів, які відповідають на "Pooch" на питання 1 та "Peach" на питання 2, наступний SQL не буде (очевидно) не працювати:

select user_id 
from answers 
where question_id=1 
  and answer_value = 'Pooch'
  and question_id=2
  and answer_value='Peach'

Моя перша думка полягала в тому, щоб самостійно приєднатись до таблиці за кожною шуканою нами відповіддю:

select a.user_id 
from answers a, answers b 
where a.user_id = b.user_id
  and a.question_id=1
  and a.answer_value = 'Pooch'
  and b.question_id=2
  and b.answer_value='Peach'

Це працює, але оскільки ми допускаємо довільну кількість фільтрів пошуку, нам потрібно знайти щось набагато ефективніше. Моє наступне рішення було приблизно таким:

select user_id, count(question_id) 
from answers 
where (
       (question_id=2 and answer_value = 'Peach') 
    or (question_id=1 and answer_value = 'Pooch')
      )
group by user_id 
having count(question_id)>1

Однак ми хочемо, щоб користувачі могли двічі взяти одну і ту ж анкету, щоб вони могли мати два відповіді на питання 1 у таблиці відповідей.

Отже, зараз я в розгубі. Який найкращий спосіб підійти до цього? Спасибі!

Відповіді:


8

Я знайшов розумний спосіб зробити цей запит без самостійного приєднання.

Я запустив ці команди в MySQL 5.5.8 для Windows і отримав такі результати:

use test
DROP TABLE IF EXISTS answers;
CREATE TABLE answers (user_id VARCHAR(10),question_id INT,answer_value VARCHAR(20));
INSERT INTO answers VALUES
('Sally',1,'Pouch'),
('Sally',2,'Peach'),
('John',1,'Pooch'),
('John',2,'Duke');
INSERT INTO answers VALUES
('Sally',1,'Pooch'),
('Sally',2,'Peach'),
('John',1,'Pooch'),
('John',2,'Duck');

SELECT user_id,question_id,GROUP_CONCAT(DISTINCT answer_value) given_answers
FROM answers GROUP BY user_id,question_id;

+---------+-------------+---------------+
| user_id | question_id | given_answers |
+---------+-------------+---------------+
| John    |           1 | Pooch         |
| John    |           2 | Duke,Duck     |
| Sally   |           1 | Pouch,Pooch   |
| Sally   |           2 | Peach         |
+---------+-------------+---------------+

Цей показ показує, що Джон дав дві різні відповіді на питання 2, а Саллі дала дві різні відповіді на питання 1.

Щоб зрозуміти, на які запитання відповіли всі користувачі по-різному, просто помістіть вищезазначений запит у підзапит і перевірте, чи існує кома у списку заданих відповідей, щоб отримати кількість виразних відповідей наступним чином:

SELECT user_id,question_id,given_answers,
(LENGTH(given_answers) - LENGTH(REPLACE(given_answers,',','')))+1 multianswer_count
FROM (SELECT user_id,question_id,GROUP_CONCAT(DISTINCT answer_value) given_answers
FROM answers GROUP BY user_id,question_id) A;

Я отримав це:

+---------+-------------+---------------+-------------------+
| user_id | question_id | given_answers | multianswer_count |
+---------+-------------+---------------+-------------------+
| John    |           1 | Pooch         |                 1 |
| John    |           2 | Duke,Duck     |                 2 |
| Sally   |           1 | Pouch,Pooch   |                 2 |
| Sally   |           2 | Peach         |                 1 |
+---------+-------------+---------------+-------------------+

Тепер просто відфільтруйте рядки, де multianswer_count = 1, використовуючи інший підзапит:

SELECT * FROM (SELECT user_id,question_id,given_answers,
(LENGTH(given_answers) - LENGTH(REPLACE(given_answers,',','')))+1 multianswer_count
FROM (SELECT user_id,question_id,GROUP_CONCAT(DISTINCT answer_value) given_answers
FROM answers GROUP BY user_id,question_id) A) AA WHERE multianswer_count > 1;

Ось що я отримав:

+---------+-------------+---------------+-------------------+
| user_id | question_id | given_answers | multianswer_count |
+---------+-------------+---------------+-------------------+
| John    |           2 | Duke,Duck     |                 2 |
| Sally   |           1 | Pouch,Pooch   |                 2 |
+---------+-------------+---------------+-------------------+

По суті, я здійснив три сканування таблиці: 1 на головній таблиці, 2 на невеликих підзапитах. НЕ ПРИЄДНАЙТЕСЬ !!!

Спробувати !!!


1
Я завжди ціную той рівень зусиль, який ви доклали до своїх відповідей.
рандокс

7

Сам мені подобається метод приєднання:

SELECT a.user_id FROM answers a
INNER JOIN answers a1 ON a1.question_id=1 AND a1.answer_value='Pooch'
INNER JOIN answers a2 ON a2.question_id=2 AND a2.answer_value='Peach'
GROUP BY a.user_id

Оновлення Після тестування з більшою таблицею (~ 1 мільйон рядків) цей метод зайняв значно більше часу, ніж простий ORметод, зазначений у первинному запитанні.


Дякую за відповідь. Проблема полягає в тому, що це потенційно може бути великою таблицею, і приєднання до неї 5-6 разів може означати отримання величезного хіта на продуктивність, правда?
Крістофер Армстронг

хороший питання. Я пишу тестування, щоб перевірити його, як я не знаю ... опублікую результати, коли це буде зроблено
Дерек Дауні

1
тому я вставив 1 мільйон рядків із випадковими парами користувачів, питань / відповідей. Приєднання все ще триває 557 секунд, а ваш запит АБО завершений за 1,84 секунди ... зараз будете сидіти в кутку.
Дерек Дауні

чи є індекси на тестовій таблиці? Якщо ви скануєте таблицю мільйонів рядків кілька разів, це буде трохи повільно, без сумніву :-).
Маріан

@Marian так, я додав індекс на (question_id, answer_value) проблему - кардинальність надзвичайно низька, тому це не дуже допомагає (кожен приєднання було відскановано 100-200k рядків)
Derek Downey

5

Ми приєднувались user_idіз answersтаблиці до ланцюга приєднань, щоб отримати дані з інших таблиць, але виділення таблиці відповідей SQL та написання її такими простими термінами допомогло мені знайти рішення:

SELECT user_id, COUNT(question_id) 
FROM answers 
WHERE
  (question_id = 2 AND answer_value = 'Peach') 
  OR (question_id = 1 AND answer_value = 'Pooch')
GROUP by user_id 
HAVING COUNT(question_id) > 1

Ми без потреби використовували другий підзапит.


мені подобається, що ти відповідаєш
Kisspa

4

Якщо у вас є великий набір даних, я б зробив два індекси:

  • question_id, answer_value, user_id; і
  • user_id, question_id, answer_value.

Вам потрібно буде приєднатися кілька разів через спосіб організації даних. Якщо ви знаєте, яке значення для цього питання найменш поширене, можливо, ви зможете трохи пришвидшити запит, але оптимізатор повинен зробити це за вас.

Спробуйте запит:

ВИБІРТЕ a1.user_id З відповідей a1
WHERE a1.question_id = 1 AND a1.answer_value = 'Pooch'
INNER JOIN відповідає a2 ON a2.question_id = 2 
   AND a2.answer_value = 'Персик' І a1.user_id = a2.user_id

У таблиці a1 слід використовувати перший індекс. Залежно від розподілу даних оптимізатор може використовувати будь-який індекс. Весь запит повинен бути задоволений з індексів.


2

Один із способів наблизитись до цього - отримати підмножину user_id та протестувати їх для другого матчу:

SELECT user_id 
FROM answers 
WHERE question_id = 1 
AND answer_value = 'Pooch'
AND user_id IN (SELECT user_id FROM answers WHERE question_id=2 AND answer_value = 'Peach');

Використання структури Роландо:

CREATE TABLE answers (user_id VARCHAR(10),question_id INT,answer_value VARCHAR(20));
INSERT INTO answers VALUES
('Sally',1,'Pouch'),
('Sally',2,'Peach'),
('John',1,'Pooch'),
('John',2,'Duke');
INSERT INTO answers VALUES
('Sally',1,'Pooch'),
('Sally',2,'Peach'),
('John',1,'Pooch'),
('John',2,'Duck');

Врожайність:

mysql> SELECT user_id FROM answers WHERE question_id = 1 AND answer_value = 'Pooch' AND user_id IN (SELECT user_id FROM answers WHERE question_id=2 AND answer_value = 'Peach');
+---------+
| user_id |
+---------+
| Sally   |
+---------+
1 row in set (0.00 sec)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.