MySQLの自己結合テーブルなしで複数の値に対して単一の列を一致させる


14

質問への回答を保存するために使用するテーブルがあります。特定の質問に対する特定の回答を持っているユーザーを見つけることができる必要があります。したがって、テーブルが次のデータで構成されている場合:

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

そして、質問1で「Pooch」、質問2で「Peach」に答えるユーザーを見つけたいと思っています。次のSQLは(明らかに)動作しません。

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

私が最初に考えたのは、探している答えごとにテーブルに参加することでした。

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'

これは機能しますが、任意の数の検索フィルターを許可するため、もっと効率的なものを見つける必要があります。私の次の解決策は次のようなものでした:

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

ただし、ユーザーが同じアンケートに2回回答できるようにするため、回答テーブルの質問1に対して2つの回答が得られる可能性があります。

だから、今私は迷っている。これにアプローチする最良の方法は何ですか?ありがとう!

回答:


8

自己結合なしでこのクエリを実行する賢い方法を見つけました。

これらのコマンドをWindows用のMySQL 5.5.8で実行すると、次の結果が得られました。

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

この表示は、ジョンが質問2に2つの異なる答えを与え、サリーが質問1に2つの異なる答えを与えたことを示しています。

すべてのユーザーがどの質問に異なる回答をしたかをキャッチするには、上記のクエリをサブクエリに配置し、指定された回答のリストでカンマを確認して、次のように個別の回答の数を取得します。

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;

私はこれを得た:

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

次に、別のサブクエリを使用して、multianswer_count = 1の行を除外します。

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;

これは私が得たものです:

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

基本的に、3つのテーブルスキャンを実行しました。1つはメインテーブル、2つは小さなサブクエリです。参加しません!!!

試してみる !!!


1
私はいつもあなたがあなたの答えに注ぐ努力のレベルに感謝しています。
-randomx

7

私は自分自身の結合方法が好きです:

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

更新 大きなテーブル(最大100万行)でテストした後、この方法はOR元の質問で述べた単純な方法よりも大幅に時間がかかりました。


返信いただきありがとうございます。問題は、これが潜在的に大きなテーブルになる可能性があり、5〜6回結合する必要があるため、パフォーマンスが大幅に低下することを意味する場合があります。
クリストファーアームストロング

良いクエシトン。私は知らないので、それをテストするためのテストケースを書いています...完了したら結果を投稿します
デレクダウニー

1
そのため、ランダムなユーザーと質問/回答のペアで100万行を挿入しました。結合はまだ557秒で行われ、ORクエリは1.84秒で完了しました...今すぐ隅に座ります。
デレクダウニー

テストテーブルにインデックスがありますか?数百万行のテーブルを数回スキャンする場合、少し遅くなりますが、間違いありません:-)。
マリアン

@Marianはい、(question_id、answer_value)問題のインデックスを追加しました。カーディナリティが非常に低いため、あまり役に立ちません(各結合は10万から20万行スキャンされました)
デレクダウニー

5

私たちは参加していました user_idanswers他のテーブルからデータを取得するために結合チェーンでテーブルからていましたが、回答テーブルSQLを分離し、そのような単純な用語で記述すると、解決策を見つけるのに役立ちました。

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

不必要に2番目のサブクエリを使用していました。


あなたの答えが好き
-Kisspa

4

データのセットが大きい場合、2つのインデックスを作成します。

  • question_id、answer_value、user_id; そして
  • user_id、question_id、answer_value。

データの編成方法のため、複数回参加する必要があります。どの質問のどの値が最も一般的でないかがわかっている場合、クエリを少し高速化できるかもしれませんが、オプティマイザがそれを行う必要があります。

次のようにクエリを試してください:

SELECT a1.user_id FROM FROM Answers a1
WHERE a1.question_id = 1 AND a1.answer_value = 'Pooch'
INNER JOINは、a2.question_id = 2でa2に回答します 
   AND a2.answer_value = 'Peach' AND a1.user_id = a2.user_id

表a1は最初のインデックスを使用する必要があります。データの分布に応じて、オプティマイザーはいずれかのインデックスを使用できます。インデックスからクエリ全体が満たされる必要があります。


2

これにアプローチする1つの方法は、user_idのサブセットを取得し、2番目の一致についてそれらをテストすることです。

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の構造の使用:

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

利回り:

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.