2012-02-03 116 views
4

我有some weird query,所以我必須執行原始SQL。問題是這個查詢變得越來越大,並且有很多可選的過濾器(排序,列標準等)。Django:過濾一個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 

我想的東西與查詢集INIT或高速緩存可以做的伎倆,但一直沒能做到這一點。

+0

什麼是「怪異」關於查詢,您需要使用原始SQL?你也可以逐漸構建一個QuerySet。 – Kekoa 2012-02-03 21:34:48

回答

14

.raw()是一個終點。 Django無法對查詢集進行任何操作,因爲這需要能夠以某種方式將您的SQL解析到首先用於創建SQL的DBAPI中。如果您使用.raw(),則完全在於您構建所需的確切SQL。

如果你能以某種方式減少你的查詢到.extra()可以處理的東西。您可以使用Django的API構建任何您喜歡的查詢,然後使用.extra()來添加額外的SQL,但那將是您唯一的解決方法。

+0

我認爲extra()可能會訣竅。不知道,謝謝! – santiagobasulto 2012-02-03 21:34:46

+0

帶額外()的安全性如何?如果你看看我需要傳遞參數給JOIN的查詢。一個int()複合只是起作用? – santiagobasulto 2012-02-03 21:40:14

+2

閱讀關於'params'的'extra'文檔部分。如果你使用'params',Django像正常的查詢集一樣處理注入,但是你已經使用'raw',你應該已經爲所有傳入的參數保留了注意。 – 2012-02-03 21:50:34

0

要構建一個查詢集,每個方法都會返回一個新的查詢集,因此每次添加時都需要存儲新的查詢集。所以改變你的僞代碼::

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

這將會產生一個更復雜的查詢集。

+0

是的,謝謝,我知道,但忘了。反正它不起作用。我無法過濾rawqueryset。 – santiagobasulto 2012-02-03 21:44:39

+2

是的,它不適用於RawQuerySet,但如果你可以重寫它只使用普通的查詢集,那就是我的建議。原始SQL和django不能很好地混合。 – Kekoa 2012-02-03 21:56:48

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

.. 。所有程序。我一直在做這一噸,以儘量減少數據庫請求。很棒!

+0

我有這種感覺,你不應該遍歷整個數據庫。不要奇怪的情況下,> 2^32,這將需要永遠。 – 2014-03-18 00:19:53

+0

當然取決於結果的大小。在實踐中,我發現數據庫調用相對昂貴且CPU便宜。如果OP的索引超過2^32相機或其他產品......我完全糾正! – 2015-05-11 21:27:39

0

我實現了Django原始查詢集,它支持filter(),order_by(),values()values_list()。它不適用於任何RAW查詢,但對於典型的SELECT,其中一些INNER JOINLEFT JOIN它應該工作。

FilteredRawQuerySet被實現爲Django的模型QuerySetRawQuerySet,其中經由RawQuerySet生成SQL查詢的基礎(左側部分),而WHEREORDER BY指令由QuerySet generared的組合:

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

你可以做的另一件事是,如果你不能將其轉換爲常規查詢集是在數據庫中創建一個後臺查看。它基本上在您訪問View時執行View中的查詢。在Django中,您將創建一個非託管模型以附加到視圖。使用該模型,您可以將過濾器應用爲常規模型。使用外鍵,您可以將on_delete arg設置爲models.DO_NOTHING。關於非託管模式

的更多信息: https://docs.djangoproject.com/en/2.0/ref/models/options/#managed