1

我可能会问一个相对简单的问题。但我找不到解决方案。这是一个 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 和 option_id=3 的人。

预期的结果应该是一个人:约翰。

但是我尝试了类似的方法,但它不起作用,因为它还会返回拥有 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 ) 

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

//////// POST EDITED: 我需要关注另一个重要的点 //////// 如果我们添加一个新的条件 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_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
于 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

我更喜欢使用 COUNT DISTINCT 以防您多次使用相同的选项 id。

祝你好运。

于 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} 中选择具有至少 4 个选项 ID 的人?
于 2013-02-12T15:42:21.470 回答
0

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

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