it-swarm.it

Corrispondenza di una singola colonna con più valori senza tabella autoadesiva in MySQL

Abbiamo una tabella che usiamo per memorizzare le risposte alle domande. Dobbiamo essere in grado di trovare utenti che abbiano determinate risposte a domande particolari. Quindi, se la nostra tabella è composta dai seguenti dati:

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

e vogliamo trovare utenti che rispondano a "Pooch" per la domanda 1 e "Peach" per la domanda 2, il seguente SQL (ovviamente) non preoccuperà:

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

Il mio primo pensiero è stato quello di unirmi al tavolo per ogni risposta che stiamo cercando:

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'

Funziona, ma poiché consentiamo un numero arbitrario di filtri di ricerca, dobbiamo trovare qualcosa di molto più efficiente. La mia prossima soluzione fu qualcosa del genere:

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

Tuttavia, desideriamo che gli utenti siano in grado di prendere due volte lo stesso questionario, quindi potrebbero potenzialmente avere due risposte alla domanda 1 nella tabella delle risposte.

Quindi, ora sono in perdita. Qual è il modo migliore per affrontare questo? Grazie!

14

Ci stavamo unendo al user_id dalla tabella answers in una catena di join per ottenere dati da altre tabelle, ma isolare la tabella delle risposte SQL e scriverla in termini così semplici mi ha aiutato a individuare la soluzione:

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

Stavamo inutilmente utilizzando una seconda query secondaria.

5

Ho trovato un modo intelligente per eseguire questa query senza un self join.

Ho eseguito questi comandi in MySQL 5.5.8 per Windows e ho ottenuto i seguenti risultati:

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

Questo display rivela che John ha dato due risposte diverse alla domanda 2 e Sally ha dato due risposte diverse alla domanda 1.

Per individuare a quali domande hanno risposto in modo diverso da tutti gli utenti, basta posizionare la query sopra in una sottoquery e verificare la presenza di una virgola nell'elenco di risposte fornite per ottenere il conteggio di risposte distinte come segue:

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;

Ho capito:

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

Ora basta filtrare le righe in cui multianswer_count = 1 usando un'altra subquery:

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;

Questo è quello che ho ottenuto:

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

In sostanza, ho eseguito tre scansioni della tabella: 1 nella tabella principale, 2 nelle piccole subquery. NO JOIN !!!

Provaci !!!

8
RolandoMySQLDBA

Mi piace il metodo join, me stesso:

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

Aggiornamento Dopo aver testato con una tabella più grande (~ 1 milione di righe), questo metodo ha richiesto molto più tempo del semplice metodo OR menzionato nella domanda originale.

7
Derek Downey

Se hai un ampio set di dati, farei due indici:

  • question_id, answer_value, user_id; e
  • user_id, question_id, answer_value.

Dovrai partecipare più volte a causa del modo in cui i dati sono organizzati. Se sai quale valore per quale domanda è meno comune potresti essere in grado di accelerare un po 'la query, ma l'ottimizzatore dovrebbe farlo per te.

Prova la query come:

SELEZIONA a1.user_id FROM risponde a1 
 DOVE a1.question_id = 1 AND a1.answer_value = 'Pooch' 
 INNER JOIN risponde a2 ON a2.question_id = 2 
 AND a2.answer_value = "Pesca" AND a1.user_id = a2.user_id

La tabella a1 dovrebbe usare il primo indice. A seconda della distribuzione dei dati, l'ottimizzatore può utilizzare entrambi gli indici. L'intera query dovrebbe essere soddisfatta dagli indici.

4
BillThor

Un modo per accedervi è ottenere un sottoinsieme di user_id e testarli per la seconda partita:

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

Utilizzando la struttura di 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');

I rendimenti:

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