Daniel BorzęckiBlogGitHub

Django querysets

06 June, 2016 - 2 min read

Comparing queryset

Using Django ORM can lead to interesting problems - especially regarding logic operators.

Introduction

Let's introduce a problem by writing a simple query to fetch all active Users.

> User.objects.filter(is_acitve=True)
[<User: eggs@test.com>, <User: bacon@test.com>, <User: spam@test.com>]

Naturally you can test if it's empty using Querysets exists method, but what would happen if you try to compare it to something else. Let's say empty list?

> User.objects.filter(is_acitve=True) == []
False

What if we tried to take a look at inactive users and try the same comparison.

> User.objects.filter(is_acitve=False)
[]
> User.objects.filter(is_acitve=False) == []
False

This is of course "normal" behavior since types don't match right?

> User.objects.all() == User.objects.all()
False

The result may seem unexpected or even misleading but it actually works in favor of Django ORM - because QuerySet class represents only lazy database lookup for a set of objects.

What happens when you have to compare two querysets for identity when it doesn't give proper results for most obvious examples? Here's couple ideas how to handle this problem.

Solutions

Use Counter

Dict subclass for counting hashable items. Sometimes called a bag or multiset. Elements are stored as dictionary key and their counts are stored as dictionary values.

from collections import Counter
Counter(queryset_a) == Counter(queryset_b)

Use simple set comparison

Very basic solution is to simply cast querysets to set to perform comparison.

set(queryset_a) == set(queryset_b)

Important remark: don't cast to list because then querysets can have different orders or may contain duplicates which will skew the results.