Было время, когда ORM Django считался очень милым, но совершенно глупым.
Хотя, возможность производить Annotate и Aggregate была в нем с незапамятных времен.
А в версии 1.8 была добавлена возможность использовать функции базы данных внутри выражений запросов.
И, конечно, если начинающий джангист не побоялся и прочитал введение к этим строкам, он может смело читать дальше: статья ориентирована именно на новичков.
Некоторое время назад я столкнулся с задачей выбора пользователем значений из таблицы.
При этом эти значения должны соответствовать определенному регулярному выражению.
Но на этом условие не заканчивается: вам нужно извлечь подстроку из выделенных выражений.
Опять же по обычному графику.
Я сделал это довольно быстро и хотел поделиться своим опытом с теми, кто пока не умеет использовать Annotate и Query Expressions на практике.
Попробую описать ситуацию точнее: У нас почти стандартная модель пользователей.
Некоторые пользователи имеют разные имена пользователей.
Например, менеджер, vasyaTheDirector, vovaProg и т.п.
А вот у коммерческих пользователей имена имеют формат {CountryCode}{RandomUniqueNumber}.
Например, РУ2525 или ЭС1672. Значит нам нужно вытащить из базы всех коммерческих пользователей, но не всю информацию, а только уникальные номера без кодов стран.
Задача, безусловно, интересная для начинающих джангистов.
Хотя для разработчиков среднего уровня это может быть не совсем типично.
Начнем с простого: чтобы получить всех пользователей, чьи имена начинаются с двухбуквенного кода страны, вы можете использовать простую операцию фильтра с ключом __iregex в имени поля.
Получаем такой список: [ , , , , , , , , < User: UK124> , , ] Дальше интереснее.from django.contrib.auth import get_user_model User = get_user_model() queryset = User.objects.filter(username__iregex=r'^[A-Z]{2}\d+$')
Django позволяет создавать аннотации к получаемым вами значениям.
Например, нам нужно подсчитать количество книг, связанных с пользователем через ForeignKey. Мы можем выполнить User.books.all()count() или получить значение непосредственно в наборе запросов с помощью Annotate. Мы объявим поле book_count, которое будет доступно нам как свойство результирующего экземпляра User или как ключ словаря.
Посмотрим, как это будет выглядеть, не на абстрактном примере с книгами, а в контексте нашей задачи.
from django.db.models import Func
queryset = User.objects.annotate(username_index=Func()).
filter(username__iregex=r'^[A-Z]{2}\d+$')
В Django есть различные функции для аннотации значений.
Например, Макс, Мин, Среднее, Количество.
Они являются частью механизма выражений запроса.
Эти специальные выражения можно использовать как для описания запроса, так и для изменения значений при их получении.
Начиная с версии 1.8 у нас есть возможность использовать встроенные функции базы данных.
Например, нам нужно модифицировать полученные строки.
Это означает, что мы будем использовать функции, связанные с регулярными выражениями.
Я использую PostgreSQL версии 9.5, поэтому мне нужно найти функцию, которая получит подстроку из строки.
Эту функцию мы находим в официальная документация .
Функция называется: подстрока .
from django.db.models import Func, F, Value
queryset = User.objects.annotate(username_index=Func(F('username'), Value('(\d+)'), function='substring'))).
filter(username__iregex=r'^[A-Z]{2}\d+$')
Как видите, Func принимает три аргумента:
- В F() заключено имя поля, которое мы модифицируем (фактически, значение этого поля будет передано в подстроку)
- Шаблон, используемый для поиска подстроки
- Имя функции PostgreSQL, которой будут переданы предыдущие аргументы.
from django.db.models import Func, F, Value
queryset = User.objects.annotate(username_index=Func(F('username'), Value('(\d+)'), function='substring'))).
filter(username__iregex=r'^[A-Z]{2}\d+$').
values_list('username_index', flat=True)
Получаем следующий вывод: ['123', '124', '125', '123', '124', '125', '126', '123', '124', '1234', '12345'] Соответственно, если нам нужно получить уникальные номера пользователей для конкретной страны, мы меняем
username__iregex=r'^[A-Z]{2}\d+$'
на
username__iregex=r'^RU\d+$'.
Ну а теперь самое интересное.
Как вы думаете, какой SQL-запрос выполняет наш код? SELECT substring("my_users_user".
"username", (\d+)) AS "username_index" FROM "my_users_user" WHERE "my_users_user".
"username"::text ~* ^[A-Z]{2}\d+$
Как видите, запрос красивый и не требует срочности.
реанимация оптимизация.
Возвращаясь к теме проблем DJango ORM, изложенной в начале статьи, хотелось бы подчеркнуть, что Annotate и Aggregate существуют в Django уже очень давно.
И оказывается, что не все умели их готовить.
Хотя возможность выполнять функции базы данных без написания SQL-запросов появилась относительно недавно.
И мы можем сделать еще более красивые вещи.
P.S.
Если вы хотите получить данные в определенном формате, вы можете изменить код следующим образом: from django.db.models import IntegerField, ExpressionWrapper
from django.db.models import Func, F, Value
queryset = User.objects.annotate(username_index=ExpressionWrapper(Func(F('username'), Value('(\d+)'), function='substring'), output_field=IntegerField()))).
filter(username__iregex=r'^[A-Z]{2}\d+$').
values_list('username_index', flat=True)
Вывод будет таким: [123, 124, 125, 123, 124, 125, 126, 123, 124, 1234, 12345] Мы обернули Func() в ExpressionWrapper и указали ожидаемый тип данных в output_field=IntegerField().
В результате мы получили список целых чисел, а не строк.
Теги: #python #django #оптимизация запросов #postgresql #python #postgresql #django
-
История Логова Дракона
19 Oct, 24 -
Полезные И Не Очень Полезные Госуслуги
19 Oct, 24 -
Особенности Протокола В Io-Играх
19 Oct, 24 -
Async/Await В C#: Подводные Камни
19 Oct, 24