Глубокое погружение в эффективные запросы к базе данных

Скорость, с которой работает ваше приложение django, во многом зависит от скорости, с которой приложение может получать результаты из базы данных. Несмотря на то, что существуют различные способы оптимизации вашего приложения django, в этой статье будет рассказано, как вы можете использовать возможности наборов запросов для получения правильных результатов.
В этом руководстве мы будем использовать модель продукта для интернет-магазина.

class Product(models.Model):
    RATING_CHOICES = [
        ('*****', '5'),
        ('****', '4'),
        ('***', '3'),
        ('**', '2'),
        ('*', '1'),
    ]

    name = models.CharField(max_length=200)
    category = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    discounted_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
    description = models.TextField()
    image = models.ImageField(upload_to='products/', null=True,blank=True)
    rating = models.CharField(max_length=5, choices=RATING_CHOICES, null =True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

Напишем команду управления, которая добавляет в базу данных 50 товаров. Добавьте файл product.py в каталог команд внутри пакета управления.

.ecommerce
├── admin.py
├── apps.py
├── __init__.py
├── management
│   ├── commands
│   │   ├── __init__.py
│   │   └── product.py
│   └── __init__.py

Добавьте следующий код в файл product.py.

from django.core.management.base import BaseCommand
from ecommerce.models import Product
from random import choice, randint

class Command(BaseCommand):
    help = 'Add 100 products'

    def handle(self, *args, **options):
        for _ in range(50):
            name = "Product {}".format(randint(1, 50))
            category = "Fashion"
            price = randint(10, 100)
            description = "The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc."
            rating = choice(Product.RATING_CHOICES)[0]

            product = Product(
                name=name,
                category=category,
                price=price,
                description=description,
                rating=rating
            )
            product.save()

        self.stdout.write(self.style.SUCCESS('products added'))

Чтобы запустить команду, скрипт, введите следующую команду

python manage.py product

Если вам нужно добавить больше продуктов в другую категорию, измените название категории и повторите команду.

Понимание наборов запросов

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

Получение объектов

Django использует атрибут объектов для представления менеджера, который управляет взаимодействием между базой данных и моделями.

Чтобы получить все объекты продукта, используйте метод all() .

>>> products = Product.objects.all()
>>> 

Этот запрос вернет набор запросов, содержащий все объекты Product.

Вы можете настраивать запросы, упорядочивая, разделяя или фильтруя запрос. Например, чтобы получить продукты с определенной категорией, используйте метод filter():

>>> products = Product.objects.filter(category='Fashion')

Используйте метод order_by(), чтобы указать критерии для упорядочения извлеченных объектов модели. Например, давайте создадим запрос, который упорядочивает товары по цене в порядке убывания.

>>> products = Product.objects.order_by('price') #objects sorted based on their price, from lowest to highest.
>>> [p.price for p in products]
[Decimal('3.00'), Decimal('10.00'), Decimal('10.00'), Decimal('15.00'), Decimal('19.00'),...)]
>>> 

Используйте метод нарезки, чтобы получить последние пять продуктов в базе данных.

>>> products = Product.objects.order_by('-created_at')[:5]

Приведенный выше запрос использует поле created_at для получения последних пяти продуктов, добавленных в базу данных; порядок -created_at гарантирует, что самые последние продукты появляются первыми в наборе запросов.

Наборы запросов Django ленивы:

Django QuerySets использует ленивую загрузку, что означает, что фактический запрос к базе данных будет выполняться только тогда, когда вам понадобятся данные. Эта отложенная оценка позволяет комбинировать несколько фильтров, аннотаций и порядков без немедленной выборки данных из базы данных. Это ленивое вычисление помогает свести к минимуму ненужные операции с базой данных и повысить производительность за счет доступа к базе данных только при необходимости.

Например, рассмотрим следующий набор запросов, включающий цепочку из нескольких фильтров:


>>> filtered_products = Product.objects.filter(rating = '5' ).order_by("-price")
>>> len(connection.queries)
0
>>> 

На данный момент набор запросов еще не был оценен, а количество запросов равно 0; когда мы определяем набор запросов, набор запросов действует как «ленивый» объект, который содержит информацию о запросе. Запрос не попадет в базу данных, пока мы не выполним действие, такое как повторение набора запросов, доступ к определенным элементам или выполнение таких методов, как `count()` или `get()`.

>>> [f" The price of {product.name} is  ${product.price}" for product in filtered_products]
[' The price of Product 280 is  $95.00', ' The price of Product 125 is  $91.00', ' The price of Product 761 is  $91.00', ' The price of Product 87 is  $88.00', ' The price of Product 573 is  $87.00', ' The price of Product 616 is  $80.00', ' The price of Product 611 is  $78.00', ' The price of Product 31 is  $77.00', ' The price of Product 433 is  $73.00', ' The price of Product 348 is  $73.00', ' The price of Product 651 is  $62.00', ' The price of Product 121 is  $62.00', ' The price of Product 859 is  $61.00', ' The price of Product 163 is  $56.00', ' The price of Product 718 is  $50.00', ' The price of Product 32 is  $46.00', ' The price of Product 415 is  $44.00', ' The price of Product 591 is  $37.00', ' The price of Product 414 is  $34.00', ' The price of Product 926 is  $29.00', ' The price of Product 822 is  $28.00', ' The price of Product 459 is  $28.00', ' The price of Product 691 is  $27.00', ' The price of Product 781 is  $17.00']
>>> len(connection.queries)
1
>>> 

Наборы запросов кэшируются

В Django наборы запросов кэшируются, чтобы уменьшить доступ к базе данных. Когда набор запросов оценивается в первый раз и результат извлекается из базы данных, Django сохраняет результат в кеше наборов запросов
Однако немедленные будущие оценки того же набора запросов в рамках одного и того же цикла запроса/ответа будут использовать кешировать результат вместо повторного обращения к базе данных.
Давайте рассмотрим пример с использованием модели Product:

>>> products = Product.objects.filter(rating = '5' ).order_by("-price")
>>> [product.name for product in filter_prodcuts] #this hits the database
>>> len(connection.queries)
1
>>> [product.price for product in products] # this uses the cached data

>>> len(connection.queries)  # number of queries remain the same
1

Важно отметить, что кэш набора запросов зависит от состояния базовых данных. Предположим, что данные изменились; например, новая запись сохраняется в течение того же цикла запроса/ответа.

Кэш наборов запросов устаревает, и новый запрос оценивается для обеспечения наиболее актуальных результатов.

Поиск полей

Django также поддерживает поиск полей для использования в запросах.

exact: используется для выполнения запроса на точное соответствие, используйте нотацию с двойным подчеркиванием (__), за которой следует имя поля. Например, чтобы получить продукты с определенным названием,

>>> product=Product.objects.filter(name__exact='Product 12')
>>> product
<QuerySet [<Product: Product 12>]>
>>> 

iexact:используется для выполнения точного cнечувствительного к ASE совпадения

>>> product = Product.objects.filter(name__iexact='PRODUCT 12')
>>> product
<QuerySet [<Product: Product 12>]> # Product 12 is matched even though the query is PRODUCT 12 
>>> 

Продукт 12 соответствует, хотя запрос является ПРОДУКТ 12

Поиск полей сравнения

Django поддерживает различные поисковые операции сравнения, такие как:

  • больше, чем (gt)
  • больше или равно (gte)
  • меньше чем (л)
  • меньше или равно (lte).

Например, давайте получим товары с ценой меньше 20:

>>> from ecommerce.models import Product
>>> products = Product.objects.filter(price__lt=20)
>>> products
<QuerySet [<Product: Product 26>, <Product: Product 27>, <Product: Product 15>, <Product: Product 24>, <Product: name>]>
>>> 

Поиск полей даты и времени. Django предоставляет полезные возможности поиска полей даты и времени. Вы можете использовать поиск по дате, году, месяцу, дню, часу, минуте и секунде, чтобы фильтровать товары по определенным датам или времени.

Например, давайте получим продукты, созданные в текущем году:

>>> from django.utils import timezone
>>> products=Product.objects.filter(created_at__year= timezone.now().year)
>>> products
<QuerySet [<Product: Product 17>, <Product: Product 13>, <Product: Product 2>, <Product: Product 8>, <Product: Product 28>, <Product: Product 21>, <Product: Product 11>, <Product: Product 32>, <Product: Product 28>, <Product: Product 16>, <Product: Product 26>, <Product: Product 22>, <Product: Product 34>, <Product: Product 26>, <Product: Product 22>, <Product: Product 28>, <Product: Product 20>, <Product: Product 48>, <Product: Product 27>, <Product: Product 33>, '...(remaining elements truncated)...']>
>>> 

Агрегации и аннотации:

Наборы запросов поддерживают различные функции агрегирования, такие как count(), sum(), avg() и аннотации с использованием annotate().
Эти функции позволяют выполнять вычисления непосредственно в базе данных, а не в коде Python, оптимизируя производительность запросов.
Например, вы можно использовать avg() to для получения средней цены продуктов в определенной категории без необходимости выбирать отдельные продукты

>>> avg_price = Product.objects.filter(category='Fashion').aggregate(avg_price=Avg('price'))
>>> print(avg_price['avg_price'])
53.1200000000000
>>> 

Оштрафуйте продукт с самой высокой ценой с помощью Max

>>> from django.db.models import Max
>>> max_price = Product.objects.aggregate(max_price=Max('price'))
>>> print(f"The highest price of a product is {max_price['max_price']}")
The highest price of a product is 100

Заключение

В этой статье были рассмотрены некоторые основные концепции ORM django. Прочтите эту статью, чтобы узнать больше о том, как выполнять оптимизацию с использованием других методов, таких как предварительная выборка, индексация и т. д.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу