使用多对多关系进行内部连接类似搜索

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 - 从一个表中选择,同时在另外两个表之间进行内部连接(多对多表和另一个表)

通过联结表进行多对多自连接

Mysql连接查询匹配所有标签的多个“标签”(多对多关系)?

春季 JPA |在多对多关系中搜索

查询放大graphql多对多关系

如何使用 Sunspot 设置具有多对多关系的构面搜索?