Abgleichen einer einzelnen Spalte mit mehreren Werten ohne selbstverknüpfende Tabelle in MySQL

14

Wir haben eine Tabelle, in der wir Antworten auf Fragen speichern. Wir müssen in der Lage sein, Benutzer zu finden, die bestimmte Antworten auf bestimmte Fragen haben. Also, wenn unsere Tabelle aus folgenden Daten besteht:

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

und wir möchten Benutzer finden, die 'Pooch' für Frage 1 und 'Peach' für Frage 2 beantworten. Die folgende SQL wird (offensichtlich) nicht funktionieren:

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

Mein erster Gedanke war, mich für jede Antwort, die wir suchen, selbst an den Tisch zu setzen:

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'

Dies funktioniert, aber da wir eine beliebige Anzahl von Suchfiltern zulassen, müssen wir etwas effizienteres finden. Meine nächste Lösung war ungefähr so:

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

Wir möchten jedoch, dass Benutzer denselben Fragebogen zweimal beantworten können, damit sie möglicherweise zwei Antworten auf Frage 1 in der Antworttabelle haben.

Also, jetzt bin ich ratlos. Wie kann man das am besten angehen? Vielen Dank!

Christopher Armstrong
quelle

Antworten:

8

Ich habe eine clevere Möglichkeit gefunden, diese Abfrage ohne Self-Join durchzuführen.

Ich habe diese Befehle in MySQL 5.5.8 für Windows ausgeführt und dabei die folgenden Ergebnisse erzielt:

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

Diese Anzeige zeigt, dass John auf Frage 2 zwei unterschiedliche Antworten gab und Sally auf Frage 1 zwei unterschiedliche Antworten gab.

Um herauszufinden, welche Fragen von allen Benutzern unterschiedlich beantwortet wurden, platzieren Sie einfach die obige Abfrage in einer Unterabfrage und überprüfen Sie, ob in der Liste der gegebenen Antworten ein Komma steht, um die Anzahl der unterschiedlichen Antworten wie folgt zu erhalten:

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;

Ich schaff das:

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

Filtern Sie jetzt einfach Zeilen mit multianswer_count = 1 mit einer anderen Unterabfrage heraus:

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;

Das habe ich bekommen:

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

Im Wesentlichen habe ich drei Tabellenscans durchgeführt: 1 für die Haupttabelle, 2 für die kleinen Unterabfragen. KEINE MITGLIEDSCHAFT !!!

Versuche es !!!

RolandoMySQLDBA
quelle
1
Ich freue mich immer über die Anstrengungen, die Sie in Ihre Antworten gesteckt haben.
Randomx
7

Ich mag die Join-Methode selbst:

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

Update Nach dem Testen mit einer größeren Tabelle (~ 1 Million Zeilen) dauerte diese Methode erheblich länger als die ORin der ursprünglichen Frage erwähnte einfache Methode.

Derek Downey
quelle
Danke für die Antwort. Das Problem ist, dass dies möglicherweise ein großer Tisch sein kann. Wenn Sie fünf bis sechs Mal dabei sein müssen, bedeutet dies möglicherweise, dass Sie einen enormen Leistungseffekt erzielen.
Christopher Armstrong
gute frage. Ich schreibe einen Testfall, um ihn zu testen, da ich nicht weiß ... werde die Ergebnisse veröffentlichen, wenn es fertig ist
Derek Downey
1
Also habe ich 1 Million Zeilen mit zufälligen Benutzer-, Frage / Antwort-Paaren eingefügt. Der Beitritt dauert noch immer 557 Sekunden, und Ihre OP-Abfrage ist in 1,84 Sekunden abgeschlossen. Sie werden jetzt in einer Ecke sitzen.
Derek Downey
Haben Sie Indizes auf dem Testtisch? Wenn Sie mehrmals in Millionen-Zeilen-Tabellen scannen, wird dies zweifellos etwas langsam :-).
Marian
@Marian ja, habe ich einen Index auf (question_id, answer_value) Problem ist die Mächtigkeit extrem niedrig ist, so dass es nicht viel Hilfe tut (jeder beitreten wurde 100-200k Zeilen gescannt)
Derek Downey
5

Wir haben user_iddie answersTabelle in einer Reihe von Verknüpfungen verknüpft, um Daten aus anderen Tabellen abzurufen, aber das Isolieren der SQL-Antworttabelle und das Schreiben in so einfachen Begriffen haben mir geholfen, die Lösung zu finden:

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

Wir haben unnötigerweise eine zweite Unterabfrage verwendet.

Christopher Armstrong
quelle
Ich möchte, dass du antwortest
Kisspa
4

Wenn Sie eine große Datenmenge haben, würde ich zwei Indizes erstellen:

  • question_id, answer_value, user_id; und
  • user_id, question_id, answer_value.

Aufgrund der Art und Weise, wie die Daten organisiert sind, müssen Sie mehrfach beitreten. Wenn Sie wissen, welcher Wert für welche Frage am wenigsten verbreitet ist, können Sie die Abfrage möglicherweise etwas beschleunigen, der Optimierer sollte dies jedoch für Sie tun.

Versuchen Sie die Abfrage als:

SELECT a1.user_id FROM antwortet a1
WHERE a1.question_id = 1 AND a1.answer_value = 'Pooch'
INNER JOIN antwortet a2 ON a2.question_id = 2 
   AND a2.answer_value = 'Peach' AND a1.user_id = a2.user_id

Tabelle a1 sollte den ersten Index verwenden. Abhängig von der Datenverteilung verwendet der Optimierer möglicherweise einen der Indizes. Die gesamte Abfrage sollte aus den Indizes befriedigt werden.

BillThor
quelle
2

Eine Möglichkeit, dies zu erreichen, besteht darin, eine Teilmenge von user_id abzurufen und diese für die zweite Übereinstimmung zu testen:

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

Verwenden der Rolando-Struktur:

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

Erträge:

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
quelle