OneToOne Relation
OneToOne은 1:1 관계를 뜻한다. 예를 들어 "하나의 사용자는 하나의 프로필만 가질 수 있다"라는 상황에서 사용할 수 있다. OneToOne Relation을 경우 하나의 객체만을 반환한다. 아래 예제를 통해 이해해보자.
Author와 AuthorProfile Model
class BaseModel(models.Model):
date_of_create = models.DateTimeField(auto_now_add=True)
date_of_update = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Author(BaseModel):
first_name = models.CharField(max_length=10)
last_name = models.CharField(max_length=10)
email = models.EmailField(blank=True, verbose_name='e-mail')
objects = models.Manager()
class Meta:
db_table = "author"
def __str__(self):
return u"%s %s" % (self.first_name, self.last_name)
class AuthorProfile(BaseModel):
description = models.CharField(max_length=15, null=True)
author = models.OneToOneField(Author, on_delete=models.CASCADE)
class Meta:
db_table = "author_profile"
def __str__(self):
return u"[ %s ]: Profile" % (self.author.name)
>>> author = Author.objects.get(id=1)
(0.001) | SELECT version(), @@sql_mode, @@default_storage_engine, @@sql_auto_is_null, @@lower_case_table_names,
| convert_tz('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
(0.001) | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
(0.002) | SELECT `author`.`id`,
| `author`.`date_of_create`,
| `author`.`date_of_update`,
| `author`.`first_name`,
| `author`.`last_name`,
| `author`.`email`
| FROM `author`
| WHERE `author`.`id` = 1
| LIMIT 21
>>> author_profile = AuthorProfile.objects.create(description='first author', author=author)
(0.002) | INSERT INTO `author_profile` (`date_of_create`, `date_of_update`, `description`, `author_id`)
| VALUES ('2022-06-06 11:37:36.998191', '2022-06-06 11:37:36.998259', 'first author', 1)
>>> AuthorProfile.objects.create(description='second profile', author=author)
Traceback (most recent call last):
...
_mysql.connection.query(self, query)
django.db.utils.IntegrityError: (1062, "Duplicate entry '1' for key 'author_id'")
OneToOneField, Read
>>> AuthorProfile.objects.all()
(0.001) | SELECT `author_profile`.`id`,
| `author_profile`.`date_of_create`,
| `author_profile`.`date_of_update`,
| `author_profile`.`description`,
| `author_profile`.`author_id`
| FROM `author_profile`
| LIMIT 21
(0.001) | SELECT `author`.`id`,
| `author`.`date_of_create`,
| `author`.`date_of_update`,
| `author`.`first_name`,
| `author`.`last_name`,
| `author`.`email`
| FROM `author`
| WHERE `author`.`id` = 1
| LIMIT 21
(0.001) | SELECT `author`.`id`,
| `author`.`date_of_create`,
| `author`.`date_of_update`,
| `author`.`first_name`,
| `author`.`last_name`,
| `author`.`email`
| FROM `author`
| WHERE `author`.`id` = 2
| LIMIT 21
<QuerySet [<AuthorProfile: [ minjune kim ]: Profile>, <AuthorProfile: [ seojune choi ]: Profile>]>
추가로 삽입했던 데이터까지 합쳐서 제대로 읽어 들이지만 불필요한 쿼리를 발생시키는 것 같다(N+1). AuthorProfile Model에서 데이터를 읽어 들이는 케이스이므로 정방향 참조인 Select Related를 통해서 데이터를 읽어보자.
>>> AuthorProfile.objects.select_related('author')
(0.001) | SELECT `author_profile`.`id`,
| `author_profile`.`date_of_create`,
| `author_profile`.`date_of_update`,
| `author_profile`.`description`,
| `author_profile`.`author_id`,
| `author`.`id`,
| `author`.`date_of_create`,
| `author`.`date_of_update`,
| `author`.`first_name`,
| `author`.`last_name`,
| `author`.`email`
| FROM `author_profile`
| INNER JOIN `author` ON (`author_profile`.`author_id` = `author`.`id`)
| LIMIT 21
<QuerySet [<AuthorProfile: [ minjune kim ]: Profile>, <AuthorProfile: [ seojune choi ]: Profile>]>
AuthorProfile Model의 author를 가져올 때는 INNER JOIN을 발생시킨다는 것을 알 수 있다.
OneToOneRel이 Null=True 이면?
해당 author 속성을 NULL이 가능하게 설정 후 데이터를 읽었을 경우 어떻게 동작하는지 확인해보자. AuthorProfile Model을 다음과 같이 변경 하자.
class AuthorProfile(BaseModel):
...
author = models.OneToOneField(Author, on_delete=models.SET_NULL, null=True)
...
이후 데이터를 삽입하자.
>>> AuthorProfile.objects.create(description='Null Author', author=None)
(0.003) | INSERT INTO `author_profile` (`date_of_create`, `date_of_update`, `description`, `author_id`)
| VALUES ('2022-06-06 11:53:07.068142', '2022-06-06 11:53:07.068212', 'Null Author', NULL)
그리고 다시 Select Related 이용해서 읽어보자.
>>> AuthorProfile.objects.select_related('author')
(0.001) | SELECT version(), @@sql_mode, @@default_storage_engine, @@sql_auto_is_null, @@lower_case_table_names,
| convert_tz('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
(0.001) | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
(0.001) | SELECT `author_profile`.`id`,
| `author_profile`.`date_of_create`,
| `author_profile`.`date_of_update`,
| `author_profile`.`description`,
| `author_profile`.`author_id`,
| `author_profile`.`manager_id`,
| `author`.`id`,
| `author`.`date_of_create`,
| `author`.`date_of_update`,
| `author`.`first_name`,
| `author`.`last_name`,
| `author`.`email`
| FROM `author_profile`
| LEFT OUTER JOIN `author` ON (`author_profile`.`author_id` = `author`.`id`)
| LIMIT 21
<QuerySet [<AuthorProfile: [ minjune kim ]: Profile>]>
on_delete 속성과 null 속성이 변경되었으므로 쿼리도 LEFT OUTER JOIN으로 변경되었다.
OneToOneRel이 N개 일 때
OneToOneRel 속성을 하나 더 추가한 뒤 Select Related를 걸면 어떻게 될지 문득 궁금했다. 추가한 모델은 다음과 같다.
class AuthorManager(BaseModel):
manager_name = models.CharField(max_length=15, null=True)
class Meta:
db_table = "author_manager"
def __str__(self):
return u"[ %s ]" % (self.manager_name)
이후 AuthorProfile은 다음과 같이 변경했다.
class AuthorProfile(BaseModel):
description = models.CharField(max_length=15, null=True)
author = models.OneToOneField(Author, on_delete=models.CASCADE)
manager = models.OneToOneField(AuthorManager, on_delete=models.CASCADE)
class Meta:
db_table = "author_profile"
def __str__(self):
return u"[ %s ]: Profile" % (self.author)
Author를 관리하는 Manager는 한 명이고 이는 AuthorProfile Model에 저장하자 라는 의도이다. 이후 AuthorProfile에 AuthorManager 정보를 저장하자.
>>> author_manager = AuthorManager.objects.create(manager_name='first_manager')
>>> first_author_profile = AuthorProfile.objects.get(id=1)
>>> first_author_profile.manager = author_manager
>>> first_author_profile.save()
이후 같은 방법으로 Select Related를 이용해 읽었을 경우 결과는 다음과 같다.
>>> AuthorProfile.objects.select_related('author', 'manager')
(0.001) | SELECT version(), @@sql_mode, @@default_storage_engine, @@sql_auto_is_null, @@lower_case_table_names,
| convert_tz('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
(0.001) | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
(0.001) | SELECT `author_profile`.`id`,
| `author_profile`.`date_of_create`,
| `author_profile`.`date_of_update`,
| `author_profile`.`description`,
| `author_profile`.`author_id`,
| `author_profile`.`manager_id`,
| `author`.`id`,
| `author`.`date_of_create`,
| `author`.`date_of_update`,
| `author`.`first_name`,
| `author`.`last_name`,
| `author`.`email`,
| `author_manager`.`id`,
| `author_manager`.`date_of_create`,
| `author_manager`.`date_of_update`,
| `author_manager`.`manager_name`
| FROM `author_profile`
| INNER JOIN `author` ON (`author_profile`.`author_id` = `author`.`id`)
| INNER JOIN `author_manager` ON (`author_profile`.`manager_id` = `author_manager`.`id`)
| LIMIT 21
'Frame Work > Django' 카테고리의 다른 글
uWSGI Socket + Nginx + Docker (0) | 2022.11.10 |
---|---|
uWSGI를 알아보자 (0) | 2022.11.05 |
django.db.utils.IntegrityError: (1215, 'Cannot add foreign key constraint') (0) | 2022.05.07 |
[Django] Session ! (0) | 2022.05.06 |
[Django] Select && Prefetch Related ! (0) | 2022.02.20 |