Search code examples
djangodatabase-designdjango-orm

Why would someone set primary_key=True on an One to one reationship (OneToOneField)?


I was watching a video on and the instructor stated that:

We should set primary_key=True to prevent a Model from having duplicate rows in a OneToOne relationship (Ex: Prevent a user from having multiple profiles).

I know this statement is wrong! AFAIK, an OneToOne field is just a ForeignKey with unique parameter set to True. But I got curious and looked-up the Django documentation, Sure enough they are using primary=True in their example.

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(models.Model):
    place = models.OneToOneField(
        Place,
        on_delete=models.CASCADE,
        primary_key=True,
    )

So, why would someone set primary_key=True on an OneToOne relation? My guess is that it's just reasonable to have that field as a primary key and there is no technical background behind it.


Solution

  • This is a pattern to implement object-oriented inheritance in a relational database, for example as this article of Oracle discusses.

    Indeed, it means that one can define a Place, and for that Place create a Restaurant model as well. It has a OneToOneField(…) to the "parent" model. The OneToOneField prevents that one can define two (or more) Restaurants for the same Place.

    Usually it is defined as a primary key, since then it shares the same "primary code space", and it removes a column that is otherwise used to do the mapping and thus would make the database larger.

    Django will implement this the same way. If we define this as:

    class Place(models.Model):
        name = models.CharField(max_length=50)
        address = models.CharField(max_length=80)
    
    class Restaurant(Place):
        pass

    then it will be implemented as:

    mysql> describe place;
    +---------+-------------+------+-----+---------+----------------+
    | Field   | Type        | Null | Key | Default | Extra          |
    +---------+-------------+------+-----+---------+----------------+
    | id      | int         | NO   | PRI | NULL    | auto_increment |
    | name    | varchar(50) | NO   |     | NULL    |                |
    | address | varchar(80) | NO   |     | NULL    |                |
    +---------+-------------+------+-----+---------+----------------+
    3 rows in set (0.00 sec)
    
    mysql> describe restaurant;
    +--------------+------+------+-----+---------+-------+
    | Field        | Type | Null | Key | Default | Extra |
    +--------------+------+------+-----+---------+-------+
    | place_ptr_id | int  | NO   | PRI | NULL    |       |
    +--------------+------+------+-----+---------+-------+
    

    It thus will add a primary key named place_ptr_id that refers to the place table. This originates from a OneToOneField that Django adds to the Restaurant model named place_ptr.

    We should set primary_key=True to prevent a Model from having duplicate rows in a OneToOne relationship (Ex: Prevent a user from having multiple profiles)

    This makes no sense, since a OneToOneField is in essence a ForeignKey with unique=True [Django-doc]. So this is already enforced by the OneToOneField, it is not necessary to make it a primary key.

    Something that can not be done with the above modeling is preventing that an Place is a Restaurant and Library at the same time.