1

比較的簡単な質問をするかもしれません。しかし、私はこれに対する解決策を見つけることができません。これは 2 つのテーブルが多対多であるため、それらの間に 3 つ目のテーブルがあります。以下のスキーマ:

CREATE TABLE `options` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `options` (`id`, `name`) VALUES
(1, 'something'),
(2, 'thing'),
(3, 'some option'),
(4, 'other thing'),
(5, 'vacuity'),
(6, 'etc');

CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `person` (`id`, `name`) VALUES
(1, 'ROBERT'),
(2, 'BOB'),
(3, 'FRANK'),
(4, 'JOHN'),
(5, 'PAULINE'),
(6, 'VERENA'),
(7, 'MARCEL'),
(8, 'PAULO'),
(9, 'SCHRODINGER');

CREATE TABLE `person_option_link` (
  `person_id` int(11) NOT NULL,
  `option_id` int(11) NOT NULL,
  UNIQUE KEY `person_id` (`person_id`,`option_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `person_option_link` (`person_id`, `option_id`) VALUES
(1, 1),
(2, 1),
(2, 2),
(3, 2),
(3, 3),
(3, 4),
(3, 5),
(4, 1),
(4, 3),
(4, 6),
(5, 3),
(5, 4),
(5, 5),
(6, 1),
(7, 2),
(8, 3),
(9, 4)
(5, 6);

アイデアは次のとおりです。option_id=1 AND option_id=3 へのリンクを持っているすべての人を取得したいと考えています。

期待される結果は、John という 1 人の人物である必要があります。

しかし、私はそのようなものを試してみました.1または3の人も返すため、うまくいきません:

SELECT * 
FROM person p
LEFT JOIN person_option_link l ON p.id = l.person_id
WHERE l.option_id IN ( 1, 3 ) 

この場合のベストプラクティスは何ですか?

//////// 編集後: もう 1 つの重要な点に注目する必要があります //////// そして、NOT IN で新しい条件を追加するとどうなるでしょうか? お気に入り:

SELECT * 
FROM person p
LEFT JOIN person_option_link l ON p.id = l.person_id
WHERE l.option_id IN ( 3, 4 ) 
AND l.option_id NOT IN ( 6 )

この場合、結果は FRANK になるはずです。なぜなら、3 と 4 も持っている PAULINE にはオプション 6 があり、それは望ましくないからです。

ありがとう!

4

4 に答える 4

2

これはRelational Division問題です。

SELECT p.id, p.name
FROM   person p
       INNER  JOIN person_option_link l 
          ON p.id = l.person_id
WHERE  l.option_id IN ( 1, 3 ) 
GROUP  BY p.id, p.name
HAVING COUNT(*) = 2

option_idfor everyidに一意の制約が適用されていない場合は、一意のDISTINCTフィルタにキーワードが必要ですoption_ID

SELECT p.id, p.name
FROM   person p
       INNER  JOIN person_option_link l 
          ON p.id = l.person_id
WHERE  l.option_id IN ( 1, 3 ) 
GROUP  BY p.id, p.name
HAVING COUNT(DISTINCT l.option_id) = 2
于 2013-02-12T15:26:58.593 に答える
2

とを使用GROUP BYCOUNTます。

SELECT p.id, p.name
FROM person p
LEFT JOIN person_option_link l ON p.id = l.person_id
WHERE l.option_id IN ( 1, 3 ) 
GROUP BY p.id, p.name
HAVING COUNT(Distinct l.option_id) = 2

同じオプションIDを複数回持つ可能性がある場合に備えて、COUNT DISTINCTを使用することをお勧めします。

幸運を。

于 2013-02-12T15:27:13.573 に答える
0

person_option_linkこれは最良のオプションではないかもしれませんが、テーブルに「二重結合」を使用できます。

SELECT * 
  FROM person AS p
  JOIN person_option_link AS l1 ON p.id = l1.person_id AND l1.option_id = 1
  JOIN person_option_link AS l2 ON p.id = l2.person_id AND l2.option_id = 3

これにより、特定のユーザーに対して、オプション ID が 1 の行とオプション ID が 3 の別の行が同時に存在することが保証されます。

GROUP BY の代替手段は確かに機能します。それらもより速いかもしれません(ただし、確実にクエリプランを精査する必要があります)。GROUP BY の代替案は、より多くの値を処理するためにより適切にスケーリングされます。たとえば、オプション ID が 2、3、5、7、11、13、17、19 のユーザーのリストは、このバリアントでは扱いにくいですが、GROUP BY のバリアントは構造化なしで機能します。クエリに変更します。GROUP BY バリアントを使用して、8 つの値のうち少なくとも 4 つを持つユーザーを選択することもできますが、この手法では実質的に実行不可能です。

ただし、GROUP BY を使用するには、次の目的でクエリを少し修正 (または再考) する必要があります。

  • セット {1, 3} 内の 2 つのオプション ID を持つユーザーを選択するにはどうすればよいですか?
  • セット {2, 3, 5, 7, 11, 13, 17, 19} 内の 8 つのオプション ID を持つ人を選択するにはどうすればよいですか?
  • セット {2, 3, 5, 7, 11, 13, 17, 19} のオプション ID を 4 つ以上持つユーザーを選択するにはどうすればよいですか?
于 2013-02-12T15:42:21.470 に答える
0

質問の「has not these ids」の部分については、WHERE 句を追加するだけです。

WHERE person_id NOT IN 
(
SELECT person_id
FROM person_option_link
WHERE option_id = 4
)
于 2013-02-12T19:33:10.047 に答える