it-swarm.com.de

Abgleichen einer einzelnen Spalte mit mehreren Werten ohne selbstverbindende Tabelle in MySQL

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. Wenn unsere Tabelle also 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 gesuchte Antwort 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 viel 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, sodass sie möglicherweise zwei Antworten auf Frage 1 in der Antworttabelle haben.

Jetzt bin ich ratlos. Was ist der beste Weg, um dies zu erreichen? Vielen Dank!

14

Wir haben uns dem user_id aus der answers -Tabelle in einer Verknüpfungskette, um Daten aus anderen Tabellen abzurufen, aber das Isolieren der Antworttabelle SQL 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.

Ich habe einen cleveren Weg gefunden, um diese Abfrage ohne Selbstverknüpfung durchzuführen.

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

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 zwei unterschiedliche Antworten auf Frage 2 und Sally zwei unterschiedliche Antworten auf Frage 1 gab.

Um zu ermitteln, welche Fragen von allen Benutzern unterschiedlich beantwortet wurden, platzieren Sie einfach die obige Abfrage in einer Unterabfrage und suchen Sie in der Liste der angegebenen Antworten nach einem Komma, um die Anzahl der unterschiedlichen Antworten wie folgt zu ermitteln:

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 mithilfe 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 VERBINDUNGEN !!!

Versuche es !!!

8
RolandoMySQLDBA

Ich selbst mag die Join-Methode:

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 Nach dem Testen mit einer größeren Tabelle (~ 1 Million Zeilen) dauerte diese Methode erheblich länger als die in der ursprünglichen Frage erwähnte einfache Methode OR.

7
Derek Downey

Wenn Sie einen großen Datensatz haben, würde ich zwei Indizes erstellen:

  • question_id, answer_value, user_id; und
  • benutzer_ID, Frage_ID, Antwort_Wert.

Aufgrund der Organisation der Daten müssen Sie mehrmals beitreten. Wenn Sie wissen, welcher Wert für welche Frage am seltensten vorkommt, können Sie die Abfrage möglicherweise etwas beschleunigen, der Optimierer sollte dies jedoch für Sie tun.

Versuchen Sie die Abfrage wie folgt:

SELECT a1.user_id FROM Antworten a1 
 WHERE a1.question_id = 1 UND a1.answer_value = 'Pooch' 
 INNER JOIN antwortet a2 ON a2.question_id = 2 
 UND a2.answer_value = 'Pfirsich' UND a1.user_id = a2.user_id

Tabelle a1 sollte den ersten Index verwenden. Abhängig von der Datenverteilung kann der Optimierer einen der beiden Indizes verwenden. Die gesamte Abfrage sollte aus den Indizes erfüllt werden.

4
BillThor

Eine Möglichkeit, sich dem anzunähern, 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 von Rolandos 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');

Ausbeuten:

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