2012-06-13 13 views
4

我總是發現Django orm對子類化模型的處理非常漂亮。這可能就是爲什麼我遇到像這樣的問題。django orm - 如何在超類的子類的外鍵上使用select_related()

採取三種模式:

class A(models.Model): 
    field1 = models.CharField(max_length=255) 

class B(A): 
    fk_field = models.ForeignKey('C') 

class C(models.Model): 
    field2 = models.CharField(max_length=255) 

所以,現在你可以查詢A模型,並得到所有的B模型,其中可供選擇:

the_as = A.objects.all() 
for a in the_as: 
    print a.b.fk_field.field2 #Note that this throws an error if there is no B record 

這裏的問題是,你正在尋找大量的數據庫調用來檢索所有的數據。

現在假設您想要檢索數據庫中所有A模型的查詢集,但也包含所有子類記錄和子類的外鍵記錄,並使用select_related()將應用程序限制爲單個數據庫調用。你會寫這樣的查詢:

the_as = A.objects.select_related("b", "b__fk_field").all() 

一個查詢返回所有需要的數據!真棒。

除外。由於這個版本的查詢是做自己的過濾,即使select_related是不應該在所有篩選的結果:

set_1 = A.objects.select_related("b", "b__fk_field").all() #Only returns A objects with associated B objects 
set_2 = A.objects.all() #Returns all A objects 
len(set_1) > len(set_2) #Will always be False 

我使用的Django的調試工具來檢查查詢和發現問題。生成的SQL查詢使用INNER JOIN加入C表來查詢,而不是LEFT OUTER JOIN像其他子類領域:

SELECT "app_a"."field1", "app_b"."fk_field_id", "app_c"."field2" 
FROM "app_a" 
    LEFT OUTER JOIN "app_b" ON ("app_a"."id" = "app_b"."a_ptr_id") 
    INNER JOIN "app_c" ON ("app_b"."fk_field_id" = "app_c"."id"); 

而且似乎如果我簡單地改變INNER JOINLEFT OUTER JOIN,然後我得到的記載,我想要,但是這在使用Django的ORM時無濟於事。

這是一個在Django的ORM中的select_related()中的錯誤?有沒有解決這個問題的方法,或者我只需要直接查詢數據庫並自己映射結果呢?我應該使用像Django-Polymorphic這樣的東西嗎?

回答

0

我找到了解決方法,但我會等一會兒接受它,希望能得到一些更好的答案。

select_related('b__fk_field')創建的INNER JOIN需要從底層SQL中刪除,以便結果不會被數據庫中的B記錄過濾。因此,新的查詢需要離開b__fk_field參數select_related出:

the_as = A.objects.select_related('b') 

然而,這迫使我們調用數據庫,每次一個C對象是從A對象訪問。

for a in the_as: 
    #Note that this throws an DoesNotExist error if a doesn't have an 
    #associated b 
    print a.b.fk_field.field2 #Hits the database everytime. 

解決此的黑客是從一個查詢得到所有我們從數據庫中需要C對象,然後讓每個B對象手動引用它們。我們能做到這一點,因爲訪問檢索到的B對象的數據庫調用將有引用其相關C對象fk_field_id

c_ids = [a.b.fk_field_id for a in the_as] #Get all the C ids 
the_cs = C.objects.filter(pk__in=c_ids) #Run a query to get all of the needed C records 
for c in the_cs: 
    for a in the_as: 
     if a.b.fk_field_id == c.pk: #Throws DoesNotExist if no b associated with a 
      a.b.fk_field = c 
      break 

我敢肯定有寫,如果沒有嵌套循環功能的方式,但這種說明發生了什麼事。這並不理想,但它提供了絕對最小數量的數據庫命中的所有數據 - 這正是我想要的。

+0

這是一個尚未解決的問題嗎? – ilse2005

2

它看起來像一個bug,特別是它似乎忽略了A-> B關係的可空性質,例如,如果你在A中有一個外鍵引用而不是子類,那麼外鍵將會當然可以爲空,django會爲它使用左連接。你應該在django問題跟蹤器中提出這個問題。你也可以嘗試使用prefetch_related而不是select_related來解決你的問題。

相關問題