使用多对多关系进行内部连接类似搜索
Posted
技术标签:
【中文标题】使用多对多关系进行内部连接类似搜索【英文标题】:Inner join like search using many to many relationship 【发布时间】:2019-02-11 05:59:43 【问题描述】:我无法弄清楚如何使用高级 Q 搜索来检索对象,任何可能完全以错误的方式接近它。
我认为在我尝试描述问题之前完全定义模型会更容易。我要解决的问题比我要在这里给出的示例稍微复杂一些,但那是因为我想更清楚地了解我正在尝试做的事情。
我这里有两个模型:
class Tag(models.Model):
name = models.CharField()
class Container(models.Model):
tags = models.ManyToManyField(Tag)
我能够检索到我想要的标签列表:
valid_tags = [Tag(x) for x in ['a', 'b', 'c', 'd']]
以下是一些容器的示例:
containers = [
Container(tags=[Tag(x) for x in ['a', 'b']),
Container(tags=[Tag(x) for x in ['b']),
Container(tags=[Tag(x) for x in ['a', 'b', 'c']),
Container(tags=[Tag(x) for x in ['e']),
Container(tags=[Tag(x) for x in ['a', 'e']),
]
我想要的查询输出是这样的:
valid_containers = [
Container(tags=[Tag(x) for x in ['a', 'b']),
Container(tags=[Tag(x) for x in ['b']),
Container(tags=[Tag(x) for x in ['a', 'b', 'c']),
]
所以,基本上我要做的是一个“内连接”类型的操作,它将返回所有标签都在 valid_tags 中的容器。
如果我运行以下代码,它会返回以下输出,其中包含标签不满足条件的容器。
Container.objects.filter(tags__in=valid_tags)
[
Container(tags=[Tag(x) for x in ['a', 'b']),
Container(tags=[Tag(x) for x in ['b']),
Container(tags=[Tag(x) for x in ['a', 'b', 'c']),
Container(tags=[Tag(x) for x in ['a', 'e']), # Not wanted
]
我也尝试过使用高级 Q 对象,类似于 .filter(tags__in=valid_tags).exclude(~Q(tags__in=valid_tags)),但是它只返回整个容器列表,这是我不想要的.
我什至不确定我在这里尝试实现的查询在 SQL 中是什么样的,所以我无法编写一个返回正确响应的原始查询,我也想避免编写原始 SQL , 如果可能的话。我不反对,但我希望能够使用 Django 内置的 DB 方法。
另外,不确定这是否相关,但我正在针对 Django 1.11 运行我的代码,并且无法升级到 Django 2;如果我正在寻找的功能有助于使这项工作在 Django 2 中而不是在 Django 1.11 中,我不确定我是否能够升级,所以我可能必须弄清楚 SQL 查询。
编辑:
我发现 annotate 支持 Q 查询。因此,我随后尝试注释 a) 容器中有效的标签或 b) 容器中 not 有效的标签的计数。我希望能够通过执行 .filter(bad_tag_count=0) 过滤掉 (b) 中的任何内容,但我下面的代码似乎只输出对象中的标签数量:
containers \
.annotate(
valid_tags=Count(Q(tags__in=valid_tags))
).annotate(
bad_tags=Count(~Q(tags__in=valid_tags)
)
问题是 .valid_tags 和 .bad_tags 都只是输出容器中包含的标签的数量。至于我在这个查询中做错了什么,我真的很茫然。
【问题讨论】:
***.com/a/11025600/2897115。我不确定,但我认为 .filter(tags__in=valid_tags).exclude(~Q(tags__in=valid_tags)) w.r.t 以上链接可以提供帮助。 我认为这也是可行的,但不幸的是它只是返回,就好像我已经运行了 .all() 一样。 【参考方案1】:事实证明,我意识到我确实是从错误的角度解决问题。我的目标是找到所有标签满足有效标签条件的容器。
解决方法很简单:
non_valid_tags = Tag.objects.exclude(id__in=valid_tags) # Apparently Django knows that if the query passed in is based on the object here, it will use the same field that is being queried so I don't need to do valid_tags.values_list('id', flat=True), although that might be a point to consider if the query ends up running for too long
valid_containers = Containers.objects.exclude(tags__in=non_valid_tags) # rows corresponding to [0, 1, 2]
虽然这不是我需要解决的全部问题,但这为我提供了充实查询的起点。
【讨论】:
以上是关于使用多对多关系进行内部连接类似搜索的主要内容,如果未能解决你的问题,请参考以下文章
SQL - 从一个表中选择,同时在另外两个表之间进行内部连接(多对多表和另一个表)