> 文章列表 > Mysql中多对多关系查询,过滤条件中需要判断是否为子集,通过两次not in等价转换进行优化

Mysql中多对多关系查询,过滤条件中需要判断是否为子集,通过两次not in等价转换进行优化

Mysql中多对多关系查询,过滤条件中需要判断是否为子集,通过两次not in等价转换进行优化

最近开发过程中遇到了一个mysql查询优化问题。问题简化如下:
假设有两个table, 他们之间是多对多关系

数据表1:announcement

CREATE TABLE `announcement` (`id` int(11) NOT NULL AUTO_INCREMENT,...# 其他字段PRIMARY KEY (`id`)
);

数据表2:metric

CREATE TABLE `metric` (`id` int(11) NOT NULL AUTO_INCREMENT,...# 其他字段PRIMARY KEY (`id`)

为了记录两个表之间的关系,还需要一个中间表:
中间表: announcement_metric

CREATE TABLE `announcement_metric` (`id` int(11) NOT NULL AUTO_INCREMENT,`announcement_id` int(11) NOT NULL,`metric_id` int(11) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `announcement_metric` (`announcement_id`,`metric_id`),KEY `metric` (metric_id),...# 其他字段
) ;

假设每个用户的都有不同的metric权限,对于某个用户的metric权限列表是已知的。如果一个Announcement对应的所有Metric都在用户权限列表中,那么这个Announcment对用户来说是可见的。现在需要得到一个用户所有可见的Announcement列表。
需求比较清晰,可以在代码中比较简单地实现
方法一(遍历记录查询):
遍历announcement中的记录,对每个announcement记录通过中间表查询其对应的metric_id列表,然后判断是否在用户的权限列表中。这种方法最容易想到,不过在迭代过程中用到了多次数据库查询,效率并不高。
方法二(内存中构建announcement和metric的关系map):
对思路一中的方法进行改进,我们可以一次性查询出关系表中的所有记录,构建一个map, key为announcement_id,value为该announcement对应的metric_id列表。然后遍历这个map,记录符合条件的announcement_id。这个方法一次性查询关系表的所有记录,避免了多次查询数据库。不过如果数据量比较大,仍然会耗费我们的内存资源来存储关系表中的数据。

优化查询的一个思路,就是那能否通过编写sql, 将上述逻辑都放在一次数据库查询中,直接在数据库侧查询出结果返回。mysql中并没有现成的函数可以比较直接的实现判断是否是子集的功能,但是我们可以反向思考。只有Announcement对应的所有Metric都在用户权限列表中,这个Announcement对用户来说才是可见的,那么只要Announcement对应的metric,有一个不在用户的权限列表中,那么这个Announcement对用户就是不可见的。排除所有不可见的,就是我们要得到的Announcement列表了。
方法三(直接通过sql查询)

select * from announcement where id not in ( select announcement_id from announcement_metric where metric_id not in ({permission_list}) 

其中{permission_list}代表用户的权限列表。这里通过两次not in实现等价转化,先是在子查询中查出所有不可见的announcement_id,然后再在announcement全集中将这些不可见排除,就是我们想要的结果。