Django query to join records of two tables

Happy Ahmad picture Happy Ahmad · Jul 30, 2016 · Viewed 7.7k times · Source

I am using django-mssql 1.6.2 package with django 1.7 to get one or several records of tables from sql server 2008. When I call "get" or "filter" as follows, everything is fine but my server program is very slow. Consider the following tables:

class Contact(models.Model):
      id = models.IntegerField(primary_key=True, unique=True, null=False)
      address = models.CharField(max_length = 100)
      phone = models.IntegerField(unique=True)

class Parent(models.Model):
      id = models.IntegerField(primary_key=True, unique=True, null=False)
      name = models.CharField(max_length = 50)
      contact = models.OneToOneField(Contact)

class Student(models.Model):
      id = models.IntegerField(primary_key=True, unique=True, null=False)
      name = models.CharField(max_length = 50)
      parent = models.ForeignKey(Parent)

Assume that we want to print a table in a template like:

Student name  |  Parent name  |  Parent phone number
John          |  Jack         |  1111111
Susan         |  Jack         |  1111111
Dan           |  Jack         |  1111111
Jackie        |  Sara         |  2222222

I use a code like:

    query_results = list()
    parents = Parent.objects.all()
    for any_parent in parents:
        students = Student.objects.filter(parent=any_parent)
        for student in students:
            element = TempObjectForStudent()
            element.student_name = student.name
            element.parent_name = any_parent.name
            element.parent_phone = any_parent.contact.phone
            query_results.append(element)

         if students.__len__() == 0:
            element = TempObjectForStudent()
            element.student_name = 'without any active student'
            element.parent_name = any_parent.name
            element.parent_phone = any_parent.contact.phone
            query_results.append(element)

class TempObjectForStudent():
    student_name = None
    parent_name = None
    parent_phone = None

With a template like:

{% for row in query_results %}
    <tr><td>{{ row.student_name }}</td><td>{{ row.parent_name }}</td><td>{{ row.parent_phone }}</td></tr>

As I mentioned before, the problem is the speed of running lots of queries in this method. How can I use join to deliver all of the table data in just one query?

Answer

an0o0nym picture an0o0nym · Jul 31, 2016

Ok, you made it a little bit too complicated.

There is no need for TempObjectForStudent. You can get the student instance without that. Simply add a related_name to you Student model's parent field, so you can get reference to the Parent object simply using dot notation.

Also please note that Django gives each model the id field automaticaly. You can read more here and here. So there is no need to create that field explicitly in your models.

Regarding your question, I would do it like this:

# models.py
class Parent(models.Model):
      name = models.CharField(max_length = 50)
      contact = models.OneToOneField(Contact)

class Student(models.Model):
      name = models.CharField(max_length = 50)
      parent = models.ForeignKey(Parent, related_name='students')

# views.py
parents = Parent.objects.all()

# template.html
{% for parent in parents %}
    <tr>
        <td>
            {% with parent.students.all as students %}
                {% for student in students %}
                    {{ student.name }}
                {% empty %}
                    'without any active student'
                {% endfor %}
            {% endwith %}
        </td>
        <td>
            {{ parent.name }}
        </td>
        <td>
            {{ parent.contact.phone }}
        </td>
    </tr>
{% endfor %}

Update:

I came across a select_related QuerySet method. I think that this is what you've been searching for! It uses SQL JOIN clause and includes all related object's fields in SELECT statement. Read more here. I hope it helps!