How to create a triple-join table with Django

后端 未结 4 1485
走了就别回头了
走了就别回头了 2021-02-14 11:25

Using Django\'s built in models, how would one create a triple-join between three models.

For example:

  • Users, Roles, and Events are the models.
  • Us
相关标签:
4条回答
  • 2021-02-14 11:53

    While trying to find a faster three-table join for my own Django models, I came across this question. By default, Django 1.1 uses INNER JOINs which can be slow on InnoDB. For a query like:

    def event_users(event_name):
        return User.objects.filter(roles__events__name=event_name)
    

    this might create the following SQL:

    SELECT `user`.`id`, `user`.`name` FROM `user` INNER JOIN `roles` ON (`user`.`id` = `roles`.`user_id`) INNER JOIN `event` ON (`roles`.`event_id` = `event`.`id`) WHERE `event`.`name` = "event_name"
    

    The INNER JOINs can be very slow compared with LEFT JOINs. An even faster query can be found under gimg1's answer: Mysql query to join three tables

    SELECT `user`.`id`, `user`.`name` FROM `user`, `roles`, `event` WHERE `user`.`id` = `roles`.`user_id` AND `roles`.`event_id` = `event`.`id` AND `event`.`name` = "event_name"
    

    However, you will need to use a custom SQL query: https://docs.djangoproject.com/en/dev/topics/db/sql/

    In this case, it would look something like:

    from django.db import connection
    def event_users(event_name):
        cursor = connection.cursor()
        cursor.execute('select U.name from user U, roles R, event E' \
                       ' where U.id=R.user_id and R.event_id=E.id and E.name="%s"' % event_name)
        return [row[0] for row in cursor.fetchall()]
    
    0 讨论(0)
  • 2021-02-14 11:54

    I'd model Role as an association class between Users and Roles, thus,

    class User(models.Model):
         ...
    
    class Event(models.Model):
         ...
    
    class Role(models.Model):
         user = models.ForeignKey(User)
         event = models.ForeignKey(Event)
    

    And enforce the one role per user per event in either a manager or SQL constraints.

    0 讨论(0)
  • 2021-02-14 11:55

    I'd recommend just creating an entirely separate model for this.

    class Assignment(Model):
        user = ForeignKey(User)
        role = ForeignKey(Role)
        event = ForeignKey(Event)
    

    This lets you do all the usual model stuff, such as

    user.assignment_set.filter(role__name="Chaperon")
    role.assignment_set.filter(event__name="Silly Walkathon")
    

    The only thing left is to enforce your one-role-per-user-per-event restriction. You can do this in the Assignment class by either overriding the save method (http://docs.djangoproject.com/en/dev/topics/db/models/#overriding-predefined-model-methods) or using signals (http://docs.djangoproject.com/en/dev/topics/signals/)

    0 讨论(0)
  • 2021-02-14 12:02

    zacherates writes:

    I'd model Role as an association class between Users and Roles (...)

    I'd also reccomed this solution, but you can also make use of some syntactical sugar provided by Django: ManyToMany relation with extra fields.

    Example:

    class User(models.Model):
        name = models.CharField(max_length=128)
    
    class Event(models.Model):
        name = models.CharField(max_length=128)
        members = models.ManyToManyField(User, through='Role')
    
        def __unicode__(self):
            return self.name
    
    class Role(models.Model):
        person = models.ForeignKey(User)
        group = models.ForeignKey(Event)
        date_joined = models.DateField()
        invite_reason = models.CharField(max_length=64)
    
    0 讨论(0)
提交回复
热议问题