2016-05-13 14 views
7

短intoduction的問題...如何避免在Django自定義數據庫函數調用周圍的SQL中括號?

  • PostgreSQL有非常整齊地排列字段(int數組,字符串數組)和功能對他們來說就像UNNESTANY
  • Django支持這些字段(我使用的是djorm_pgarray),但函數不是本機支持的。
  • 可以使用.extra(),但Django 1.8引入了一個新概念database functions

讓我提供一個最基本的例子,我基本上用這些做了什麼。 A Dealer有一個它支持的make的列表。 A Vehicle有一個品牌,並與經銷商聯繫在一起。但碰巧Vehicle的製作與Dealer的製作清單不符,這是不可避免的。

MAKE_CHOICES = [('honda', 'Honda'), ...] 

class Dealer(models.Model): 
    make_list = TextArrayField(choices=MAKE_CHOICES) 

class Vehicle(models.Model): 
    dealer = models.ForeignKey(Dealer, null=True, blank=True) 
    make = models.CharField(max_length=255, choices=MAKE_CHOICES, blank=True) 

有經銷商數據庫,並讓,我要統計所有車輛,車輛的品牌和其經銷商的使列表做匹配。這就是我如何避免.extra()

from django.db.models import functions 

class SelectUnnest(functions.Func): 
    function = 'SELECT UNNEST' 

... 

Vehicle.objects.filter(
    make__in=SelectUnnest('dealer__make_list') 
).count() 

產生的SQL:

SELECT COUNT(*) AS "__count" FROM "myapp_vehicle" 
INNER JOIN "myapp_dealer" 
    ON ("myapp_vehicle"."dealer_id" = "myapp_dealer"."id") 
WHERE "myapp_vehicle"."make" 
    IN (SELECT UNNEST("myapp_dealer"."make_list")) 

和它的作品,而且比我們能在Django使用傳統的M2M方法快得多。但是,對於這項任務,UNNEST不是一個很好的解決方案:ANY要快得多。我們來試試吧。

class Any(functions.Func): 
    function = 'ANY' 

... 

Vehicle.objects.filter(
    make=Any('dealer__make_list') 
).count() 

生成下面的SQL:

SELECT COUNT(*) AS "__count" FROM "myapp_vehicle" 
INNER JOIN "myapp_dealer" 
    ON ("myapp_vehicle"."dealer_id" = "myapp_dealer"."id") 
WHERE "myapp_vehicle"."make" = 
    (ANY("myapp_dealer"."make_list")) 

它失敗了,因爲周圍ANY括號是假的。如果刪除它們,它將在psql控制檯中順利運行,而且速度很快。

所以我的問題。

  1. 有沒有什麼辦法去除這些大括號?我在Django文檔中找不到任何關於此的信息。
  2. 如果沒有, - 也許還有其他方法來重述這個查詢?

P. S.我認爲,廣泛的用於不同的後端數據庫函數庫將是數據庫重的Django應用程序非常有用。

當然,其中大多數不會是便攜式的。但是,您通常不會經常將這樣的項目從一個數據庫後端遷移到另一個數據庫。在我們的例子中,使用數組字段和PostGIS我們堅持使用PostgreSQL,並且不打算移動。

有人開發這樣的東西嗎?

P. P. S.有人可能會說,在這種情況下,我們應該使用單獨的表來製作和intarray而不是字符串數組,這是正確的,並且會完成,但問題的性質不會改變。

UPDATE。

  • TextArrayFielddjorm_pgarray定義。在鏈接的源文件中,您可以看到它的工作原理。
  • 該值是文本字符串的列表。在Python中,它被表示爲一個列表。例如:['honda', 'mazda', 'anything else']

下面是關於它在數據庫中的說法。

=# select id, make from appname_tablename limit 3; 
id | make 
---+---------------------- 
58 | {vw} 
76 | {lexus,scion,toyota} 
39 | {chevrolet} 

而底層的PostgreSQL字段類型是text[]

+0

這是最有趣的,我覺得雖然它沒有明確的文件中提到,FUNC鍵,它的子類可以只用於統計和標註但不在過濾器中。 – e4c5

+1

我甚至嘗試覆蓋Func中的as_sql方法,看看是否可以用來去掉括號。但事實證明,括號被添加到其他地方 – e4c5

+0

@ e4c5是的,我也查看了源代碼。也許有人深入Django ORM內部並且可以回答這個問題。 – Altaisoft

回答

3

我已經成功地得到(或多或少),你需要使用以下內容:

from django.db.models.lookups import BuiltinLookup 
from django.db.models.fields import Field 

class Any(BuiltinLookup): 
    lookup_name = 'any' 

    def get_rhs_op(self, connection, rhs): 
     return " = ANY(%s)" % (rhs,) 

Field.register_lookup(Any) 

和查詢:

Vehicle.objects.filter(make__any=F('dealer__make_list')).count() 

的結果是:

SELECT COUNT(*) AS "__count" FROM "zz_vehicle" 
    INNER JOIN "zz_dealer" ON ("zz_vehicle"."dealer_id" = "zz_dealer"."id") 
    WHERE "zz_vehicle"."make" = ANY(("zz_dealer"."make_list")) 

BTW。而不是djorm_pgarray和TextArrayField您可以使用本機的Django:

make_list = ArrayField(models.CharField(max_length=200), blank=True) 

(簡化你的依賴)

+0

非常感謝。我根本沒有想到自定義數據庫查找。我們一定會使用這種方法。再次感謝你,這非常酷。 – Altaisoft

相關問題