SQL“where IN”查询2个表的多对多关系

时间:2013-02-12 15:24:39

标签: mysql sql many-to-many

我可能会问一个相对简单的问题。但我无法找到解决方案。这是两个表的问题很多,所以它们之间有第三个表。下面的架构:

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 OR 3的人:

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

在这种情况下,最佳做法是什么?

//////// POST编辑:我需要关注另一个重点//////// 如果我们用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 个答案:

答案 0 :(得分: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_id的每个id未强制使用唯一约束,则需要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

答案 1 :(得分: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。

祝你好运。

答案 2 :(得分: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}中至少有4个选项ID的人?

答案 3 :(得分:0)

对于“没有这些ID”这个问题的一部分,只需添加一个WHERE子句:

WHERE person_id NOT IN 
(
SELECT person_id
FROM person_option_link
WHERE option_id = 4
)