Imagine I have the following model:
class Person(models.Model):
...other stuff...
optional_first_name= models.CharField(max_length=50, blank=True)
How would I go about writing a request that returns an array of the most popular names, in decreasing order of occurence, with their counts, while ignoring the empty names?
i.e. for a database with 13 Leslies, 8 Andys, 3 Aprils, 1 Ron and 18 people who haven't specified their name, the output would be:
[('leslie', 13), ('andy', 8), ('april', 3), ('ron', 1)]
The closest I can get is by doing the following:
q= Person.objects.all()
q.query.group_by=['optional_first_name']
q.query.add_count_column()
q.values_list('optional_first_name', flat= True)
But it's still not quite what I want.
After some digging, finally found out:
Person.objects.values('optional_first_name').annotate(c=Count('optional_first_name')).order_by('-c')
Related
models.py
class Orders(models.Model):
orderid = models.IntegerField(db_column='orderID', primary_key=True)
pickupdate = models.DateField(db_column='pickupDate', blank=True, null=True)
returndate = models.DateField(db_column='returnDate', blank=True, null=True)
I would like to substract the returndate with pickupdate (returndate - pickupdate) for each rows, and then add them together for the whole table
I have tried the code below, but it produced large decimal numbers.
Orders.objects.aggregate(days=Sum( F('returndate') - F('pickupdate') )
I also have tried the code below,but it produced error
Orders.objects.aggregate(days=Sum( 'returndate' - 'pickupdate' )
Do you mind duplicative days in your count? If not, you could use a range to get the total number of days between the pickupdate and the returndate for each row ...
days = 0
for rec in Orders.objects.all():
for n in range(int((rec.returndate - rec.pickupdate).days)+1):
days += 1
+1 or not depending on whether you want inclusive counts (include start/end day) or exclusive counts (do not include end day).
You can try
Orders.objects.annotate(diff=F('returndate') - F('pickupdate')).aggregate(days=Sum('diff'))
I want to join the sum of related values from users with the users that do not have those values.
Here's a simplified version of my model structure:
class Answer(models.Model):
person = models.ForeignKey(Person)
points = models.PositiveIntegerField(default=100)
correct = models.BooleanField(default=False)
class Person(models.Model):
# irrelevant model fields
Sample dataset:
Person | Answer.Points
------ | ------
3 | 50
3 | 100
2 | 100
2 | 90
Person 4 has no answers and therefore, points
With the query below, I can achieve the sum of points for each person:
people_with_points = Person.objects.\
filter(answer__correct=True).\
annotate(points=Sum('answer__points')).\
values('pk', 'points')
<QuerySet [{'pk': 2, 'points': 190}, {'pk': 3, 'points': 150}]>
But, since some people might not have any related Answer entries, they will have 0 points and with the query below I use Coalesce to "fake" their points, like so:
people_without_points = Person.objects.\
exclude(pk__in=people_with_points.values_list('pk')).\
annotate(points=Coalesce(Sum('answer__points'), 0)).\
values('pk', 'points')
<QuerySet [{'pk': 4, 'points': 0}]>
Both of these work as intended but I want to have them in the same queryset so I use the union operator | to join them:
everyone = people_with_points | people_without_points
Now, for the problem:
After this, the people without points have their points value turned into None instead of 0.
<QuerySet [{'pk': 2, 'points': 190}, {'pk': 3, 'points': 150}, {'pk': 4, 'points': None}]>
Anyone has any idea of why this happens?
Thanks!
I should mention that I can fix that by annotating the queryset again and coalescing the null values to 0, like this:
everyone.\
annotate(real_points=Concat(Coalesce(F('points'), 0), Value(''))).\
values('pk', 'real_points')
<QuerySet [{'pk': 2, 'real_points': 190}, {'pk': 3, 'real_points': 150}, {'pk': 4, 'real_points': 0}]>
But I wish to understand why the union does not work as I expected in my original question.
EDIT:
I think I got it. A friend instructed me to use django-debug-toolbar to check my SQL queries to investigate further on this situation and I found out the following:
Since it's a union of two queries, the second query annotation is somehow not considered and the COALESCE to 0 is not used. By moving that to the first query it is propagated to the second query and I could achieve the expected result.
Basically, I changed the following:
# Moved the "Coalesce" to the initial query
people_with_points = Person.objects.\
filter(answer__correct=True).\
annotate(points=Coalesce(Sum('answer__points'), 0)).\
values('pk', 'points')
# Second query does not have it anymore
people_without_points = Person.objects.\
exclude(pk__in=people_with_points.values_list('pk')).\
values('pk', 'points')
# We will have the values with 0 here!
everyone = people_with_points | people_without_points
I know this is probably a really simple question, but I've been trying to solve it for ages now and keep failing!
I have two models:
class Subject(models.Model):
name = models.CharField(max_length=200)
class Pupil(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
active = models.BooleanField(default=0, db_index=True)
takes_subject = models.ManyToManyField(Subject)
Each pupil can take many subjects. Finding out how many pupils take each subject is easy,
but I want to find out how many pupils take multiple subjects. Something like:
Subjects taken | Number of pupils
===============|==================
4 | 20
3 | 15
2 | 7
1 | 38
That way I can know that say 15 pupils are taking 3 subjects while 38 pupils are taking 1 subject.
How do I achieve this?
Thanks in advance,
Alex
from collections import Counter
Counter(Pupil.objects.annotate(
count=Count('takes_subject')).values_list('count', flat=True))
That gets a list from Pupil counting how many subjects each student takes, e.g. [4, 5, 4, 4, 6,...].
Then let Counter() count how often each number occurs, and return a dict(), e.g. {4: 3, 5: 1, 6: 1, ...}.
I asked this SO question yesterday.
The code I now have to display the choices list in my models.py is:
YOB_TYPES = Choices(*(
((0, 'select_yob', _(' Select Year of Birth')),
(2000, 'to_present', _('2000 to Present'))) +
tuple((i, str(i)) for i in xrange(1990, 2000)) +
((1, 'unspecified', _('Prefer not to answer')),))
)
....
year_of_birth_type = models.PositiveIntegerField(choices=YOB_TYPES, default=YOB_TYPES.select_yob, validators=[MinValueValidator(1)])
....
The choices list is now displayed with the year of birth running from 1990 to 1999 (ascending order) as shown below:
How do I change the code so that the year of birth dates are displayed 1999 to 1990 (decending order) as shown below:
I have searched but cannot locate anything related to my issue - reversing ( .reverse() ) the tuple output - maybe I am searching the wrong topic.
See this line:
tuple((i, str(i)) for i in xrange(1990, 2000)) +
Adjust like so:
tuple((i, str(i)) for i in xrange(1999, 1989, -1) +
The third argument specifies your "step", in this case -1 (to go in reverse). Remember that with xrange, the second parameter is not included in the iteration, so use 1989 rather than 1990 (the same reason why you used 2000 earlier, rather than 1999).
Django documents give this example of associating extra data with a M2M relationship. Although that is straight forward, now that I am trying to make use of the extra data in my views it is feeling very clumsy (which typically means "I'm doing it wrong").
For example, using the models defined in the linked document above I can do the following:
# Some people
ringo = Person.objects.create(name="Ringo Starr")
paul = Person.objects.create(name="Paul McCartney")
me = Person.objects.create(name="Me the rock Star")
# Some bands
beatles = Group.objects.create(name="The Beatles")
my_band = Group.objects.create(name="My Imaginary band")
# The Beatles form
m1 = Membership.objects.create(person=ringo, group=beatles,
date_joined=date(1962, 8, 16),
invite_reason= "Needed a new drummer.")
m2 = Membership.objects.create(person=paul, group=beatles,
date_joined=date(1960, 8, 1),
invite_reason= "Wanted to form a band.")
# My Imaginary band forms
m3 = Membership.objects.create(person=me, group=my_band,
date_joined=date(1980, 10, 5),
invite_reason= "Want to be a star.")
m4 = Membership.objects.create(person=paul, group=my_band,
date_joined=date(1980, 10, 5),
invite_reason= "Wanted to form a better band.")
Now if I want to print a simple table that for each person gives the date that they joined each band, at the moment I am doing this:
bands = Group.objects.all().order_by('name')
for person in Person.objects.all():
print person.name,
for band in bands:
print band.name,
try:
m = person.membership_set.get(group=band.pk)
print m.date_joined,
except:
print 'NA',
print ""
Which feels very ugly, especially the "m = person.membership_set.get(group=band.pk)" bit. Am I going about this whole thing wrong?
Now say I wanted to order the people by the date that they joined a particular band (say the beatles) is there any order_by clause I can put on Person.objects.all() that would let me do that?
Any advice would be greatly appreciated.
You should query the Membership model instead:
members = Membership.objects.select_related('person', 'group').all().order_by('date_joined')
for m in members:
print m.band.name, m.person.name, m.date_joined
Using select_related here we avoid the 1 + n queries problem, as it tells the ORM to do the join and selects everything in one single query.