Django: фильтр RawQuerySet

у меня есть какой-то странный запрос, поэтому я должен выполнить raw SQL. Дело в том, что этот запрос становится все больше и больше и с большим количеством дополнительных фильтров (порядок, критерии столбцов и т. д.).

Итак, учитывая этот запрос:

SELECT DISTINCT Camera.* FROM Camera c
     INNER JOIN cameras_features fc1 ON c.id = fc1.camera_id AND fc1.feature_id = 1
     INNER JOIN cameras_features fc2 ON c.id = fc2.camera_id AND fc2.feature_id = 2

это примерно код Python:

def get_cameras(features):
  query = "SELECT DISTINCT Camera.* FROM Camera c"
  i = 1
  for f in features:
    alias_name = "fc%s" % i
    query += "INNER JOIN cameras_features %s ON c.id = %s.camera_id AND %s.feature_id = " % (alias_name,alias_name,alias_name)
    query += " %s "
    i += 1
  return Camera.objects.raw(query, tuple(features))

это работает отлично, но мне нужно добавить больше фильтров и заказа, например, предположим, что мне нужно фильтровать по цвету и порядку по цене, он начинает grow:

#extra_filters is a list of tuples like:
# [('price', '=', '12'), ('color' = 'blue'), ('brand', 'like', 'lum%']
def get_cameras_big(features,extra_filters=None,order=None):
  query = "SELECT DISTINCT Camera.* FROM Camera c"
  i = 1
  for f in features:
    alias_name = "fc%s" % i
    query += "INNER JOIN cameras_features %s ON c.id = %s.camera_id AND %s.feature_id = " % (alias_name,alias_name,alias_name)
    query += " %s "
    i += 1
  if extra_filters:
    query += " WHERE "
    for ef in extra_filters:
      query += "%s %s %s" % ef #not very safe, refactoring needed
  if order:
    query += "order by %s" % order

  return Camera.objects.raw(query, tuple(features))

так, мне не нравится, как он начал расти, я знаю Model.objects.raw() возвращает RawQuerySet, поэтому я хотел бы сделать что-то вроде этого:

queryset = get_cameras( ... )
queryset.filter(...)
queryset.order_by(...)

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

raw_query_set = get_cameras( ... )
camera.objects.filter(id__in(raw_query_set.ids)) #don't know if it works, but you get the idea

Я думаю, что что-то с QuerySet init или кэш может сделать трюк, но не смог сделать он.

5 ответов


.raw() является конечной точкой. Django ничего не может сделать с queryset, потому что это потребует возможности каким-то образом проанализировать ваш SQL обратно в DBAPI, который он использует для создания SQL в первую очередь. Если вы используете .raw() это полностью на вас, чтобы построить точный SQL вам нужно.

если вы можете каким-то образом уменьшить свой запрос во что-то, что может быть обработано .extra() вместо. Вы можете создать любой запрос, который вам нравится, с помощью API Django, а затем закрепить дополнительный SQL с помощью .extra(), но это будет ваш единственный путь.


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

queryset = get_cameras( ... )
queryset = queryset.filter(...)
queryset = queryset.order_by(...)

это приведет к созданию более сложного queryset.


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

results_list.sort(key=lambda item:item.some_numeric_field, reverse=True)

и фильтрации такой...

filtered_results = [i for i in results_list if i.some_field == 'something'])

...все программно. Я делаю это тонну, чтобы минимизировать запросы db. Отлично работает!


я реализовал Django raw queryset, который поддерживает filter(), order_by(), values() и values_list(). Он не будет работать для любого необработанного запроса, но для типичного SELECT С INNER JOIN или LEFT JOIN он должен работать.

на FilteredRawQuerySet реализован как комбинация Django model QuerySet и RawQuerySet, где база (левая часть) SQL-запроса генерируется через RawQuerySet, а WHERE и ORDER BY директивы generared по QuerySet:

https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/query.py

он работает с Django 1.8 .. 1.11.

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

вот пример использование:

https://github.com/Dmitri-Sintsov/djk-sample/search?l=Python&q=filteredrawqueryset&type=&utf8=%E2%9C%93


еще одна вещь, которую вы можете сделать, это то, что если вы не можете преобразовать его в обычный QuerySet, это создать представление в вашей базе данных. Он в основном выполняет запрос в представлении при доступе к нему. В Django вы создадите неуправляемую модель для присоединения к представлению. С этой моделью вы можете применить фильтр, как если бы это была обычная модель. С помощью внешних ключей вы бы установили arg on_delete в models.DO_NOTHING.

больше информации о неуправляемом модели: https://docs.djangoproject.com/en/2.0/ref/models/options/#managed