Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>QuerySets and QuerySet slicing are evaluated <em>lazily</em>. Iterating ids executes the query and makes <code>ids</code> behave like a static list instead of a QuerySet. So when you loop through <code>ids</code>, it causes <code>entries</code> later on to be a fixed set of values; but if you <em>don't</em> loop through <code>ids</code>, then <code>entries</code> is just a subquery with a <code>LIMIT</code> clause added to represent the slicing you do.</p> <p>Here is what is happening in detail:</p> <pre><code>def auto_update_ratings(amounts, assessment_entries_qs, lowest_rating=-1): # assessment_entries_qs is an unevaluated QuerySet # from your calling code, it would probably generate a query like this: # SELECT * FROM assessments ORDER BY RANDOM() start = 0 rating = lowest_rating ids = assessment_entries_qs.values_list('id', flat=True) # ids is a ValueQuerySet that adds "SELECT id" # to the query that assessment_entries_qs would generate. # So ids is now something like: # SELECT id FROM assessments ORDER BY RANDOM() # we omit the loop for amount in amounts: end_mark = start + amount entries = ids[start:end_mark] # entries is now another QuerySet with a LIMIT clause added: # SELECT id FROM assessments ORDER BY RANDOM() LIMIT start,(start+end_mark) # When filter() gets a QuerySet, it adds a subquery a = assessment_entries_qs.filter(id__in=entries).update(rating=rating) # FINALLY, we now actually EXECUTE a query which is something like this: # UPDATE assessments SET rating=? WHERE id IN # (SELECT id FROM assessments ORDER BY RANDOM() LIMIT start,(start+end_mark)) start = end_mark rating += 1 </code></pre> <p>Since the subquery in <code>entries</code> is executed <em>every time you insert</em> and it has a random order, the slicing you do is meaningless! This function does not have deterministic behavior.</p> <p>However when you iterate ids you actually execute the query, so your slicing has deterministic behavior again and the code does what you expect.</p> <p>Let's see what happens when you use a loop instead:</p> <pre><code>ids = assessment_entries_qs.values_list('id', flat=True) # Iterating ids causes the query to actually be executed # This query was sent to the DB: # SELECT id FROM assessments ORDER BY RANDOM() for id in ids: pass # ids has now been "realized" and contains the *results* of the query # e.g., [5,1,2,3,4] # Iterating again (or slicing) will now return values rather than modify the query for amount in amounts: end_mark = start + amount entries = ids[start:end_mark] # because ids was executed, entries contains definite values # When filter() gets actual values, it adds a simple condition a = assessment_entries_qs.filter(id__in=entries).update(rating=rating) # The query executed is something like this: # UPDATE assessments SET rating=? WHERE id IN (5,1) # "(5,1)" will change on each iteration, but it will always be a set of # scalar values rather than a subquery. start = end_mark rating += 1 </code></pre> <p>If you ever need to eagerly evaluate a QuerySet to get all its values at a moment in time, rather than perform a do-nothing iteration just convert it to a list:</p> <pre><code> ids = list(assessment_entries_qs.values_list('id', flat=True)) </code></pre> <p>Also the Django docs go into detail about <a href="https://docs.djangoproject.com/en/dev/ref/models/querysets/#when-querysets-are-evaluated" rel="nofollow">when exactly a <code>QuerySet</code> is evaluated</a>.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload