2012-07-31 6 views
1

Supposons que je ce modèle:Django LEFT JOIN, efficacement?

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

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

Et je veux faire cette requête: « Trouver 10 albums dont le nom commence par « Le », puis me donner toutes les photos de ces albums. »

Dans SQL je pouvais faire quelque chose comme ceci:

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

Ma question est, comment puis-je faire cela dans Django? (SANS DÉCLENCHER UNE QUESTION POUR CHAQUE ALBUM!) Je suppose que c'est une exigence assez commune, et je ne peux pas croire qu'il n'y a pas moyen de le faire.

S'il n'y a pas de manière Django-ey, je me contenterai de "comment puis-je l'implémenter dans Django en utilisant du SQL brut?".

Voici quelques choses qui ne fonctionnent pas:

  • select_related(); C'est pour avantForeignKey relations, c'est en arrière.
  • prefetch_related(); également pour les relations à terme. Edit: En fait, ce fonctionne! Au moins pour un niveau de ForeignKey s.
  • PhotoAlbum.photo_set; qui déclenche une requête pour chaque album.
  • Le plus proche que j'ai est:

    albums = PhotoAlbum.objects.all() [: 10] Photos = Photo.objects.filter (album__in = albums)

Mais il ne fonctionne malheureusement pas sur MySQL, et on m'a dit qu'il vaut mieux utiliser LEFT JOIN que la requête de type WHERE ... IN (SELECT ...) que cela crée.

Modifier

J'ai trouvé un 3 year old mailing list post about the problem. Aucune solution à l'intérieur.

A 6 year old bug report saying they won't fix it. Aucune raison donnée autre que "ce n'est pas comme ça que ça fonctionne". Apparemment, il est possible dans RoR si.

Répondre

1

Cela fonctionnera et ne fera que deux requêtes. prefetch_related travaille pour FKs inverse, c'est en fait ce qu'il a été créé pour:

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

Cela fonctionne! Il utilise 'WHERE id IN (1, 2, 3, 4, 5, 6)' au lieu de 'LEFT JOIN' mais assez juste. Maintenant, je veux chaîner 'prefetch_related()' à deux niveaux de reverseKey 'ForeignKey's, mais je vais laisser cela pour une autre fois. À votre santé. – Timmmm

4

Dans Django 1.4+, vous pouvez utiliser prefetch_related:

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

Dans les versions moins essayer django-batch-select.

MISE À JOUR

Désolé. Je suis toujours sur 1.3 principalement, donc je n'utilise pas beaucoup prefetch_related. Dans tous les autres types de requêtes, vous n'incluez pas l'annexe _set, mais Django a apparemment enfreint la convention ici. Cela fonctionnera si vous utilisez prefetch_related('photo_set').

Si vous devez rechercher plusieurs objets, vous pouvez lister les champs comme vous le feriez avec select_related, par exemple.:

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

Mais attention à cette partie de la documentation:

Rappelez-vous aussi que, comme toujours avec QuerySets, toutes les méthodes enchaînées ultérieures qui implique une requête de base de données différente ignorera les résultats précédemment mises en cache, et récupérer des données en utilisant une nouvelle requête de base de données. Donc, si vous écrivez ce qui suit:

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

... alors le fait que pizza.toppings.all() a été préchargées ne vous aidera pas - en fait, il nuit à la performance, puisque vous avez fait une requête de base de données que vous n'avez pas utilisé. Alors utilisez cette fonctionnalité avec prudence!

+0

Et puis-je utiliser 'PhotoAlbum.photo_set' et il ne déclenche pas une requête? Puis-je également chaîner 'prefetch_related()' s alors si j'avais un arbre à trois niveaux (par exemple aussi une table 'Bookcase' qui contenait' PhotoAlbum's) je pourrais faire 'Bookcase.objects.filter (madefrom = 'Mahogany') .prefetch_related ('photoalbum'). filter (title__startswith = 'Le'). prefetch_Related ('photo') [: 10] 'et il obtiendrait toutes les photos pour tous les livres commençant par" The "dans les 10 premières bibliothèques en acajou ? Aussi, je note que 'prefetch_related()' fait la jointure en python. Et si j'ai un milliard de photos? – Timmmm

+0

De plus, votre code ne fonctionne pas car 'photo' n'est pas un champ de' PhotoAlbum'. Il semble que 'prefetch_related()' fonctionne uniquement avec les relations forward. – Timmmm

+0

Voir la mise à jour ci-dessus. –

0

Vous ne voulez pas déclencher un photo_set sur tous les PhotoAlbums, oui? Juste les spécifiés?

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

Si 100 albums commencent par "The" cela fera toujours 100 requêtes, non? – Timmmm