Django:检索不同的QuerySet

时间:2011-11-22 20:54:58

标签: mysql django django-queryset

我的应用程序中有以下型号。 Addition模型用于管理Book模型和Collection模型之间的多对多关系,因为我需要在中间模型上包含额外的字段。

class Book(models.Model):
    name = models.CharField(max_length=200)
    picture = models.ImageField(upload_to='img', max_length=1000)
    price = models.DecimalField(max_digits=8, decimal_places=2)

class Collection(models.Model):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=100)
    books = models.ManyToManyField(Book, through='Addition')
    subscribers = models.ManyToManyField(User, related_name='collection_subscriptions', blank=True, null=True)

class Addition(models.Model):
    user = models.ForeignKey(User)
    book = models.ForeignKey(Book)
    collection = models.ForeignKey(Collection)
    created = models.DateTimeField(auto_now=False, auto_now_add=True)
    updated = models.DateTimeField(auto_now=True, auto_now_add=True)

在我的应用中,用户可以将书籍添加到他们创建的馆藏中(例如小说,历史等)。然后其他用户可以关注他们喜欢的那些集合。

当用户登录该网站时,我想显示最近添加到他们所关注的馆藏中的所有书籍。每本书,我还要显示添加它的人的姓名,以及它所在的集合的名称。

我可以获得以下所有添加内容......

additions = Addition.objects.filter(collection__subscribers=user).select_related()

...但这会导致重复的图书被检索并显示给用户,通常是并排的。

如果有办法检索用户正在关注的馆藏中的不同书籍列表?

我正在使用Django 1.3 + MySQL。

感谢。

更新

我应该补充一点,总的来说,由于几个原因,我不是在寻找任何'循环结果并重复删除'的解决方案。

可能会有数十甚至数十万个添加内容(我也会在列出用户添加的所有新增内容的页面上显示此信息),并且响应时间非常重要。

在限制初始结果集时,此解决方案可能会变得更加实用,但它会产生分页问题,​​这也是必需的。即,如何对整个结果集进行分页,同时仅对该集合的一小部分进行重复数据删除。我愿意接受可以解决这个问题的任何想法。

更新

我还应该提一下,如果同一本书被多个用户添加,我实际上并没有优先选择使用哪种添加,原始或最新添加都可以正常工作。

4 个答案:

答案 0 :(得分:0)

以下内容如何 - 它不是一个纯粹的SQL解决方案,而且它会花费额外的数据库查询和一些循环时间,但它应该仍然可以执行,并且它会让您更好地控制哪些添加优先于其他人:

def filter_additions(additions):
    # Use a ValuesQuerySet for performance
    additions_values = additions.values()

    # The following code just eliminates duplicates. You could do 
    # something much more powerful/interesting here if you like,
    # e.g. give preference to additions by a user`s friends 

    book_pk_registry = {}
    excluded_addition_pks = []

    for addition in additions_values:
        addition_pk = addition['id']
        book_pk = addition['book_id']
        if book_pk not in book_pk_registry:
            book_pk_registry[book_pk] = True
        else:
            excluded_addition_pks.append(addition_pk)

    additions = additions.exclude(pk__in=excluded_addition_pks)


additions = Addition.objects.filter(collection__subscribers=user)
additions = filter_additions(additions)

如果涉及的书籍可能超过一千本,您可能希望对初始添加查询设置限制。在排除中传递大量的ID列表并不是一个好主意。使用'values()'非常重要,因为Python可以循环遍历一个基本的dicts列表,比查询集快得多,而且它使用的内存要少得多。

答案 1 :(得分:0)

假设没有大量的新增功能可供展示,这很容易实现:

# duplicated..
additions = Addition.objects.filter(collection__subscribers=user, created__gt=DATE_LAST_LOGIN).select_related()

# remove duplication
added_books = {}
for addition in additions:
    added_books[addition.book] = True
added_books = added_books.keys()

根据您对问题的描述,性能不会成为问题。

答案 2 :(得分:0)

additions = Addition.objects.filter(collection__subscribers=user).values('book').annotate(user=Min('user'), collection=Min('collection')).order_by()

此查询将为您提供包含其用户和集合的唯一书籍列表。书籍,馆藏,用户将是pk,而不是对象。但我希望你将它们存储在缓存中,这样就不会有问题了。

但是对于你的工作量,我会考虑非规范化。我的查询非常很重,如果您经常添加,则不容易缓存其结果。我的第一种方法是将latest_additions字段添加到Collection模型并使用信号进行更新(不添加重复项)。此字段的格式取决于您。

答案 3 :(得分:0)

有时可以放入SQL,尤其是当仅使用ORM的解决方案不具备性能时。在SQL中很容易获得非重复的Addition行ID,然后您可以切换回ORM来选择数据。这是两个查询,但是会胜过我目前看到的任何单一查询解决方案。

from django.db import connection
from operator import itemgetter
cursor = connection.cursor()

# Select non-duplicate book additions, preferring for most recently updated
query = '''SELECT id, MAX(updated) FROM %s
    GROUP BY book_id''' % Addition._meta.db_table
cursor.execute(query)

# Flatten the results to an id list
addition_ids = map(itemgetter(0), cursor.fetchall())

additions = Addition.objects.filter(
    collection__subscribers=user, id__in=addition_ids).select_related()