Фильтрация агрегата в Django ORM

Можно также протестировать, если объект имеет определенную переменную с:

>>> hi_obj = hi()
>>> hasattr(hi_obj, "some attribute")
9
задан Jason Christa 21 December 2009 в 19:04
поделиться

5 ответов

OK, now that the question includes the model definitions, I submit to you that this should work, unless your version of Django doesn't support some feature I use here (in which case, please let me know!):

Post.objects.filter(thread__in=thread_set, status='active').aggregate(num_posts=Count('id'))

Django allows __in filters to take a QuerySet to decide what the IN clause should look like in SQL, so if you pass thread__in=thread_set, Django will filter the posts so that only those whose thread field points to one of the ids of the threads in your thread_set remain for the aggregate call to see.

This should filter the posts with just one db query with something like WHERE thread_id IN ... inside, rather than with one query per thread, which would indeed be horrid. If anything else happened, this would be a bug in Django...

The result should be at most two queries to establish a Category's postcount -- one to obtain thread_set and another one actually to count the posts. The alternative is to have a thread/post join to be filtered based on Thread's category field and Post's status field, which I wouldn't necessarily expect to be that much faster. (I say 'at most', because I guess they could be fused automatically... Though I don't think this would happen with current Django. Can't check ATM, sorry.)

EDIT: Django's QuerySet API reference says this on __in filters:


IN

In a given list.

Example:

Entry.objects.filter(id__in=[1, 3, 4])

SQL equivalent:

SELECT ... WHERE id IN (1, 3, 4);

You can also use a queryset to dynamically evaluate the list of values instead of providing a list of literal values:

inner_qs = Blog.objects.filter(name__contains='Cheddar')
entries = Entry.objects.filter(blog__in=inner_qs)

This queryset will be evaluated as subselect statement:

SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')

The above code fragment could also be written as follows:

inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
entries = Entry.objects.filter(blog__in=inner_q)

Changed in Django 1.1: In Django 1.0, only the latter piece of code is valid.

This second form is a bit less readable and unnatural to write, since it accesses the internal query attribute and requires a ValuesQuerySet. If your code doesn't require compatibility with Django 1.0, use the first form, passing in a queryset directly.


So, I guess Django is capable of passing a single query to the db in the case at issue here. If the db's query analyser does a good job, the effect might be very nearly optimal. :-)

10
ответ дан 4 December 2019 в 21:10
поделиться

Я искал нечто подобное и не нашел отличного решения. Я использую что-то вроде этого:

def post_count(self):
        return len(Post.objects.filter(someModel = self).filter(active_status = 1))

Это не очень хорошо, но я не думаю, что Django позволяет фильтровать на основе агрегатов и аннотаций вторичной модели. Я буду проверять, найдет ли кто-нибудь лучшее решение.

0
ответ дан 4 December 2019 в 21:10
поделиться

Да. Просто сделай это. Это должно работать, как ожидалось:

self.thread_set.filter(active_status=1).aggregate(num_posts=Count('post'))['num_posts']

Любой исходный запрос возвращает QuerySet , поэтому любые доступные методы, возвращающие QuerySets , могут быть практически неограниченно связаны вместе для сложных совпадений критериев. Поскольку aggregate () не возвращает QuerySet , вы хотите, чтобы он был последним в цепочке.

0
ответ дан 4 December 2019 в 21:10
поделиться

You may want to look at writing a custom Manager object:

http://docs.djangoproject.com/en/1.1/topics/db/managers/

I haven't used aggregate(), but that may let you write a custom manager to provide a filtered active_thread_set and then do self.active_thread_set.aggregate(...). If not, it will let you do the custom SQL and add a num_posts property onto the Thread objects (see the PollManager.with_counts() example.)

0
ответ дан 4 December 2019 в 21:10
поделиться

Можно ли немного изменить ситуацию?

Как показано ниже, вы можете добавить свойство post_count в класс Thread, который считает активные сообщения в Thread.

Это свойство post_count затем может быть использовано для вычисления активных сообщений в категории, суммируя все активные сообщения во всех потоках в категории.

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100, blank=True, primary_key=True)
    ordering = models.IntegerField(max_length=3, default=0)

    @property
    def thread_count(self):
        return self.thread_set.all().count()

    @property
    def post_count(self): # <-- Changed
        return reduce(lambda x,y: x + y, [x.post_count for x in self.thread_set.all()])

class Thread(models.Model):
    user = models.ForeignKey(User)
    category = models.ForeignKey(Category)
    title = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100)
    content = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    latest_activity = models.DateTimeField(auto_now_add=True)

    @property
    def post_count(self): # <---- Newly added
        return self.post_set.filter(status = 'ACTIVE').count()

class Post(models.Model):
    thread = models.ForeignKey(Thread)
    parent = models.ForeignKey('Post', null=True, blank=True)
    display_name = models.CharField(max_length=100)
    email = models.EmailField(db_index=True)
    ip_address = models.IPAddressField(null=True, blank=True)
    content = models.TextField()
    status = models.CharField(choices=STATUS_CHOICES, max_length=25, db_index=True, default='approved')
    created = models.DateTimeField()
0
ответ дан 4 December 2019 в 21:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: