views:

1056

answers:

5

I have the following code in my index view.

latest_entry_list = Entry.objects.filter(is_published=True).order_by('-date_published')[:10]
for entry in latest_entry_list:
    entry.views = entry.views + 1
    entry.save()

If there are ten (the limit) rows returned from the initial query, will the save issue 10 seperate updated calls to the database, or is Django "smart" enough to issue just one update call?

Is there a more efficient method to achieve this result?

+3  A: 

Revised

You're updating 10 separate, individual, distinct objects.

The 10 separate, individual, distinct updates can't easily be collapsed into one magical update that somehow touches 10 objects.

S.Lott
Sorry, misread your question. Changed my answer completely.
S.Lott
Ah, thanks for that. I'm new to Django and it seams I was confused. You're right there's no magical SQL call that'll do what I'm asking.
Ty
Um. Actually there is a magical SQL call that'll do what you're asking: UPDATE <table> SET views = views+1 WHERE ... Django's ORM can't do that for you yet, though. Coming soon.
Carl Meyer
It seams I'm corrected again: (UPDATE blog_entry SET views = views + 1 WHERE id = 1 OR id = 2 OR ID = 3). Thanks Carl.
Ty
+7  A: 

You could handle the updates in a single transaction, which could improve performance significantly. Use a separate function, decorated with @transaction.commit_manually.

@transaction.commit_manually
def update_latest_entries(latest_entry_list):
    for entry in latest_entry_list:
        entry.views += 1
        entry.save()
    transaction.commit()
Jeff Bauer
I've implemented this, and though I can't notice any speed improvements yet, you tought me somthing new -- So thank you!
Ty
+2  A: 

If you really need the efficiency, at the moment you'd have to drop down into SQL and run the update yourself. That's not worth the added complexity in this case, though.

By Django 1.1 you'll be able to do this in a single SQL call via the ORM using F() objects to reference fields in the update value.

Carl Meyer
+11  A: 

You can use F() objects for this now:

New in Django Development version.
Calls to update can also use F() objects to update one field based on the value of another field in the model. This is especially useful for incrementing counters based upon their current value.

Entry.objects.filter(is_published=True).update(views=F('views')+1)

Although you can't do an update on a sliced query set... edit: actually you can...

This can be done completely in django ORM. You need two SQL queries:

  1. Do your filter and collect a list of primary keys
  2. Do an update on a non-sliced query set of items matching any of those primary keys.

Getting the non-sliced query set is the hard bit. I wondered about using in_bulk but that returns a dictionary, not a query set. One would usually use Q objects to do complex OR type queries and that will work, but pk__in does the job much more simply.

latest_entry_query_set = Entry.objects.filter(is_published=True)\
                                      .order_by('-date_published')[:10]  
latest_entry_ids = (ent.pk for ent in latest_entry_query_set)  #iterator
non_sliced_query_set = Entry.objects.filter(pk__in=latest_entry_ids)  
n = non_sliced_query_set.update(views=F('views')+1)  
print n or 0, 'items updated'

Due to the way that django executes queries lazily, this results in just 2 database hits, no matter how many items are updated.

Tom Viner
Note, these features will be available in the soon-to-be released Django 1.1
Soviut
Yup, and they are already in the current svn trunk which many people use.
Tom Viner
A: 

A performance improvement to the previous entry. This results in one database hit, with a subquery.

latest_entry_query_set = Entry.objects.filter(is_published=True)
                                      .order_by('-date_published')[:10]  
non_sliced_query_set = Entry.objects.filter(pk__in=latest_entry_query_set.values('id'))  
n = non_sliced_query_set.update(views=F('views')+1)  
print n or 0, 'items updated'
Collin Anderson