2012-02-03 2 views
4

У меня есть some weird query, поэтому мне нужно выполнить необработанный SQL. Дело в том, что этот запрос становится все больше и больше и с множеством дополнительных фильтров (упорядочение, критерии столбцов и т. Д.).Django: filter a RawQuerySet

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

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)) 

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

#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)) 

Итак, мне не нравится h ой он начал расти, я знаю 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 инициализации или кэш может сделать трюк, но не были в состоянии сделать это.

+0

Что такое «странное» о том, что запрос, который нужно использовать сырые SQL? Вы также можете постепенно создавать QuerySet. – Kekoa

ответ

14

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

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

+0

Я думаю, что дополнительный() может сделать трюк. Не знал об этом, спасибо! – santiagobasulto

+0

Как насчет безопасности с помощью дополнительных()? Если вы посмотрите на запрос, мне нужно передать параметр JOIN. Сложная компиляция int() работает? – santiagobasulto

+2

Прочтите раздел в документации 'extra' о' params'. Если вы используете 'params', Django позаботится об инъекции точно так же, как при обычном наборе запросов, но если вы уже используете' raw', вы должны были позаботиться об этом уже для любых переданных параметров. –

0

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

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

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

+0

Да, спасибо, я знал это, но забыл. Это все равно не работает. Я не могу отфильтровать rawqueryset. – santiagobasulto

+2

Да, это не сработает для RawQuerySet, но если вы можете переписать его, просто используя обычные наборы запросов, вот что я хотел бы предложить. Сырые SQL и django не смешиваются хорошо. – Kekoa

0

Там еще один вариант: включить 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. Прекрасно работает!

+0

У меня такое чувство, что вы не должны перебирать всю базу данных. Удивите случай, когда n> 2^32, это займет навсегда. –

+0

Зависит от размера результатов, конечно. На практике я обнаружил, что вызовы БД относительно дороги, а ЦП дешев. Если ОП индексирует более 2^32 камер или других продуктов ... Я полностью исправляюсь! –

0

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

FilteredRawQuerySet реализован в виде комбинации Django модели 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

0

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

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