Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ORM and Closure Tables

I'm trying to model data that's organised as a hierarchical tree using a closure table. The entries that will represent nodes in the tree are nothing fancy and are defined as follows.

class Region(models.Model):
    RegionGuid = models.CharField(max_length=40, unique=True, db_column='RegionGUID', blank=True)
    CustomerId = models.IntegerField(null=True, db_column='CustomerID', blank=True)
    RegionName = models.CharField(max_length=256, db_column='RegionName', blank=True)
    Description = models.TextField(db_column="Description", blank=True)
    class Meta:
        db_table = u'Region'

The paths between the nodes are defined using the following closure table. It consists of a FK to the ancestor node, FK to the descendant node and the path length (i.e. number of nodes) between Ancestor and Descendant:

class RegionPath(models.Model):
    Ancestor = models.ForeignKey(Region, null=True, db_column='Ancestor', blank=True)
    Descendant = models.ForeignKey(Region, null=True, db_column='Descendant', blank=True)
    PathLength = models.IntegerField(null=True, db_column='PathLength', blank=True)
    class Meta:
        db_table = u'RegionPath'

Now how would I retrieve all Region rows and their respective parent node (i.e. where the RegionPath.PathLength = 1)? My SQL is slightly rusty but I think the SQL query should look something like this.

SELECT r.* from Region as r 
LEFT JOIN 
(SELECT r2.RegionName, p.Ancestor, p.Descendant from Region as r2 INNER JOIN RegionPath as p on r2.id = p.Ancestor WHERE p.PathLength = 1) AS Parent
on r.id = Parent.Descendant

Any help in expressing this using Django's QuerySet API would be much appreciated.

like image 333
CadentOrange Avatar asked Feb 09 '13 14:02

CadentOrange


1 Answers

By adding related_name to the foreign keys like so:

class RegionPath(models.Model):
    Ancestor = models.ForeignKey(Region, null=True, db_column='Ancestor', blank=True, related_name="ancestor")
    Descendant = models.ForeignKey(Region, null=True, db_column='Descendant', blank=True, related_name="descendants")
    PathLength = models.IntegerField(null=True, db_column='PathLength', blank=True)
    class Meta:
        db_table = u'RegionPath'

You can make queries for either relation:

children = Region.objects.filter(ancestors__PathLength=1)
parents = Region.objects.filter(descendants__PathLength=1)

I made my test on a very similar model. You might have to add .distinct(), you might want to select_related() to reduce queries.

like image 57
Árni St. Sigurðsson Avatar answered Oct 13 '22 01:10

Árni St. Sigurðsson