Django中的并集和交集
class Tag(models.Model):
name = models.CharField(maxlength=100)
class Blog(models.Model):
name = models.CharField(maxlength=100)
tags = models.ManyToManyField(Tag)
这里有一些简单的模型,主要是为了问我的问题。
我想知道如何用标签以两种不同的方式查询博客。
- 查询那些被标记为“tag1”或“tag2”的博客文章:
Blog.objects.filter(tags_in=[1,2]).distinct()
- 查询那些同时被标记为“tag1”和“tag2”的博客对象:?
- 查询那些只被标记为“tag1”和“tag2”,而没有其他标签的博客对象:??
这里的标签和博客只是用作示例。
4 个回答
10
请不要重复造轮子,直接使用django-tagging这个应用,它正好适合你的需求。它可以完成你提到的所有查询,甚至更多功能。
如果你需要在你的标签模型中添加自定义字段,可以看看我修改过的django-tagging版本。
17
我用Django 1.0测试过这些内容:
首先是“或”查询:
Blog.objects.filter(tags__name__in=['tag1', 'tag2']).distinct()
你也可以使用Q类来实现:
Blog.objects.filter(Q(tags__name='tag1') | Q(tags__name='tag2')).distinct()
接下来是“和”查询:
Blog.objects.filter(tags__name='tag1').filter(tags__name='tag2')
至于第三种情况,我不太确定,你可能需要直接使用SQL来处理。
23
你可以使用Q对象来解决第一个问题:
# Blogs who have either hockey or django tags.
from django.db.models import Q
Blog.objects.filter(
Q(tags__name__iexact='hockey') | Q(tags__name__iexact='django')
)
关于并集和交集,我觉得这些功能有点超出了Django ORM的范围,但其实是可以实现的。下面的例子来自一个叫做django-tagging的Django应用,它提供了这些功能。你可以查看models.py文件的第346行:
对于第二部分,你其实是在寻找两个查询的并集。
def get_union_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
model associated with *any* of the given list of tags.
"""
tags = get_tag_list(tags)
tag_count = len(tags)
queryset, model = get_queryset_and_model(queryset_or_model)
if not tag_count:
return model._default_manager.none()
model_table = qn(model._meta.db_table)
# This query selects the ids of all objects which have any of
# the given tags.
query = """
SELECT %(model_pk)s
FROM %(model)s, %(tagged_item)s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
AND %(model_pk)s = %(tagged_item)s.object_id
GROUP BY %(model_pk)s""" % {
'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
'model': model_table,
'tagged_item': qn(self.model._meta.db_table),
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
}
cursor = connection.cursor()
cursor.execute(query, [tag.pk for tag in tags])
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
return queryset.filter(pk__in=object_ids)
else:
return model._default_manager.none()
至于第三部分,我认为你是在寻找交集。可以参考models.py文件的第307行。
def get_intersection_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
model associated with *all* of the given list of tags.
"""
tags = get_tag_list(tags)
tag_count = len(tags)
queryset, model = get_queryset_and_model(queryset_or_model)
if not tag_count:
return model._default_manager.none()
model_table = qn(model._meta.db_table)
# This query selects the ids of all objects which have all the
# given tags.
query = """
SELECT %(model_pk)s
FROM %(model)s, %(tagged_item)s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
AND %(model_pk)s = %(tagged_item)s.object_id
GROUP BY %(model_pk)s
HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
'model': model_table,
'tagged_item': qn(self.model._meta.db_table),
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
'tag_count': tag_count,
}
cursor = connection.cursor()
cursor.execute(query, [tag.pk for tag in tags])
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
return queryset.filter(pk__in=object_ids)
else:
return model._default_manager.none()