MySQL'de birleştirme tablosu olmadan tek sütunu birden çok değerle eşleştirme


14

Soruların cevaplarını saklamak için kullandığımız bir tablo var. Belirli sorulara belirli cevapları olan kullanıcıları bulabilmeliyiz. Tablonuz aşağıdaki verilerden oluşuyorsa:

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

ve soru 1 için 'Pooch' ve soru 2 için 'Peach' yanıtlayan kullanıcıları bulmak istiyoruz, aşağıdaki SQL (açıkçası) çalışmaz:

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

İlk düşüncem, aradığımız her cevap için masaya kendi kendine katılmaktı:

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'

Bu işe yarıyor, ancak rastgele sayıda arama filtresine izin verdiğimiz için, çok daha verimli bir şey bulmamız gerekiyor. Bir sonraki çözümüm şöyleydi:

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

Ancak, kullanıcıların aynı anketi iki kez alabilmelerini istiyoruz.

Şimdi kaybettim. Buna yaklaşmanın en iyi yolu nedir? Teşekkürler!

Yanıtlar:


8

Kendi kendine katılmadan bu sorguyu yapmak için akıllı bir yol buldum.

Bu komutları Windows için MySQL 5.5.8'de çalıştırdım ve aşağıdaki sonuçları aldım:

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

Bu ekran, John'un 2. soruya iki farklı cevap verdiğini ve Sally'nin 1. soruya iki farklı cevap verdiğini ortaya koymaktadır.

Hangi soruların tüm kullanıcılar tarafından farklı yanıtlandığını yakalamak için, yukarıdaki sorguyu bir alt sorguya yerleştirin ve aşağıdaki gibi farklı cevapların sayısını almak için verilen cevaplar listesinde virgül olup olmadığını kontrol edin:

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;

Bunu anladım:

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

Şimdi başka bir alt sorgu kullanarak multianswer_count = 1 olan satırları filtreleyin:

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;

Elimde bu var:

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

Esasen üç tablo taraması yaptım: 1 ana masada, 2 küçük sorguda. HİÇBİR KATILDI !!!

Bir şans ver !!!


1
Cevaplarınıza gösterdiğiniz çaba düzeyini her zaman takdir ediyorum.
randomx

7

Ben katılmak yöntemi, kendim:

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

Güncelleme Daha büyük bir tabloyla (~ 1 milyon satır) test ettikten sonra, bu yöntem ORorijinal soruda belirtilen basit yöntemden önemli ölçüde daha uzun sürdü .


Cevap için teşekkürler. Sorun şu ki, bu potansiyel olarak büyük bir masa olabilir ve 5-6 kez katılmak zorunda kalmak büyük bir performans isabeti almak anlamına gelebilir, değil mi?
Christopher Armstrong

iyi quesiton. Ben test etmek için bir testcase yazıyorum, bilmediğim gibi ... bittiğinde sonuç göndereceğiz
Derek Downey

1
bu yüzden rastgele kullanıcı, soru / cevap çiftleri ile 1 milyon satır ekledim. Üyelik hala 557 saniyede ve OR sorgunuz 1.84 saniyede bitiyor ... şimdi köşede oturacak.
Derek Downey

test tablosunda dizinleriniz var mı? Milyonlarca tabloyu birkaç kez tarıyorsanız, biraz yavaş olacaktır, şüphesiz :-).
Marian

@Marian evet, (index_id, answer_value) problemine bir endeks ekledim, kardinalite son derece düşük, bu yüzden çok yardımcı olmuyor (her birleştirme 100-200k satır tarandı)
Derek Downey

5

Biz birleştirdiklerini user_iddan answersbana çözümü nokta yardımcı diğer tablolardan veri almak için katılır zincirinin tablonun, ama cevap tablo SQL yalıtma ve böyle basit terimlerle yazmaya:

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

Gereksiz yere ikinci bir alt sorgu kullanıyorduk.


i cevap gibi
Kisspa

4

Büyük bir veri kümeniz varsa, iki dizin yaparım:

  • soru_kimliği, cevap_değer, kullanıcı_kimliği; ve
  • kullanıcı_kimliği, soru_kimliği, yanıt_değer.

Verilerin düzenlenme şekli nedeniyle birden çok kez katılmanız gerekir. Hangi sorunun hangi değer için en az yaygın olduğunu biliyorsanız, sorguyu biraz hızlandırabilirsiniz, ancak optimize edici bunu sizin için yapmalıdır.

Sorguyu şu şekilde deneyin:

A1.user_id FROM SEÇİN a1
A1.question_id = 1 NEREDE VE a1.answer_value = 'Pooch'
INNER JOIN, a2 AÇIK a2.question_id = 2 yanıtlıyor 
   AND a2.answer_value = 'Şeftali' AND a1.user_id = a2.user_id

Tablo A1 ilk dizini kullanmalıdır. Veri dağıtımına bağlı olarak iyileştirici her iki dizini de kullanabilir. Sorgunun tamamı dizinlerden sağlanmalıdır.


2

Buna yaklaşmanın bir yolu, user_id öğesinin bir alt kümesini almak ve bunları ikinci eşleşme için test etmektir:

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

Rolando'nun yapısını kullanma:

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

Verim:

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.