Mencocokkan satu kolom dengan beberapa nilai tanpa tabel bergabung sendiri di MySQL

14

Kami memiliki tabel yang kami gunakan untuk menyimpan jawaban atas pertanyaan. Kami harus dapat menemukan pengguna yang memiliki jawaban tertentu untuk pertanyaan tertentu. Jadi, jika tabel kami terdiri dari data berikut:

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

dan kami ingin menemukan pengguna yang menjawab 'Pooch' untuk pertanyaan 1 dan 'Peach' untuk pertanyaan 2, SQL berikut ini (jelas) tidak akan berfungsi:

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

Pikiran pertama saya adalah bergabung sendiri di meja untuk setiap jawaban yang kami cari:

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'

Ini berfungsi, tetapi karena kami mengizinkan jumlah filter pencarian yang sewenang-wenang, kami perlu menemukan sesuatu yang jauh lebih efisien. Solusi saya berikutnya adalah sesuatu seperti ini:

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

Namun, kami ingin pengguna dapat mengambil kuesioner yang sama dua kali, sehingga mereka berpotensi memiliki dua jawaban untuk pertanyaan 1 di tabel jawaban.

Jadi, sekarang saya bingung. Apa cara terbaik untuk mendekati ini? Terima kasih!

Christopher Armstrong
sumber

Jawaban:

8

Saya telah menemukan cara cerdas untuk melakukan kueri ini tanpa bergabung sendiri.

Saya menjalankan perintah ini di MySQL 5.5.8 untuk Windows dan mendapatkan hasil sebagai berikut:

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

Tampilan ini mengungkapkan bahwa John memberikan dua jawaban berbeda untuk pertanyaan 2 dan Sally memberikan dua jawaban berbeda untuk pertanyaan 1.

Untuk mengetahui pertanyaan mana yang dijawab berbeda oleh semua pengguna, cukup tempatkan kueri di atas dalam subquery dan periksa koma dalam daftar jawaban yang diberikan untuk mendapatkan hitungan jawaban berbeda sebagai berikut:

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;

Saya mengerti:

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

Sekarang cukup filter baris di mana multianswer_count = 1 menggunakan subquery lain:

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;

Inilah yang saya dapat:

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

Pada dasarnya, saya melakukan tiga pemindaian tabel: 1 di tabel utama, 2 di subqueries kecil. TIDAK BERGABUNG !!!

Cobalah !!!

RolandoMySQLDBA
sumber
1
Saya selalu menghargai upaya yang Anda berikan dalam jawaban Anda.
randomx
7

Saya suka metode bergabung, sendiri:

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

Pembaruan Setelah menguji dengan tabel yang lebih besar (~ 1 juta baris), metode ini memakan waktu lebih lama dari ORmetode sederhana yang disebutkan dalam pertanyaan awal.

Derek Downey
sumber
Terima kasih balasannya. Masalahnya adalah ini bisa berpotensi menjadi meja besar, dan harus bergabung 5-6 kali mungkin berarti mendapatkan kinerja yang luar biasa, betul?
Christopher Armstrong
pertanyaan yang bagus. Saya sedang menulis testcase untuk mengujinya, karena saya tidak tahu ... akan memposting hasil setelah selesai
Derek Downey
1
jadi saya memasukkan 1 juta baris dengan pengguna acak, pasangan tanya / jawab. Gabung masih berjalan di 557 detik dan permintaan ATAU Anda selesai dalam 1,84 detik ... akan duduk di sudut sekarang.
Derek Downey
apakah Anda memiliki indeks pada tabel tes? Jika Anda memindai tabel jutaan baris beberapa kali, ini akan sedikit lambat, tidak diragukan lagi :-).
Marian
@ Maria ya, saya menambahkan indeks pada (question_id, answer_value) masalah adalah kardinalitas sangat rendah, sehingga tidak banyak membantu (setiap bergabung adalah 100-200k baris dipindai)
Derek Downey
5

Kami bergabung dengan user_iddari answerstabel dalam rantai bergabung untuk mendapatkan data dari tabel lain, tetapi mengisolasi tabel jawaban SQL dan menulisnya dalam istilah sederhana seperti itu membantu saya menemukan solusinya:

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

Kami tidak perlu menggunakan sub-permintaan kedua.

Christopher Armstrong
sumber
saya suka Anda menjawab
Kisspa
4

Jika Anda memiliki satu set data yang besar, saya akan melakukan dua indeks:

  • question_id, answer_value, user_id; dan
  • user_id, question_id, answer_value.

Anda harus bergabung beberapa kali karena cara pengorganisasian data. Jika Anda tahu nilai pertanyaan mana yang paling tidak umum, Anda mungkin dapat sedikit mempercepat kueri, tetapi pengoptimal harus melakukannya untuk Anda.

Coba kueri sebagai:

SELECT a1.user_id DARI jawaban a1
WHERE a1.question_id = 1 AND a1.answer_value = 'Pooch'
INNER BERGABUNG menjawab a2 ON a2.question_id = 2 
   AND a2.answer_value = 'Peach' AND a1.user_id = a2.user_id

Tabel a1 harus menggunakan indeks pertama. Bergantung pada distribusi data, pengoptimal dapat menggunakan indeks mana pun. Seluruh permintaan harus dipenuhi dari indeks.

BillThor
sumber
2

Salah satu cara untuk mendekatinya adalah dengan mendapatkan subset dari user_id dan menguji mereka untuk kecocokan kedua:

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

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

Hasil:

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
sumber