Забавные Запросы С Помощью Аннотаций И Выражений Запросов Django Orm

Было время, когда ORM Django считался очень милым, но совершенно глупым.

Хотя, возможность производить Annotate и Aggregate была в нем с незапамятных времен.

А в версии 1.8 была добавлена возможность использовать функции базы данных внутри выражений запросов.

И, конечно, если начинающий джангист не побоялся и прочитал введение к этим строкам, он может смело читать дальше: статья ориентирована именно на новичков.

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

При этом эти значения должны соответствовать определенному регулярному выражению.

Но на этом условие не заканчивается: вам нужно извлечь подстроку из выделенных выражений.

Опять же по обычному графику.

Я сделал это довольно быстро и хотел поделиться своим опытом с теми, кто пока не умеет использовать Annotate и Query Expressions на практике.

Попробую описать ситуацию точнее: У нас почти стандартная модель пользователей.

Некоторые пользователи имеют разные имена пользователей.

Например, менеджер, vasyaTheDirector, vovaProg и т.п.

А вот у коммерческих пользователей имена имеют формат {CountryCode}{RandomUniqueNumber}.

Например, РУ2525 или ЭС1672. Значит нам нужно вытащить из базы всех коммерческих пользователей, но не всю информацию, а только уникальные номера без кодов стран.

Задача, безусловно, интересная для начинающих джангистов.

Хотя для разработчиков среднего уровня это может быть не совсем типично.

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

  
  
  
  
  
  
  
   

from django.contrib.auth import get_user_model User = get_user_model() queryset = User.objects.filter(username__iregex=r'^[A-Z]{2}\d+$')

Получаем такой список: [ , , , , , , , , < User: UK124> , , ] Дальше интереснее.

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 принимает три аргумента:

  1. В F() заключено имя поля, которое мы модифицируем (фактически, значение этого поля будет передано в подстроку)
  2. Шаблон, используемый для поиска подстроки
  3. Имя функции 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

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.