Сопоставление одного столбца с несколькими значениями без самостоятельной таблицы в 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 на маленьких подзапросах. НИКАКИХ СОЕДИНЕНИЙ !!!

Попробуйте!

RolandoMySQLDBA
источник
1
Я всегда ценю уровень усилий, которые вы вкладываете в свои ответы.
randomx
7

Мне нравится метод join, я сам:

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
@Marian Да, я добавил индекс для проблемы (question_id, answer_value): количество элементов чрезвычайно мало, поэтому это мало помогает (каждое объединение было отсканировано 100-200 тыс. Строк)
Дерек Дауни
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

Мы без необходимости использовали второй подзапрос.

Кристофер Армстронг
источник
Мне
4

Если у вас большой набор данных, я бы сделал два индекса:

  • question_id, answer_value, user_id; и
  • user_id, question_id, answer_value.

Вам нужно будет присоединиться несколько раз из-за того, как организованы данные. Если вы знаете, какое значение для какого вопроса является наименее распространенным, вы можете немного ускорить запрос, но оптимизатор должен сделать это за вас.

Попробуйте запрос как:

ВЫБЕРИТЕ a1.user_id ОТ ответов a1
ГДЕ a1.question_id = 1 И a1.answer_value = "Пес"
INNER JOIN отвечает a2 ON a2.question_id = 2 
   И a2.answer_value = 'Персик' И a1.user_id = a2.user_id

Таблица a1 должна использовать первый индекс. В зависимости от распределения данных оптимизатор может использовать любой индекс. Весь запрос должен быть выполнен из индексов.

BillThor
источник
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)
randomx
источник