2012-07-31 100 views
1

假設我有這種模式:Django LEFT JOIN,高效?

class PhotoAlbum(models.Model): 
    title = models.CharField(max_length=128) 
    author = models.CharField(max_length=128) 

class Photo(models.Model): 
    album = models.ForeignKey('PhotoAlbum') 

我想做這個查詢:「找到了10張專輯的名稱以‘的’啓動,然後給我所有這些相冊的照片」

在SQL我能做到這一點是這樣的:

SELECT * FROM 
    (SELECT * FROM photoalbum WHERE title LIKE 'The%' LIMIT 10) AS selected_albums 
LEFT JOIN photo ON photo.album_id = selected_albums.id 

我的問題是,我怎麼能做到這一點在Django? (無需爲每個相冊觸發查詢!)我認爲這是一個相當普遍的要求,我不相信有沒有辦法做到這一點。

如果沒有Django-ey的方式,我會解決「我怎樣才能使用原始SQL在Django中實現?」。

這裏有一些東西是行不通的:

  • select_related();那是轉發ForeignKey關係,這是倒退。
  • prefetch_related();也用於轉發關係。編輯:其實這沒有工作!至少對於ForeignKey s的一個級別。
  • PhotoAlbum.photo_set;這會觸發每個專輯的查詢。
  • 我已經得到的最接近的是:

    專輯= PhotoAlbum.objects.all()[10] 照片= Photo.objects.filter(album__in =專輯)

但對於MySQL而言並不好用,而且我被告知最好使用LEFT JOIN,而不是使用WHERE ... IN (SELECT ...)類型的查詢。

編輯

我發現了一個3 year old mailing list post about the problem。沒有解決方案。

A 6 year old bug report saying they won't fix it。除了「這不是它的工作原理」之外沒有其他理由。顯然它可能在RoR中。

回答

1

這將工作,只做兩個查詢。 prefetch_related適用於反向FK,這實際上是它創建的:

for album in PhotoAlbum.objects.filter(title__startswith='The').prefetch_related('photo_set')[:10]: 
    print album.photo_set.all() 
+0

這個工程!它使用'WHERE id IN(1,2,3,4,5,6)'而不是'LEFT JOIN',但足夠公平。現在我想將'prefetch_related()'鏈接到兩個相反的'ForeignKey'級別,但我會再次離開它。乾杯。 – Timmmm 2012-08-01 10:44:34

4

在Django中1.4+可以使用prefetch_related

PhotoAlbum.objects.filter(title__startswith='The').prefetch_related('photo')[:10] 

在較小的版本試試django-batch-select

UPDATE

對不起。我仍然主要使用1.3,所以我不使用prefetch_related。在其他任何查詢類型中,您都不包括附錄_set,但Django顯然在這裏打破了慣例。如果你使用prefetch_related('photo_set'),它會起作用。

如果您需要獲取多個東西,您可以列出字段,就像使用select_related即可:

prefetch_related('something', 'something_else', 'foo') 

但要密切關注這部分從文檔:

還記得,一如既往地與查詢集,任何後續的串連方法,這意味着不同的數據庫查詢會忽略先前緩存的結果,並使用新的數據庫查詢檢索數據。所以,如果你寫:

>>> pizzas = Pizza.objects.prefetch_related('toppings') 
    >>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas] 

...那麼pizza.toppings.all()已被預取的事實不會幫助你 - 事實上,它會降低性能,因爲你做了一個數據庫查詢你還沒有用過。所以請謹慎使用此功能!

+0

然後我使用PhotoAlbum.photo_set,它不會觸發查詢嗎?如果我有一個三級樹(例如,也包含'PhotoAlbum'的'Bookcase'表),我可以連接'prefetch_related()',我可以做'Bookcase.objects.filter(madefrom ='Mahogany' ).prefetch_related('photoalbum')。filter(title__startswith ='The')。prefetch_Related('photo')[:10]'它將獲得所有以前10首桃花心木書櫃中的「The」開頭的所有照片?另外,我注意到'prefetch_related()'在python中進行連接。如果我有十億張照片怎麼辦? – Timmmm 2012-07-31 18:30:12

+0

此外,您的代碼實際上並不工作,因爲'photo'不是'PhotoAlbum'的字段。看起來'prefetch_related()'也只適用於前向關係。 – Timmmm 2012-07-31 19:05:31

+0

請參閱上面的更新。 – 2012-07-31 20:31:47

0

你不想在所有PhotoAlbums上觸發photo_set,是嗎?只是指定的?

for p in PhotoAlbum.objects.filter(title__startswith='The'): 
    p.photo_set.all() 
+0

如果100張專輯以「The」開頭,這仍然會執行100個查詢,對吧? – Timmmm 2012-08-01 10:14:28