<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 18:09:29 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="xxJDZpQJOIoI-It6qiZI7N8P1P_3IMmvdPujJyGroJ0ow4hRZneV6r67r-KmKbibHwb5Vf8XNw3JGzlzc6xH8w";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Эффективное использование ORM | Python: Django ORM</title>
<meta name="description" content="Эффективное использование ORM / Python: Django ORM: Поговорим о способах более эффективного использования инструментов высокого уровня без ручного написания запросов на SQL">
<link rel="canonical" href="https://ru.hexlet.io/courses/python-django-orm/lessons/efficiency/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Эффективное использование ORM">
<meta property="og:title" content="Python: Django ORM">
<meta property="og:description" content="Эффективное использование ORM / Python: Django ORM: Поговорим о способах более эффективного использования инструментов высокого уровня без ручного написания запросов на SQL">
<meta property="og:url" content="https://ru.hexlet.io/courses/python-django-orm/lessons/efficiency/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="_U0rvf-O-T0BBje4TBHj9_EUkY6Lmopri9NeOk8eQMoSnOCKDfBUXbdFEyBAHhOAMR28JIOtdMk2M8RuHRmnpA" />
<script src="/vite/assets/inertia-BIn5nEMk.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-DOv3_-Z_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0NiwicHVyIjoiYmxvYl9pZCJ9fQ==--5c088db10d02b94be027408f50ecf11c23d9d4cb/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Version%20control-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T18:09:29.634Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"QB8DGDUn0X2xVIfrAiK4X62tnMFmcqZxOZ8yk8Yz0m-vzsgvx1l8HQcXo3MOLUgobaSxa25FWNOEf6jHlDQ1AQ","topics":[{"id":57070,"title":"Добрый день!\n\nА как правильно применять:\n\n > Если значений нужно не два и не три, то уже имеет смысл использовать метод .value(имена,полей), который даст (при обходе итогового QuerySet) уже не кортежи, а словари с ключами, совпадающими с именами указанных полей.\n\nМне пишут:\n\n```\n'Manager' object has no attribute 'value'\n'QuerySet' object has no attribute 'value'\n```\n","plain_title":"Добрый день! А как правильно применять: Если значений нужно не два и не три, то уже имеет смысл использовать метод .value(имена,полей), который даст (при обходе итогового QuerySet) уже не кортежи, а словари с ключами, совпадающими с именами указанных полей. Мне пишут: 'Manager' object has no attribute 'value' 'QuerySet' object has no attribute 'value' ","creator":{"public_name":"Станислав Глазко","id":252357,"is_tutor":false},"comments":[{"creator":{"public_name":"Aleksei Pirogov","id":72206,"is_tutor":true},"id":120978,"body":"Опечатка! Исправлю на `.values()`, спасибо :)","topic_id":57070}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":80553,"title":"3-6 пункт сбита разметка","plain_title":"3-6 пункт сбита разметка ","creator":{"public_name":"","id":459334,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":165267,"body":"Привет. Спасибо, что обратил внимание, нумерацию поправил.","topic_id":80553}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":73535,"title":"Здравствуйте!\nДобавьте пожалуйста в этот урок пару строк про метод prefetch_related(), который как select_related() но немного другой. ","plain_title":"Здравствуйте! Добавьте пожалуйста в этот урок пару строк про метод prefetchrelated(), который как selectrelated() но немного другой. ","creator":{"public_name":"Роман","id":443122,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":153494,"body":"Добрый день, добавлю задачку дополнить урок, спасибо!","topic_id":73535}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":89993,"title":"С большим трудом сделал упражнение и то по большей части с помощью python, а не методами SQL.\nХотелось бы чтобы в будущем добавили больше практики на простых примерах, лучше раскрыли prefetch_related ( с примерами, а не просто как справку).\nВ целом по курсу последние пару уроков как-то слабо поданы, осталось ощущение непонимания что произошло","plain_title":"С большим трудом сделал упражнение и то по большей части с помощью python, а не методами SQL. Хотелось бы чтобы в будущем добавили больше практики на простых примерах, лучше раскрыли prefetch_related ( с примерами, а не просто как справку). В целом по курсу последние пару уроков как-то слабо поданы, осталось ощущение непонимания что произошло ","creator":{"public_name":"Максим Лесников","id":472113,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":178900,"body":"Добрый день!\n\nСпасибо за ваш фидбэк, обратная связь очень важна. Передал его редакторам, что занимаются курсом.","topic_id":89993}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":89872,"title":"> Реализуйте вью, выводящую список учителей с их курсами, количеством учеников в каждом курсе и списком этих учеников.\n\nНаписал вью согласно тех. заданию, в итоге получилось сделать за два запроса к БД.\nДумаю стоит немного уточнить текст подсказки и откорректировать тесты. Либо в самом тексте задания указать что должно быть не менее и не более трех запросов к БД (но считаю такое решение некорректным).\n\n[Код тут ](https://ru.hexlet.io/code_reviews/1071284)\n\n\n```\nAssertionError: 2 != 3 : 2 queries executed, 3 expected\nCaptured queries were:\n1. SELECT \"django_app_course\".\"id\", \"django_app_course\".\"name\", \"django_app_course\".\"teacher_id\", COUNT(\"django_app_course_students\".\"student_id\") AS \"students_count\", \"django_app_teacher\".\"id\", \"django_app_teacher\".\"name\" FROM \"django_app_course\" LEFT OUTER JOIN \"django_app_course_students\" ON (\"django_app_course\".\"id\" = \"django_app_course_students\".\"course_id\") INNER JOIN \"django_app_teacher\" ON (\"django_app_course\".\"teacher_id\" = \"django_app_teacher\".\"id\") GROUP BY \"django_app_course\".\"id\", \"django_app_course\".\"name\", \"django_app_course\".\"teacher_id\", \"django_app_teacher\".\"id\", \"django_app_teacher\".\"name\"\n2. SELECT (\"django_app_course_students\".\"course_id\") AS \"_prefetch_related_val_course_id\", \"django_app_student\".\"id\", \"django_app_student\".\"name\" FROM \"django_app_student\" INNER JOIN \"django_app_course_students\" ON (\"django_app_student\".\"id\" = \"django_app_course_students\".\"student_id\") WHERE \"django_app_course_students\".\"course_id\" IN (1, 2, 3, 4)\n```\n","plain_title":"Реализуйте вью, выводящую список учителей с их курсами, количеством учеников в каждом курсе и списком этих учеников. Написал вью согласно тех. заданию, в итоге получилось сделать за два запроса к БД. Думаю стоит немного уточнить текст подсказки и откорректировать тесты. Либо в самом тексте задания указать что должно быть не менее и не более трех запросов к БД (но считаю такое решение некорректным). Код тут (https://ru.hexlet.io/code_reviews/1071284) AssertionError: 2 != 3 : 2 queries executed, 3 expected Captured queries were: 1. SELECT \"django_app_course\".\"id\", \"django_app_course\".\"name\", \"django_app_course\".\"teacher_id\", COUNT(\"django_app_course_students\".\"student_id\") AS \"students_count\", \"django_app_teacher\".\"id\", \"django_app_teacher\".\"name\" FROM \"django_app_course\" LEFT OUTER JOIN \"django_app_course_students\" ON (\"django_app_course\".\"id\" = \"django_app_course_students\".\"course_id\") INNER JOIN \"django_app_teacher\" ON (\"django_app_course\".\"teacher_id\" = \"django_app_teacher\".\"id\") GROUP BY \"django_app_course\".\"id\", \"django_app_course\".\"name\", \"django_app_course\".\"teacher_id\", \"django_app_teacher\".\"id\", \"django_app_teacher\".\"name\" 2. SELECT (\"django_app_course_students\".\"course_id\") AS \"_prefetch_related_val_course_id\", \"django_app_student\".\"id\", \"django_app_student\".\"name\" FROM \"django_app_student\" INNER JOIN \"django_app_course_students\" ON (\"django_app_student\".\"id\" = \"django_app_course_students\".\"student_id\") WHERE \"django_app_course_students\".\"course_id\" IN (1, 2, 3, 4) ","creator":{"public_name":"Deepblack","id":26609,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":178743,"body":"Спасибо за замечание. Верно надо правда поправить тесты.","topic_id":89872},{"creator":{"public_name":"Deepblack","id":26609,"is_tutor":false},"id":178681,"body":"Поправка, тут я интерпретировал текст по своему.\nЯ сделал вывод курсов, учеников, их учителей и количество учеников в каждом курсе.\nЧто не совсем точно отражает то что нужно было, хоть в целом и выводит все эти данные.","topic_id":89872}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":89130,"title":"Здравствуйте!\nПодскажите, пожалуйста. а можно ли использовать для сбора данных учителей (модель Teacher) `prefetch_related(\"courses__students\") `\nвместо того, которое используется в решении?","plain_title":"Здравствуйте! Подскажите, пожалуйста. а можно ли использовать для сбора данных учителей (модель Teacher) prefetch_related(\"courses__students\") вместо того, которое используется в решении? ","creator":{"public_name":"Алексей Кольцов","id":649626,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":177664,"body":"Добрый день, у любой задачи всегда несколько спосбов решения (тем более если мы говорим о написании запроса из базы). Если ваше решение проходит тесты, то оно верное)","topic_id":89130}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":93013,"title":"1) Решил задание, но тема про Prefetch оказалась не понятна. Нужно больше визуализации данных))\n2) Подсказка про запросы идёт в разрез ТЗ во вьюхе. \n3) Стоило бы добавить django-extensions чтобы внутри shell_plus среды поиграться с разными запросами.","plain_title":"1) Решил задание, но тема про Prefetch оказалась не понятна. Нужно больше визуализации данных)) 2) Подсказка про запросы идёт в разрез ТЗ во вьюхе. 3) Стоило бы добавить django-extensions чтобы внутри shell_plus среды поиграться с разными запросами. ","creator":{"public_name":"Денис Большаков","id":577639,"is_tutor":false},"comments":[{"creator":{"public_name":"Денис Большаков","id":577639,"is_tutor":false},"id":182878,"body":"**Ivan Mamtsev**, 1) Примеры использования и вывод SQL query, что на что влияет\n2) Значит мне не понятно было)","topic_id":93013},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":182877,"body":"Добрый день, о какой визуализации вы говорите?\n\nПодсказка как раз буквально цитирует решение учителя. Так что все верно.\n\nextensions добавлю, да.","topic_id":93013}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":100391,"title":"Да уж, материал из разряда иди читай гайды на стороне и официальную документацию. Либо ты должен уже иметь какой-то опыт работы с этими инструментами, либо попробуй догадайся. Так получается, что \"мы тебе скажем, что существует кое-какая штука, но как ей пользоваться это уж ты сам сообрази. В документации всё есть! Мы же не школа какая-нибудь, где этому учат, да?\" Бился над задачей несколько часов, меньше чем в 5-6 запросов не уложился в итоге пришёл к мысли, что я тупой, плюнул со злости и посмотрел ответ. Как этим prefetch пользоваться разбирался через YouTube и т.п. Только тут еще помимо prefetch_related советют объект Prefetch со ссылкой на документацию. Вам сложно добавить пару абзацев с описанием инструмента и привести пару простых примеров? \"Отличное\" завершение учебного курса. Да и в целом курс по ORM местами ощущается самым наплевательским по отношению к студенту.\n\nМаксимум на что меня хватило.\n[https://ru.hexlet.io/code_reviews/1443919](https://ru.hexlet.io/code_reviews/1443919)","plain_title":"Да уж, материал из разряда иди читай гайды на стороне и официальную документацию. Либо ты должен уже иметь какой-то опыт работы с этими инструментами, либо попробуй догадайся. Так получается, что \"мы тебе скажем, что существует кое-какая штука, но как ей пользоваться это уж ты сам сообрази. В документации всё есть! Мы же не школа какая-нибудь, где этому учат, да?\" Бился над задачей несколько часов, меньше чем в 5-6 запросов не уложился в итоге пришёл к мысли, что я тупой, плюнул со злости и посмотрел ответ. Как этим prefetch пользоваться разбирался через YouTube и т.п. Только тут еще помимо prefetch_related советют объект Prefetch со ссылкой на документацию. Вам сложно добавить пару абзацев с описанием инструмента и привести пару простых примеров? \"Отличное\" завершение учебного курса. Да и в целом курс по ORM местами ощущается самым наплевательским по отношению к студенту. Максимум на что меня хватило. https://ru.hexlet.io/code_reviews/1443919 (https://ru.hexlet.io/code_reviews/1443919) ","creator":{"public_name":"Дмитрий Антонов","id":402945,"is_tutor":false},"comments":[{"creator":{"public_name":"Дмитрий Антонов","id":402945,"is_tutor":false},"id":192069,"body":"Добавлю. Оказывается я был на верном пути. Но из-за неверного обращения к атрибутам в шаблоне порождались новые запросы. После исправления удалось уложиться в 2 запроса. Но как работать с классом Prefetch я так и не разобрался, решил без него.","topic_id":100391},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":192299,"body":"Здорово, что в целом получилось. Подскажите чего вам не хватило в курсе?","topic_id":100391}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":100875,"title":"Я не понимаю, что значит этот абзац. \n\n> Использование `.only()` отлично показывает всю высокоуровневость ORM: можно просто обращаться к полям объекта и не думать, что и когда подгружается. Еще view выводит посты блога в виде списка диалогов, причем выводит тело только для пары первых пунктов. Это очень лаконичное решение с точки зрения кода, которое еще и не слишком нагружает базу.\n\nСлово вроде понятные, но смысл ускользает. О каком view речь? К чему тут \"еще\"?","plain_title":"Я не понимаю, что значит этот абзац. Использование .only() отлично показывает всю высокоуровневость ORM: можно просто обращаться к полям объекта и не думать, что и когда подгружается. Еще view выводит посты блога в виде списка диалогов, причем выводит тело только для пары первых пунктов. Это очень лаконичное решение с точки зрения кода, которое еще и не слишком нагружает базу. Слово вроде понятные, но смысл ускользает. О каком view речь? К чему тут \"еще\"? ","creator":{"public_name":"Владислав Смирнов","id":656520,"is_tutor":false},"comments":[{"creator":{"public_name":"Elena Gromova","id":548102,"is_tutor":true},"id":192778,"body":"**Владислав Смирнов**, добрый день! Речь видимо об [этом](https://ru.hexlet.io/courses/python-django-basics/lessons/views/theory_unit)","topic_id":100875}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}},{"id":104833,"title":"Могу я вас попросить, для будущих студентов, эту упражнение в отдельный, следующий урок передвинуть? То есть на этой теме в начале сделать упражнение непосредственно на тему из теории: select_related(), prefetch_related(), value(), values_list(), only(). На что-то из этого по крайней мере. А уже на следующем уроке хорошенько пояснить тему Prefatch(). И поставить это упражнение. А то без никакой базовой практики на предыдущие темы, вы бросили нас в огонь на Prefatch(), который мы ни слухом не духом не слышали, не видели. Просто кинули ссылку на документацию) это даже забавно, в своей простоте, но грустно и непонятно. \n\nТеперь по поводу упражнения. \n1. Понимаемо, что вы хотите чтобы мы еще вспомнили как с html работать, это уважаемо. Но тема и так сложная, а связывать её с темой, которую мы проходили очень давно, кажется не самой эффективной идеей. Не лучше будет html пока не трогать в этом упражнении? Неудивительно что тут такой низкий процент самостоятельного решения (не видел ниже). \n2. Первая строка в задании: \"В этом задании вам предстоит попрактиковаться с выборками используя prefetch_related, select_related и объект Prefetch\". Но в решении учителя не используется select_related! Это еще сильнее запутывает, в этой и так непростой задаче.\n","plain_title":"Могу я вас попросить, для будущих студентов, эту упражнение в отдельный, следующий урок передвинуть? То есть на этой теме в начале сделать упражнение непосредственно на тему из теории: selectrelated(), prefetchrelated(), value(), values_list(), only(). На что-то из этого по крайней мере. А уже на следующем уроке хорошенько пояснить тему Prefatch(). И поставить это упражнение. А то без никакой базовой практики на предыдущие темы, вы бросили нас в огонь на Prefatch(), который мы ни слухом не духом не слышали, не видели. Просто кинули ссылку на документацию) это даже забавно, в своей простоте, но грустно и непонятно. Теперь по поводу упражнения. 1. Понимаемо, что вы хотите чтобы мы еще вспомнили как с html работать, это уважаемо. Но тема и так сложная, а связывать её с темой, которую мы проходили очень давно, кажется не самой эффективной идеей. Не лучше будет html пока не трогать в этом упражнении? Неудивительно что тут такой низкий процент самостоятельного решения (не видел ниже). 2. Первая строка в задании: \"В этом задании вам предстоит попрактиковаться с выборками используя prefetchrelated, selectrelated и объект Prefetch\". Но в решении учителя не используется select_related! Это еще сильнее запутывает, в этой и так непростой задаче. ","creator":{"public_name":"Усман Сахбеев","id":707398,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":197606,"body":"Убрал шаблон, теперь в практике нужно написать одну строчку с выборкой.","topic_id":104833},{"creator":{"public_name":"Усман Сахбеев","id":707398,"is_tutor":false},"id":197441,"body":"> В практике также не нужно работать с HTML. Мы работаем с шаблоном, с его языком, который в сущности повторяет джанговский синтаксис. То есть в шаблоне нам нужно обойти выборку, используя изученные спобобы как обращение к полям модели через точку или обращение к связанным моделям. К HTML это не имеет отношения -- это непосредсвенно практика работы с ORM.\n\nЯ с вами согласен что это работа с ORM. Но это в любом случае html). Даже если я не правильно понимаю, и это не html по факту (хотя мы буквально пишем код в index.html, используем теги). Сути дела это не меняет. Правка этого кода, это отдельная задача, которая к этому моменту уже забывается. Поэтому не простая. Но и основная задача сложная, поэтому тяжело становится. Повторить написание шаблона, это очень важно. Но будто бы лучше это делать, с более простой основной задачей. Сами посудите, 60 процентов самостоятельного решения. Это на столько мало, что даже не знаю, что мне добавить то еще) IMHO, в задании явно что-то не так, и его надо менять. \n\nНу и это буквально последняя задача из всего обучения на пайтон разработчика. В ваших же интересах сделать её более понятной, интересной. Чтобы оставалось положительное впечатление. ","topic_id":104833},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":197435,"body":"Добрый день, спасибо за замечания, попробую ответить на все замечания.\n\n`Prefetch()` не нуждается в отдельном уроке, потому что это просто абстракция для ленивого вычисления prefetch'а. Она похожа на [`Q`](https://docs.djangoproject.com/en/5.1/ref/models/querysets/#q-objects) и [`F`](https://docs.djangoproject.com/en/5.1/ref/models/expressions/#django.db.models.F) объекты. Да и в документации ей уделено буквально пару строк, не считая примеров кода.\n\nВ практике также не нужно работать с HTML. Мы работаем с шаблоном, с его языком, который в сущности повторяет джанговский синтаксис. То есть в шаблоне нам нужно обойти выборку, используя изученные спобобы как обращение к полям модели через точку или обращение к связанным моделям. К HTML это не имеет отношения -- это непосредсвенно практика работы с ORM.\n\nПо поводу описания задания, поправлю. Думаю, что даже уберу `Prefetch` из практики, можно без него.","topic_id":104833}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Эффективное использование ORM","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2590,"slug":"python_django_orm_efficiency_exercise","name":null,"state":"active","kind":"exercise","language":"python","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"В этом задании вам предстоит попрактиковаться с выборками используя `prefetch_related`\n\nВ упражнении реализованы следующие модели:\n\n* `Student` с полем `name`\n* `Teacher` с полем `name`\n* `Course` с полями `name`, `students` и `teachers`\n\nК одному курсу прикреплен один учитель (Foreign Key), но у курса может быть несколько учеников. Также у каждого ученика может быть несколько курсов (Many-To-Many).\n\n## django_app/views.py\n\nДопишите вью, выводящую список учителей с их курсами, количеством учеников в каждом курсе и списком этих учеников.\n","prepared_readme":"В этом задании вам предстоит попрактиковаться с выборками используя `prefetch_related`\n\nВ упражнении реализованы следующие модели:\n\n* `Student` с полем `name`\n* `Teacher` с полем `name`\n* `Course` с полями `name`, `students` и `teachers`\n\nК одному курсу прикреплен один учитель (Foreign Key), но у курса может быть несколько учеников. Также у каждого ученика может быть несколько курсов (Many-To-Many).\n\n## django_app/views.py\n\nДопишите вью, выводящую список учителей с их курсами, количеством учеников в каждом курсе и списком этих учеников.\n","has_solution":true,"entity_name":"Эффективное использование ORM"},"units":[{"id":4719,"name":"theory","url":"/courses/python-django-orm/lessons/efficiency/theory_unit"},{"id":4720,"name":"quiz","url":"/courses/python-django-orm/lessons/efficiency/quiz_unit"},{"id":8751,"name":"exercise","url":"/courses/python-django-orm/lessons/efficiency/exercise_unit"}],"links":[],"ordered_units":[{"id":4719,"name":"theory","url":"/courses/python-django-orm/lessons/efficiency/theory_unit"},{"id":4720,"name":"quiz","url":"/courses/python-django-orm/lessons/efficiency/quiz_unit"},{"id":8751,"name":"exercise","url":"/courses/python-django-orm/lessons/efficiency/exercise_unit"}],"id":2103,"slug":"efficiency","state":"approved","name":"Эффективное использование ORM","course_order":100,"goal":"Поговорим о способах более эффективного использования инструментов высокого уровня без ручного написания запросов на SQL","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Как и любое средство повышения уровня абстракции, ORM может выдавать не самые эффективные запросы в некоторых случаях. К сожалению, по-другому быть не может: тонко настроенные SQL-запросы находятся на существенно более низком уровне. В Django ORM можно переписывать код запроса руками, но есть ли в этом необходимость? Вовсе нет.\n\nДля начала прочтем отрывок из документации к фреймворку:\n\n1. Сначала **измерьте**. У любого QuerySet можно запросить описание предполагаемых запросов с помощью вызова метода `.explain()`. Тут пригодится умение читать SQL и понимать то, о чем рассказывает через `explain` ваша СУБД\n2. Добавьте **индексы**. SQL explain часто может сказать, что в каком-то месте делается \"full scan\" то есть полный перебор, а это означает, что где-то не хватает индексов. Стоит подумать о том, чтобы оные добавить. Подробности можно почитать в [документации](https://docs.djangoproject.com/en/5.2/ref/models/indexes/)\n3. Делайте как можно больше работы силами СУБД. Аннотируйте, агрегируйте. СУБД знает, как это оптимизировать и как кэшировать результаты.\n4. В крайнем случае пишите **необходимый минимум** SQL.\n5. Знайте, как работают QuerySets и помните:\n\n- **Насколько** QuerySets ленив — он не делает лишних запросов, пока вы не попросите\n- **В какой момент** QuerySet вычисляется (финализируется)\n- **Каким образом** данные представлены в памяти\n- **Как** QuerySet кеширует результаты запросов и подзапросов\n\nСледует помнить, что многие ситуации разрешаются задолго до того, как вы дойдете до написания SQL.\n\n## Explain\n\nОдин из ключевых приемов для анализа запросов это функция `EXPLAIN`. Он показывает, как база данных планирует выполнить ваш запрос. Он раскрывает такие важные детали, как использование индексов, порядок соединения таблиц и предполагаемую стоимость операций. В Django метод `.explain()` предоставляет удобный доступ к этой функциональности, помогая понять, почему некоторые запросы могут работать медленнее других и как их можно оптимизировать.\n\n```python\nfrom django.db.models import Count, Q\nfrom django.db import connection\n\n# Создаем сложный запрос для анализа\ncomplex_query = (\n Post.objects.annotate(comment_count=Count(\"comments\"), tag_count=Count(\"tags\"))\n .filter(Q(comment_count__gte=10) | Q(tag_count__gte=5))\n .select_related(\"author\")\n)\n\nprint(complex_query.explain())\n\n\"\"\"\n-> Hash Join (cost=35.50..60.53 rows=100 width=40)\n -> Seq Scan on blog_post (cost=0.00..25.00 rows=100 width=20)\n -> Hash (cost=20.50..20.50 rows=50 width=20)\n -> Index Scan using blog_tags_pkey on blog_tags\n\"\"\"\n```\n\n`EXPLAIN` выведет структуру запроса конкретной базы данных. Выше пример для Postgres, в другой базе вывод будет своим.\n\n## Ограничение состава загружаемых данных\n\nДовольно часто нам не требуются все поля модели — нужна всего пара полей из нескольких десятков. Тут пригодится метод `.values_list(имена, полей)`, который возвращает новый QuerySet. Его элементами будут кортежи со значениями указанных полей в указанном же порядке. Такой QuerySet удобно обходить в цикле с одновременной распаковкой:\n\n```python\nfor pid, title in Post.objects.values_list(\"id\", \"title\"):\n print(pid, \"|\", title)\n\n# SELECT \"blog_post\".\"id\",\n# \"blog_post\".\"title\"\n# FROM \"blog_post\"\n\n# Execution time: 0.000571s [Database: default]\n# => 1 | Intro\n# => 2 | Update\n```\n\nЕсли значений нужно не два и не три, то есть смысл использовать метод `.values(имена, полей)`. При обходе итогового QuerySet он даст уже не кортежи, а словари с ключами, совпадающими с именами указанных полей.\n\nОднако ни кортежи, ни словари не дают воспользоваться методами модели, а в некоторых ситуациях это все-таки требуется. Если точно известно, какие поля понадобятся при работе с моделью и при вызове ее методов, подойдет метод `.only(имена, полей)`: возращаемый им QuerySet выдает объекты модели, но в каждом объекте заполнены только указанные поля. Так мы можем использовать все возможности модели. Но следует помнить, что первое же обращение к полю, не указанному в вызове `.only()`, породит запрос. Он сработает для текущего объекта — запросит данные для этого поля и запомнит их на будущее:\n\n```python\nps = Post.objects.only(\"title\")\npost = ps[0] # Первый пост, загружаются только заголовки (и id)\n# SELECT \"blog_post\".\"id\",\n# \"blog_post\".\"title\"\n# FROM \"blog_post\"\n# LIMIT 1\n\n# Execution time: 0.000327s [Database: default]\n\npost.title\n# => 'Intro'\npost.body # Поле потребует загрузки\n# SELECT \"blog_post\".\"id\",\n# \"blog_post\".\"body\"\n# FROM \"blog_post\"\n# WHERE \"blog_post\".\"id\" = 1\n# LIMIT 21\n\n# Execution time: 0.000262s [Database: default]\n\n# => 'Hi, my name is Bob!'\n```\n\nИспользование `.only()` отлично показывает всю высокоуровневость ORM: можно просто обращаться к полям объекта и не думать, что и когда подгружается. Еще _view_ выводит посты блога в виде списка диалогов, причем выводит тело только для пары первых пунктов. Это очень лаконичное решение с точки зрения кода, которое еще и не слишком нагружает базу.\n\n## Ранняя загрузка связанных данных\n\nПроблема N+1 запросов - это классическая ловушка производительности при работе с ORM. Она возникает, когда код сначала делает один запрос для получения списка объектов, а затем для каждого объекта выполняет дополнительный запрос для получения связанных данных.\n\nНапример, мы открываем список из 100 постов блога, и для каждого поста нужно показать имя автора. Наивный подход приведет к тому, что сначала будет выполнен один запрос для получения постов, а затем для каждого поста будет выполнен отдельный запрос для получения информации об авторе. В итоге получится 101 запрос к базе данных (1 + 100), отсюда и название \"N+1\".\n\nВот как это выглядит в коде:\n\n```python\nposts = Post.objects.all() # 1 запрос\nfor post in posts:\n author_name = post.author.name # +N запросов\n```\n\nЗдесь один запрос получает все посты, а затем для каждого поста запрашивается автор.\n\nНельзя сказать, что тут все плохо. Авторы подгружаются по мере необходимости, если мы хотим вывести список заголовков постов и показать автора первых двух постов, то это приемлемый код.\n\nЕсли же все посты нужны вместе с авторами, нужно использовать метод `.select_related()`, который эквивалентен `JOIN` в SQL. Если при вызове метода не указывать аргументы, то будут присоединены все связанные таблицы. Если же указать имена связей, то указанные таблицы подгрузятся сразу, остальные — как обычно, по требованию. Это достаточно гибкое средство, особенно в сочетании с другими способами оптимизации:\n\n```python\npost = Post.objects.select_related(\"author\").only(\"title\", \"author__email\")[0]\nprint((post.title, post.author.email)) # => ('Intro', 'bob@blogs.org')\nprint(post.body) # => 'Hi, my name is Bob!'\nprint(post.author.first_name) # => 'Bob'\n```\n\nОбратите внимание, что первый запрос содержал только указанные поля — пусть даже и в двух таблицах — а также служебные поля вроде `id` и поля для связи таблиц. При этом с точки зрения наблюдателя `post` выглядит как цельный объект модели `Post`, а его атрибут `.author` — как цельный объект модели `User`. Все дополнительные данные прозрачно подгружаются по мере необходимости.\n\nКроме `select_related()`, в Django есть еще один похожий метод — `prefetch_related()`. С помощью обоих методов можно работать со связанными объектами, но немного в разных сценариях:\n\n* Метод `select_related()` используется для отношений типа \"один к одному\" (OneToOne) и \"многие к одному\" (ForeignKey). Например, такое отношение есть между постом и автором в блоге, потому что каждый пост написан одним автором. Чтобы получить информацию об авторе каждого поста, можно добавить `select_related()` в тот же запрос к базе данных. Метод `select_related()` работает через выполнение `SQL JOIN` и включение полей связанного объекта в оператор `SELECT`.\n\n* Метод `prefetch_related()` используется для отношений типа \"один ко многим\" и \"многие ко многим\" (ManyToManyField). Наглядный пример — пост в блоге, у которого может быть много комментариев (один ко многим), или теги постов (многие ко многим). Метод `prefetch_related()` выполняет отдельные запросы для каждого отношения и выполняет объединение уже в Python. Так мы заранее извлекаем связанные объекты, что помогает избежать проблемы N+1 запросов\n\n```python\nclass Post(models.Model):\n title = models.CharField(max_length=200)\n tags = models.ManyToManyField(\"Tag\")\n author = models.ForeignKey(\"Author\", on_delete=models.CASCADE)\n\n\nclass Comment(models.Model):\n post = models.ForeignKey(Post, related_name=\"comments\")\n text = models.TextField()\n author = models.ForeignKey(\"Author\", on_delete=models.CASCADE)\n\n\n# загрузит все связанные данные за 3 запроса\nposts = Post.objects.prefetch_related(\n \"comments\", # загрузит все комментарии\n \"tags\", # загрузит все теги\n \"comments__author\", # загрузит авторов комментариев\n).select_related(\"author\") # загрузит автора поста через JOIN\n```\n\nОба метода помогают избежать проблемы _N+1_, когда итерация по связанным объектам вызывает дополнительный запрос для каждого объекта. Но все таки один метод может быть более подходящим, чем другой — все зависит от типа отношения и конкретного случая.\n\nПроще говоря, оба метода позволяют сказать: «Я получаю данные и знаю, что еще мне понадобятся эти другие связанные данные, поэтому получи все сразу, а не ходи туда-сюда в базу данных».\n\n## Самостоятельная работа\n\n1. На примере моделей из учебного проекта постройте несколько сложных запросов и понаблюдайте, какие запросы и в какой момент делает ORM.\n2. Попробуйте пооптимизировать запросы с помощью описанных в уроке средств.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":4691,"name":"theory","url":"/courses/python-django-orm/lessons/about/theory_unit"}],"links":[{"id":422638,"name":"Django: The model layer\n","url":"https://docs.djangoproject.com/en/5.2/#the-model-layer"},{"id":422639,"name":"Что такое ORM?\n","url":"https://ru.hexlet.io/qna/glossary/questions/chto-takoe-orm"}],"ordered_units":[{"id":4691,"name":"theory","url":"/courses/python-django-orm/lessons/about/theory_unit"}],"id":2087,"slug":"about","state":"approved","name":"Введение","course_order":10,"goal":"Знакомимся с целями и задачами курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"В основе любой программы лежит определённая предметная область. Например, графический редактор имеет дело с геометрическими примитивами, такими как отрезки, круги или квадраты, а сервисы доставки еды занимаются логистикой.\n\nПредметная область включает в себя набор сущностей, которые взаимодействуют друг с другом, а также правила их взаимодействия, называемые бизнес-логикой. Правила, работающие в конкретной предметной области, никак не связаны ни с языком, ни с фреймворком, на которых пишется программа. Более того, они вообще не имеют никакого отношения к программированию и существуют независимо.\n\nВозьмём для примера Хекслет. Это проект, построенный вокруг обучения. Его предметная область включает множество сущностей, часть из которых используется в проекте. К ним относятся понятия: Профессия, Курс, Проект, Урок, Практика, Квиз. Это лишь малая часть понятий: всего на Хекслете более 300 таких сущностей. Все они подчиняются определённым бизнес-правилам, которые реализованы в коде. Например, пользователь не может одновременно вступать сразу в несколько профессий и должен иметь оплаченную подписку для доступа к профессиональным курсам. Таких правил в проектах сотни и тысячи. Во многом именно они определяют сложность кода.\n\nДля упрощения описания предметной области и хранения её данных в приложениях применяют ORM. Это специализированные фреймворки, которые определяют способ создания сущностей и их связывания. Вторая большая задача этих фреймворков – обеспечивать хранение данных, создаваемых в процессе работы.\n\nЭтот курс посвящён изучению основных концепций ORM. К ним относятся:\n\n* Миграции и схема\n* Модели и связи\n* Работа с формами\n* Динамические запросы и их построение\n* Обработка коллекций\n* Историчность\n* Мягкое удаление\n"},"id":238,"slug":"python-django-orm","challenges_count":3,"name":"Python: Django ORM","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите Django ORM – основной способ работы с данными в Django. Вы узнаете больше о моделях и их отображениях на базу данных, о миграциях и построении запросов. Вы разберетесь, как описывать связи между моделями и выполнять обратимые операции в режиме транзакций. Знания из этого курса позволят вам удобно работать с данными в проектах на Django.","kind":"basic","updated_at":"2026-01-20T11:38:04.884Z","language":"python","duration_cache":39660,"skills":["Работать с современной ORM","Строить сложные запросы с функциями агрегации и аннотаций","Анализировать эффективность использования ORM"],"keywords":[],"lessons_count":10,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTgzOSwicHVyIjoiYmxvYl9pZCJ9fQ==--1ed2cb32853ce5426058edc676c28bc93e2b6db9/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":7,"slug":"python","title":"Python-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":10,"duration_in_months":10},"id":7,"slug":"python","title":"Python-разработчик ","subtitle":"Изучите Python, Django, REST и Fast API для создания веб-приложений","subtitle_for_lists":"Изучите Python, Django, REST и Fast API для создания веб-приложений","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"python","price_text":"от 6 792 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"},{"stack":{"id":24,"slug":"python-django-development","title":"Django","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":300,"duration_in_months":2},"id":36,"slug":"python-django-developer","title":"Django","subtitle":"Навык создания веб-приложений на Django, необходимый для получения оффера на позицию Python-разработчика","subtitle_for_lists":"Изучите фреймворк Django для создания веб-приложений ","locale":"ru","current":true,"duration_in_months_text":"2 месяца","stack_slug":"python-django-development","price_text":"от 3 900 ₽","duration_text":"2 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0NiwicHVyIjoiYmxvYl9pZCJ9fQ==--5c088db10d02b94be027408f50ecf11c23d9d4cb/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Version%20control-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/python-django-orm/lessons/efficiency/theory_unit","version":"143505ecd123087a8fdfa4acb7147980e9d23d76","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Python: Django ORM</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Эффективное использование ORM</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Эффективное использование ORM","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Python: Django ORM"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Как и любое средство повышения уровня абстракции, ORM может выдавать не самые эффективные запросы в некоторых случаях. К сожалению, по-другому быть не может: тонко настроенные SQL-запросы находятся на существенно более низком уровне. В Django ORM можно переписывать код запроса руками, но есть ли в этом необходимость? Вовсе нет.</p>
<p>Для начала прочтем отрывок из документации к фреймворку:</p>
<ol>
<li>Сначала <strong>измерьте</strong>. У любого QuerySet можно запросить описание предполагаемых запросов с помощью вызова метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.explain()</code>. Тут пригодится умение читать SQL и понимать то, о чем рассказывает через <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">explain</code> ваша СУБД</li>
<li>Добавьте <strong>индексы</strong>. SQL explain часто может сказать, что в каком-то месте делается "full scan" то есть полный перебор, а это означает, что где-то не хватает индексов. Стоит подумать о том, чтобы оные добавить. Подробности можно почитать в <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://docs.djangoproject.com/en/5.2/ref/models/indexes/" rel="noopener noreferrer" target="_blank">документации</a></li>
<li>Делайте как можно больше работы силами СУБД. Аннотируйте, агрегируйте. СУБД знает, как это оптимизировать и как кэшировать результаты.</li>
<li>В крайнем случае пишите <strong>необходимый минимум</strong> SQL.</li>
<li>Знайте, как работают QuerySets и помните:</li>
</ol>
<ul>
<li><strong>Насколько</strong> QuerySets ленив — он не делает лишних запросов, пока вы не попросите</li>
<li><strong>В какой момент</strong> QuerySet вычисляется (финализируется)</li>
<li><strong>Каким образом</strong> данные представлены в памяти</li>
<li><strong>Как</strong> QuerySet кеширует результаты запросов и подзапросов</li>
</ul>
<p>Следует помнить, что многие ситуации разрешаются задолго до того, как вы дойдете до написания SQL.</p>
<h2 id="heading-2-1">Explain</h2>
<p>Один из ключевых приемов для анализа запросов это функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">EXPLAIN</code>. Он показывает, как база данных планирует выполнить ваш запрос. Он раскрывает такие важные детали, как использование индексов, порядок соединения таблиц и предполагаемую стоимость операций. В Django метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.explain()</code> предоставляет удобный доступ к этой функциональности, помогая понять, почему некоторые запросы могут работать медленнее других и как их можно оптимизировать.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">from django.db.models import Count, Q
from django.db import connection
# Создаем сложный запрос для анализа
complex_query = (
Post.objects.annotate(comment_count=Count("comments"), tag_count=Count("tags"))
.filter(Q(comment_count__gte=10) | Q(tag_count__gte=5))
.select_related("author")
)
print(complex_query.explain())
"""
-> Hash Join (cost=35.50..60.53 rows=100 width=40)
-> Seq Scan on blog_post (cost=0.00..25.00 rows=100 width=20)
-> Hash (cost=20.50..20.50 rows=50 width=20)
-> Index Scan using blog_tags_pkey on blog_tags
"""</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">EXPLAIN</code> выведет структуру запроса конкретной базы данных. Выше пример для Postgres, в другой базе вывод будет своим.</p>
<h2 id="heading-2-2">Ограничение состава загружаемых данных</h2>
<p>Довольно часто нам не требуются все поля модели — нужна всего пара полей из нескольких десятков. Тут пригодится метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.values_list(имена, полей)</code>, который возвращает новый QuerySet. Его элементами будут кортежи со значениями указанных полей в указанном же порядке. Такой QuerySet удобно обходить в цикле с одновременной распаковкой:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">for pid, title in Post.objects.values_list("id", "title"):
print(pid, "|", title)
# SELECT "blog_post"."id",
# "blog_post"."title"
# FROM "blog_post"
# Execution time: 0.000571s [Database: default]
# => 1 | Intro
# => 2 | Update</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если значений нужно не два и не три, то есть смысл использовать метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.values(имена, полей)</code>. При обходе итогового QuerySet он даст уже не кортежи, а словари с ключами, совпадающими с именами указанных полей.</p>
<p>Однако ни кортежи, ни словари не дают воспользоваться методами модели, а в некоторых ситуациях это все-таки требуется. Если точно известно, какие поля понадобятся при работе с моделью и при вызове ее методов, подойдет метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.only(имена, полей)</code>: возращаемый им QuerySet выдает объекты модели, но в каждом объекте заполнены только указанные поля. Так мы можем использовать все возможности модели. Но следует помнить, что первое же обращение к полю, не указанному в вызове <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.only()</code>, породит запрос. Он сработает для текущего объекта — запросит данные для этого поля и запомнит их на будущее:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">ps = Post.objects.only("title")
post = ps[0] # Первый пост, загружаются только заголовки (и id)
# SELECT "blog_post"."id",
# "blog_post"."title"
# FROM "blog_post"
# LIMIT 1
# Execution time: 0.000327s [Database: default]
post.title
# => 'Intro'
post.body # Поле потребует загрузки
# SELECT "blog_post"."id",
# "blog_post"."body"
# FROM "blog_post"
# WHERE "blog_post"."id" = 1
# LIMIT 21
# Execution time: 0.000262s [Database: default]
# => 'Hi, my name is Bob!'</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Использование <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.only()</code> отлично показывает всю высокоуровневость ORM: можно просто обращаться к полям объекта и не думать, что и когда подгружается. Еще <em>view</em> выводит посты блога в виде списка диалогов, причем выводит тело только для пары первых пунктов. Это очень лаконичное решение с точки зрения кода, которое еще и не слишком нагружает базу.</p>
<h2 id="heading-2-3">Ранняя загрузка связанных данных</h2>
<p>Проблема N+1 запросов - это классическая ловушка производительности при работе с ORM. Она возникает, когда код сначала делает один запрос для получения списка объектов, а затем для каждого объекта выполняет дополнительный запрос для получения связанных данных.</p>
<p>Например, мы открываем список из 100 постов блога, и для каждого поста нужно показать имя автора. Наивный подход приведет к тому, что сначала будет выполнен один запрос для получения постов, а затем для каждого поста будет выполнен отдельный запрос для получения информации об авторе. В итоге получится 101 запрос к базе данных (1 + 100), отсюда и название "N+1".</p>
<p>Вот как это выглядит в коде:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">posts = Post.objects.all() # 1 запрос
for post in posts:
author_name = post.author.name # +N запросов</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Здесь один запрос получает все посты, а затем для каждого поста запрашивается автор.</p>
<p>Нельзя сказать, что тут все плохо. Авторы подгружаются по мере необходимости, если мы хотим вывести список заголовков постов и показать автора первых двух постов, то это приемлемый код.</p>
<p>Если же все посты нужны вместе с авторами, нужно использовать метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.select_related()</code>, который эквивалентен <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">JOIN</code> в SQL. Если при вызове метода не указывать аргументы, то будут присоединены все связанные таблицы. Если же указать имена связей, то указанные таблицы подгрузятся сразу, остальные — как обычно, по требованию. Это достаточно гибкое средство, особенно в сочетании с другими способами оптимизации:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">post = Post.objects.select_related("author").only("title", "author__email")[0]
print((post.title, post.author.email)) # => ('Intro', 'bob@blogs.org')
print(post.body) # => 'Hi, my name is Bob!'
print(post.author.first_name) # => 'Bob'</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Обратите внимание, что первый запрос содержал только указанные поля — пусть даже и в двух таблицах — а также служебные поля вроде <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">id</code> и поля для связи таблиц. При этом с точки зрения наблюдателя <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">post</code> выглядит как цельный объект модели <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Post</code>, а его атрибут <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">.author</code> — как цельный объект модели <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">User</code>. Все дополнительные данные прозрачно подгружаются по мере необходимости.</p>
<p>Кроме <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">select_related()</code>, в Django есть еще один похожий метод — <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">prefetch_related()</code>. С помощью обоих методов можно работать со связанными объектами, но немного в разных сценариях:</p>
<ul>
<li>
<p>Метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">select_related()</code> используется для отношений типа "один к одному" (OneToOne) и "многие к одному" (ForeignKey). Например, такое отношение есть между постом и автором в блоге, потому что каждый пост написан одним автором. Чтобы получить информацию об авторе каждого поста, можно добавить <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">select_related()</code> в тот же запрос к базе данных. Метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">select_related()</code> работает через выполнение <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">SQL JOIN</code> и включение полей связанного объекта в оператор <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">SELECT</code>.</p>
</li>
<li>
<p>Метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">prefetch_related()</code> используется для отношений типа "один ко многим" и "многие ко многим" (ManyToManyField). Наглядный пример — пост в блоге, у которого может быть много комментариев (один ко многим), или теги постов (многие ко многим). Метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">prefetch_related()</code> выполняет отдельные запросы для каждого отношения и выполняет объединение уже в Python. Так мы заранее извлекаем связанные объекты, что помогает избежать проблемы N+1 запросов</p>
</li>
</ul>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField("Tag")
author = models.ForeignKey("Author", on_delete=models.CASCADE)
class Comment(models.Model):
post = models.ForeignKey(Post, related_name="comments")
text = models.TextField()
author = models.ForeignKey("Author", on_delete=models.CASCADE)
# загрузит все связанные данные за 3 запроса
posts = Post.objects.prefetch_related(
"comments", # загрузит все комментарии
"tags", # загрузит все теги
"comments__author", # загрузит авторов комментариев
).select_related("author") # загрузит автора поста через JOIN</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Оба метода помогают избежать проблемы <em>N+1</em>, когда итерация по связанным объектам вызывает дополнительный запрос для каждого объекта. Но все таки один метод может быть более подходящим, чем другой — все зависит от типа отношения и конкретного случая.</p>
<p>Проще говоря, оба метода позволяют сказать: «Я получаю данные и знаю, что еще мне понадобятся эти другие связанные данные, поэтому получи все сразу, а не ходи туда-сюда в базу данных».</p>
<h2 id="heading-2-4">Самостоятельная работа</h2>
<ol>
<li>На примере моделей из учебного проекта постройте несколько сложных запросов и понаблюдайте, какие запросы и в какой момент делает ORM.</li>
<li>Попробуйте пооптимизировать запросы с помощью описанных в уроке средств.</li>
</ol></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/python?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Python-разработчик </p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Python, Django, REST и Fast API для создания веб-приложений</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png" alt="Python-разработчик " loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 6 792 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/python-django-developer?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">2 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Django</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите фреймворк Django для создания веб-приложений </p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0NiwicHVyIjoiYmxvYl9pZCJ9fQ==--5c088db10d02b94be027408f50ecf11c23d9d4cb/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Version%20control-bro.png" alt="Django" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/python-django-orm/lessons/efficiency/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 10</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/python-django-orm/lessons/efficiency/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-D8AK0-_C.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-DOv3_-Z_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>