Продвинутый Django 5 для продолжающих

Прогресс по курсу:  0/193

2.2 Связь One-To-One (Один к одному)
2 из 2 шагов пройдено

Получение данных из отношения "один к одному"

Во-первых, получите сотрудника с именем John Doe:

e = Employee.objects.filter(first_name='John',last_name='Doe').first()

Django выполняет SELECT оператор, который получает строку с first_name 'John' и last_name 'Doe'.

У компании hr_employee может быть несколько сотрудников с одинаковыми именем и фамилией.  Следовательно, filter() возвращает QuerySet. Чтобы получить первую строку в QuerySet, мы используем first() метод. Метод first() возвращает один экземпляр класса Employee.

Обратите внимание, что запрос не получает контактные данные из hr_contact таблицы. Он получает данные только из hr_employee таблицы.

При доступе к contact атрибуту сотрудника:

e.contact

Django выполняет второй SELECT оператор для получения данных из hr_contact таблицы:

 

Следующее получает контакт с идентификатором 1:

c = Contact.objects.get(id=1)

При привязке контакта к сотруднику вы можете получить доступ employee к contact объекту:

c.employee

Django выполняет SELECT оператор для получения данных из hr_employee таблицы:

Обратите внимание, что Contact класс не имеет employee атрибута. Однако вы можете получить доступ, employee если контакт связан с ним.

Давайте создадим еще один контакт, который не связан ни с одним сотрудником:

c = Contact(phone='+79121111111',address='Sankt-Peterburg, Lenin Street, 82')
c.save()

Django выполнит следующий INSERT оператор:

Если вы найдете контакт, который не связан ни с одним сотрудником, и получите доступ к сотруднику, вы получите исключение RelatedObjectDoesNotExist.

Выбор связанных объектов

Сначала создайте нового сотрудника:

e = Employee(first_name='Jane',last_name='Doe')
e.save()

Во-вторых, получить всех сотрудников:

>>> Employee.objects.all()

Django возвращает двух сотрудников

Если вам нужно отобразить всех сотрудников, а также их контакты на одной странице, то у вас проблема с запросом N+1:

  • Во-первых, вам нужен один запрос, чтобы получить всех сотрудников (N сотрудников).
  • Во-вторых, вам нужно N запросов, чтобы выбрать связанный контакт каждого сотрудника.

В случае с нашим простым примером это не проблема, но в больших базах данных с большим количеством связей нагрузка на базу данных может быть непомерно высокой. Чтобы избежать этого, вы можете запросить всех сотрудников и контакты с помощью одного запроса, используя select_related() метод:

Employee.objects.select_related('contact').all()

В этом случае Django выполняет следующий LEFT JOIN оператор:

Django использует LEFT JOIN, чтобы вернуть всех сотрудников из hr_employee таблицы и контакты, связанные с выбранными сотрудниками:

<QuerySet [<Employee: John Doe>, <Employee: Jane Doe>]>

В заключение, отношения «один к одному» — полезный инструмент для моделирования сложных отношений в Django. Используя OneToOneField, вы можете легко определить уникальную связь между двумя моделями.


Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Я правильно понимаю, что запрос 

Employee.objects.select_related('contact').all()

вернёт всех сотрудников?

Если у сотрудника есть привязанный контакт, то вернётся объединённая таблица сотрудника и контакта, иначе только таблица сотрудника?

@Селянинов_Игорь, Это вернет всех сотрудников.

При обращении через Employee.objects.all() к контакту сотрудника, первым делом ORM достанет сотрудника, и будет каждый раз обращаться к таблице контактов чтобы достать нужную информацию оттуда:

>>> for employee in Employee.objects.all():
...     print(employee.first_name, employee.contact.phone)
... 
SELECT "orm_employee"."id",
       "orm_employee"."first_name",
       "orm_employee"."last_name",
       "orm_employee"."contact_id"
  FROM "orm_employee"
Execution time: 0.001017s [Database: default]
SELECT "orm_contact"."id",
       "orm_contact"."phone",
       "orm_contact"."address"
  FROM "orm_contact"
 WHERE "orm_contact"."id" = 1
 LIMIT 21
Execution time: 0.000483s [Database: default]
John 89412424564
SELECT "orm_contact"."id",
       "orm_contact"."phone",
       "orm_contact"."address"
  FROM "orm_contact"
 WHERE "orm_contact"."id" = 2
 LIMIT 21
Execution time: 0.000064s [Database: default]
Sara 89412445511

А при обращении через Employee.objects.select_related('contact').all() мы это достаем одним запросом, так как Django использует LEFT JOIN.

>>> for employee in Employee.objects.select_related('contact').all():
...     print(employee.first_name, employee.contact.phone)
... 
SELECT "orm_employee"."id",
       "orm_employee"."first_name",
       "orm_employee"."last_name",
       "orm_employee"."contact_id",
       "orm_contact"."id",
       "orm_contact"."phone",
       "orm_contact"."address"
  FROM "orm_employee"
  LEFT OUTER JOIN "orm_contact"
    ON ("orm_employee"."contact_id" = "orm_contact"."id")
Execution time: 0.000251s [Database: default]
John 1233
Sara 435

@Селянинов_Игорь, Кстати вот тут https://stepik.org/lesson/1023437/step/4?unit=1031566 мы рассмотрим еще раз этот метод, может быть то описание будет более понятно.

Спасибо!