使用https://docs.djangoproject.com/en/dev/topics/db/queries/#making-queries中的模型,并进行少量修改:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
class Author(models.Model):
name = models.CharField(max_length=200)
joined = models.DateField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
authors = models.ManyToManyField(Author)
rating = models.IntegerField()
我想创建一个从Author到Entries的词典,作者今年加入了该词典,并且Entry的评分为4或更高。结果字典的结构应如下所示:
author_entries = {author1: [set of entries], author2: [set of entries], etc.}
在访问数据库的时间少于3分钟(或至少与作者或条目的数量不成比例)时。
我的第一次尝试(db hits ==作者数量,100作者100 db-hits):
res = {}
authors = Author.objects.filter(joined__year=date.today().year)
for author in authors:
res[author] = set(author.entry_set.filter(rating__gte=4))
第二次尝试,尝试一次读取条目:
res = {}
authors = Author.objects.filter(joined__year=date.today().year)
entries = Entry.objects.select_related().filter(rating__gte=4, authors__in=authors)
for author in authors:
res[author] = {e for e in entries if e.authors.filter(pk=author.pk)}
这甚至更糟,有100位作者,198次数据库命中(最初的第二次尝试使用了{e for e in entries if author in e.authors}
,但是Django却没有。
我发现的唯一方法涉及原始SQL(4个db-hits):
res = {}
_authors = Author.objects.filter(joined__year=date.today().year)
_entries = Entry.objects.select_related().filter(rating__gte=4, authors__in=_authors)
authors = {a.id: a for a in _authors}
entries = {e.id: e for e in _entries}
c = connection.cursor()
c.execute("""
select entry_id, author_id
from sampleapp_entry_authors
where author_id in (%s)
""" % ','.join(str(v) for v in authors.keys()))
res = {a: set() for a in _authors}
for eid, aid in c.fetchall():
if eid in entries:
res[authors[aid]].add(entries[eid])
(在c.execute(..)
调用中使用字符串替换的道歉–我找不到where in ?
调用所需的sqlite语法)。
还有更多类似Django的方式吗?
我用我正在使用的代码(https://github.com/thebjorn/revm2m)创建了一个git repo,测试在https://github.com/thebjorn/revm2m/blob/master/revm2m/sampleapp/tests.py
中答案 0 :(得分:3)
您可以使用Prefetch
-object [Django-doc]:
from django.db.models import Prefetch
good_ratings = Prefetch(
'entry_set',
queryset=Entry.objects.filter(rating__gte=4),
to_attr='good_ratings'
)
authors = Author.objects.filter(
joined__year=date.today().year
).prefetch_related(
good_ratings
)
现在Author
中的authors
对象将具有一个预先加载的额外属性good_ratings
(to_attr
对象的Prefetch
的值) QuerySet
包含评级大于或等于4的Entry
。
因此您可以像这样对它们进行后处理:
res = {
author: set(author.good_ratings)
for author in authors
}
尽管由于Author
个对象(不是从这个QuerySet
开始的对象)已经带有属性,所以反正可能没有太多用途。