I am trying to design a solution to Extentions of the Django docs' pizza example (screen for pizzas by topping) and have been working with Attempt 1.
So far my solution only works on two or three screening criteria before Django barfs and throws a DatabaseError: too many SQL variables. It is my current understanding that this is caused by a database limitation (in this case sqlite3). I am also of the impression that this error is a marker for poor code versus inadequate database configuration. But I simply don't know enough about SQL or QuerySet mechanics to know what actions a programmer can take to cause/avoid this error.
I now have the following questions:
1. What is a SQL variable in Django?
2. How do these SQL variables accumulate in Django?
3. How do these SQL variables get 'flushed' in Django?
4. What am I doing/not doing in the the Query Code section below such that this error is expected to be thrown?
models.py
from django.db import models
class Topping(models.Model):
name = models.CharField(max_length=128, primary_key=True)
units = models.CharField(max_length=4) # e.g. 'g' | 'kg' | 'lb' | 'oz' ...
# On Python 3: def __str__(self):
def __unicode__(self):
return self.name
class ToppingAmount(models.Model):
pizzaID = models.CharField(max_length=128)
topping = models.ForeignKey(Topping)
amount = models.FloatField()
# On Python 3: def __str__(self):
def __unicode__(self):
return self.topping.name
class Pizza(models.Model):
name = models.CharField(max_length=128, primary_key=True)
toppings = models.ManyToManyField(ToppingAmount)
# On Python 3: def __str__(self):
def __unicode__(self):
return self.name
Query Code
def screen_query(lte, gte):
topping_amounts = ToppingAmount.objects.all()
for c in lte:
bad = topping_amounts.filter(topping__pk=c[0]).filter(amount__gt=c[1])
for b in bad:
topping_amounts = topping_amounts.exclude(pizzaID=b.pizzaID)
for c in gte:
topping_amountsg = ToppingAmount.objects.none()
prima_good = topping_amounts.filter(topping__name=c[0]).filter(amount__gte=c[1])
for g in prima_good:
topping_amountsg = topping_amountsg | topping_amounts.filter(pizzaID=g.pizzaID)
topping_amounts = topping_amountsg
pizzas = Pizza.objects.none()
for ta in topping_amounts:
pizzas = pizzas | Pizza.objects.filter(pk=ta.pizzaID)
return pizzas
Explanation of Code:
Constraints are of the form "amount , ">= | <=", " value" and are placed in a either a gte or lte list. As an example, the following lists indicate that we will screen the database for all Pizza objects such that they have:
- not more than 30.0 of "Pepperoni"
- not more than 63.813 of "Pineapple"
- at least 10.0 of "Bell Peppers"
- at least 55 of "Ham"
- at least 10.0 of "Bacon"
- at least 55 of "Chicken"
lte = [["Pepperoni", 30.0], ["Pineapple", 63.813]]
gte = [["Bell Peppers", 10.0], ["Ham", 55], ["Bacon", 10.0], ["Chicken", 55]]
We begin our query by considering all pizzas (via ToppingAmount) as candidates:
topping_amounts = ToppingAmount.objects.all()
Next, identify and exclude all Pizzas in contravention of the "amount , "<=", " value" constraint (via topping_amounts):
for c in lte:
bad = topping_amounts.filter(topping__pk=c[0]).filter(amount__gt=c[1])
for b in bad:
topping_amounts = topping_amounts.exclude(pizzaID=b.pizzaID)
Now that we have identified all topping_amounts objects which comply with the constraints prescribed by lte
, we can pick through those for the ones that also comply with our gte
constraints. This is accomplished by creating an empty QuerySet topping_amountsg = ToppingAmount.objects.none()
and filling it with allowable objects.
for c in gte:
topping_amountsg = ToppingAmount.objects.none()
prima_good = topping_amounts.filter(topping__name=c[0]).filter(amount__gte=c[1])
Our prima facie feasible ToppingAmount objects are now in prima_good
. Recall however that a pizza has many toppings, and so we must collect all ToppingAmount objects associated with each prima facie feasible pizza.
for g in prima_good:
topping_amountsg = topping_amountsg | topping_amounts.filter(pizzaID=g.pizzaID)
Set the topping_amounts
QuerySet equal to the pirma facie feasible ToppingAmount objects and filter against the next constraint in gte
.
topping_amounts = topping_amountsg
Once all gte
constraints have been inflicted on topping_amounts
, we will have our feasible QuerySet.
Extentions of the Django docs' pizza example (screen for pizzas by topping) is concerned with obtaining the QuerySet of feasible Pizza objects, so we extract those from our feasible ToppingAmount objects in topping_amounts
.
pizzas = Pizza.objects.none()
for ta in topping_amounts:
pizzas = pizzas | Pizza.objects.filter(pk=ta.pizzaID)
Putting all of the pieces together in a screening-query function:
def screen_query(lte, gte):
topping_amounts = ToppingAmount.objects.all()
for c in lte:
bad = topping_amounts.filter(topping__pk=c[0]).filter(amount__gt=c[1])
for b in bad:
topping_amounts = topping_amounts.exclude(pizzaID=b.pizzaID)
for c in gte:
topping_amountsg = ToppingAmount.objects.none()
prima_good = topping_amounts.filter(topping__name=c[0]).filter(amount__gte=c[1])
for g in prima_good:
topping_amountsg = topping_amountsg | topping_amounts.filter(pizzaID=g.pizzaID)
topping_amounts = topping_amountsg
pizzas = Pizza.objects.none()
for ta in topping_amounts:
pizzas = pizzas | Pizza.objects.filter(pk=ta.pizzaID)
return pizzas
If you run this code in the shell with
lte = [["Pepperoni", 30.0], ["Pineapple", 63.813]]
gte = [["Bell Peppers", 10.0], ["Ham", 55], ["Bacon", 10.0], ["Chicken", 55]]
you will get
File "/usr/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py", line 344, in execute return Database.Cursor.execute(self, query, params) DatabaseError: too many SQL variables