8

I have a django application with the following model:

Object A is a simple object extending from Model with a few fields, and let's say, a particular one is a char field called "NAME" and an Integer field called "ORDER". A is abstract, meaning there are no A objects in the database, but instead...

Objects B and C are specializations of A, meaning they inherit from A and they add some other fields.

Now suppose I need all the objects whose field NAME start with the letter "Z", ordered by the ORDER field, but I want all the B and C-specific fields too for those objects. Now I see 2 approaches:

a) Do the queries individually for B and C objects and fetch two lists, merge them, order manually and work with that.

b) Query A objects for names starting with "Z" ordered by "ORDER" and with the result query the B and C objects to bring all the remaining data.

Both approaches sound highly inefficient, in the first one I have to order them myself, in the second one I have to query the database multiple times.

Is there a magical way I'm missing to fetch all B and C objects, ordered in one single method? Or at least a more efficient way to do this than the both mentioned?

Thanks in Advance!

Bruno

Bruno
  • 479
  • 1
  • 8
  • 15

4 Answers4

6

If A can be concrete, you can do this all in one query using select_related.

from django.db import connection
q = A.objects.filter(NAME__istartswith='z').order_by('ORDER').select_related('b', 'c')
for obj in q:
   obj = obj.b or obj.c or obj
   print repr(obj), obj.__dict__ # (to prove the subclass-specific attributes exist)
print "query count:", len(connection.queries)
DrMeers
  • 4,117
  • 2
  • 36
  • 38
  • 2
    Clever. Horrible, but clever. Won't work when A is abstract though? – Wogan Mar 03 '11 at 07:39
  • Correct, concrete multi-table inheritance only. – DrMeers Mar 03 '11 at 07:41
  • I agree with Wogan, conceptually it's quite ugly, but it works like a charm providing we can make A concrete, which is not such a terrible thing to do in my case! Very Clever! – Bruno Mar 03 '11 at 17:07
1

This question was answered here.

Use the InheritanceManager from the django-model-utils project.

Community
  • 1
  • 1
Philipp Zedler
  • 1,660
  • 1
  • 17
  • 36
0

Querying using your "b" method, will allow for you to "bring in" all the remaining data without querying your B and C models separately. You can use the "dot lowercase model name" relation.

http://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance

for object in A.objects.filter(NAME__istartswith='z').order_by('ORDER'):
    if object.b:
        // do something
        pass
    elif object.c:
        // do something
        pass

You may need to try and except DoesNotExist exceptions. I'm a bit rusty with my django. Good Luck.

dting
  • 38,604
  • 10
  • 95
  • 114
  • Yes, you would need to catch `DoesNotExist`, and each attempt to access a related object (successful or not) will cost you an additional query -- terribly inefficient. – DrMeers Mar 03 '11 at 07:03
0

So long as you order both queries on B and C, it is fairly easy to merge them without having to do an expensive resort:

# first define a couple of helper functions 

def next_or(iterable, other):
    try:
        return iterable.next(), None
    except StopIteration:
        return None, other

def merge(x,y,func=lambda a,b: a<=b):
    ''' merges a pair of sorted iterables '''
    xs = iter(x)
    ys = iter(y)
    a,r = next_or(xs,ys)
    b,r = next_or(ys,xs)
    while r is None:
        if func(a,b):
            yield a
            a,r = next_or(xs,ys)
        else:
            yield b
            b,r = next_or(ys,xs)
    else:
        if a is not None:
            yield a
        else:
            yield b
    for o in r:
        yield o

# now get your objects & then merge them

b_qs = B.objects.filter(NAME__startswith='Z').order_by('ORDER')
c_qs = C.objects.filter(NAME__startswith='Z').order_by('ORDER')

for obj in merge(b_qs,c_qs,lambda a,b: a.ORDER <= b.ORDER):
    print repr(obj), obj.__dict__

The advantage of this technique is it works with an abstract base class.

Wogan
  • 70,277
  • 5
  • 35
  • 35