web-dev-qa-db-fra.com

Faire correspondre une seule colonne à plusieurs valeurs sans table auto-jointe dans MySQL

Nous avons un tableau que nous utilisons pour stocker les réponses aux questions. Nous devons être en mesure de trouver des utilisateurs qui ont certaines réponses à des questions particulières. Donc, si notre tableau comprend les données suivantes:

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

et nous voulons trouver des utilisateurs qui répondent "Pooch" pour la question 1 et "Peach" pour la question 2, le SQL suivant ne fonctionnera (évidemment) pas:

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

Ma première pensée a été de me joindre à la table pour chaque réponse que nous recherchons:

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'

Cela fonctionne, mais comme nous autorisons un nombre arbitraire de filtres de recherche, nous devons trouver quelque chose de beaucoup plus efficace. Ma prochaine solution était quelque chose comme ceci:

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

Cependant, nous voulons que les utilisateurs puissent répondre deux fois au même questionnaire, afin qu'ils puissent potentiellement avoir deux réponses à la question 1 dans le tableau des réponses.

Donc, maintenant je suis perdu. Quelle est la meilleure façon d'aborder cela? Merci!

14

Nous rejoignions le user_id de la table answers dans une chaîne de jointures pour obtenir des données d'autres tables, mais isoler la table de réponses SQL et l'écrire en termes si simples m'a aidé à trouver la solution:

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

Nous utilisions inutilement une deuxième sous-requête.

5

J'ai trouvé un moyen intelligent de faire cette requête sans auto-jointure.

J'ai exécuté ces commandes dans MySQL 5.5.8 pour Windows et j'ai obtenu les résultats suivants:

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         |
+---------+-------------+---------------+

Cet affichage révèle que John a donné deux réponses différentes à la question 2 et Sally a donné deux réponses différentes à la question 1.

Pour identifier les questions auxquelles tous les utilisateurs ont répondu différemment, placez simplement la requête ci-dessus dans une sous-requête et recherchez une virgule dans la liste des réponses données pour obtenir le nombre de réponses distinctes comme suit:

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;

J'ai compris ceci:

+---------+-------------+---------------+-------------------+
| 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 |
+---------+-------------+---------------+-------------------+

Maintenant, filtrez simplement les lignes où multianswer_count = 1 en utilisant une autre sous-requête:

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;

Voici ce que j'ai obtenu:

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

Essentiellement, j'ai effectué trois analyses de table: 1 sur la table principale, 2 sur les petites sous-requêtes. NO JOINS !!!

Essaie !!!

8
RolandoMySQLDBA

J'aime la méthode join, moi-même:

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

pdate Après avoir testé avec une table plus grande (~ 1 million de lignes), cette méthode a pris beaucoup plus de temps que la simple méthode OR mentionnée dans la question d'origine.

7
Derek Downey

Si vous avez un grand ensemble de données, je ferais deux index:

  • question_id, answer_value, user_id; et
  • user_id, question_id, answer_value.

Vous devrez vous joindre plusieurs fois en raison de la façon dont les données sont organisées. Si vous savez quelle valeur pour quelle question est la moins courante, vous pourrez peut-être accélérer un peu la requête, mais l'optimiseur devrait le faire pour vous.

Essayez la requête comme:

SELECT a1.user_id FROM répond a1 
 OERE a1.question_id = 1 ET a1.answer_value = 'Pooch' 
 INNER JOIN répond a2 ON a2.question_id = 2 
 AND a2.answer_value = 'Peach' ET a1.user_id = a2.user_id

Le tableau a1 doit utiliser le premier index. Selon la distribution des données, l'optimiseur peut utiliser l'un ou l'autre des index. La requête entière doit être satisfaite à partir des index.

4
BillThor

Une façon de l'aborder est d'obtenir un sous-ensemble de user_id et de les tester pour la deuxième correspondance:

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');

En utilisant la structure de Rolando:

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');

Rendements:

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)
2
randomx