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


14

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!

Risposte:


8

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 delle 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 !!!


1
Apprezzo sempre il livello di impegno che metti nelle tue risposte.
randomx,

7

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

Aggiornare Dopo il test con una tabella più grande (~ 1 milione di righe), questo metodo ha richiesto molto più tempo rispetto al ORmetodo semplice menzionato nella domanda originale.


Grazie per la risposta. Il problema è che questo potrebbe potenzialmente essere un tavolo di grandi dimensioni e doversi unire a 5-6 volte può significare subire un enorme successo in termini di prestazioni, giusto?
Christopher Armstrong,

buon quesiton. sto scrivendo un testcase per provarlo, come non lo so ... pubblicherò i risultati quando sarà fatto
Derek Downey

1
così ho inserito 1 milione di righe con coppie di domande / risposte casuali. L'unione continua a 557 secondi e la tua query OR è terminata in 1,84 secondi ... ora siederà in un angolo.
Derek Downey,

hai indici nella tabella di test? Se esegui la scansione di milioni di righe di tabella alcune volte sarà un po 'lento, senza dubbio :-).
Marian,

@Marian sì, ho aggiunto un indice sul problema (question_id, answer_value) è che la cardinalità è estremamente bassa, quindi non aiuta molto (ogni join è stato scansionato di 100-200k righe)
Derek Downey

5

Ci stavamo unendo al user_id dalla answerstabella 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.


mi piace la tua risposta
Kisspa,

4

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

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

Dovrai iscriverti 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 = 'Peach' 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.


2

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.