Domanda

Ho la seguente struttura del modello:

class Container(models.Model):
    pass

class Generic(models.Model):
    name = models.CharacterField(unique=True)
    cont = models.ManyToManyField(Container, null=True)
    # It is possible to have a Generic object not associated with any container, 
    # thats why null=True

class Specific1(Generic):
    ...

class Specific2(Generic):
    ...

...

class SpecificN(Generic):
    ...

Dire, devo recuperare tutti i modelli di tipo Specific , che hanno una relazione con un particolare Container.

L'SQL per questo è più o meno banale, ma non è questa la domanda. Sfortunatamente, non ho molta esperienza con gli ORM (in particolare l'ORM di Django), quindi qui potrei perdere uno schema.

Se fatto in maniera a forza bruta, -

c = Container.objects.get(name='somename') # this gets me the container
items = c.generic_set.all() 
# this gets me all Generic objects, that are related to the container
# Now what? I need to get to the actual Specific objects, so I need to somehow
# get the type of the underlying Specific object and get it
for item in items:
    spec = getattr(item, item.get_my_specific_type())

questo si traduce in un sacco di hit db (uno per ogni record generico, che si riferisce a un contenitore), quindi questo ovviamente non è il modo di farlo. Ora, forse, potrebbe essere fatto ottenendo direttamente gli oggetti SpecificX:

s = Specific1.objects.filter(cont__name='somename')
# This gets me all Specific1 objects for the specified container
...
# do it for every Specific type

in questo modo il db verrà colpito una volta per ogni tipo specifico (accettabile, immagino).

So che .select_related () non funziona con le relazioni m2m, quindi non è di grande aiuto qui.

Per ribadire, il risultato finale deve essere una raccolta di oggetti SpecificX (non generici).

È stato utile?

Soluzione

Penso che tu abbia già delineato le due facili possibilità. O esegui una singola query di filtro su Generic e quindi esegui il cast di ciascun elemento nel relativo sottotipo specifico (risulta in n + 1 query, dove n è il numero di elementi restituiti) oppure esegui una query separata rispetto a ciascuna tabella specifica (risulta in k query, dove k è il numero di tipi specifici).

In realtà vale la pena fare benchmark per vedere quale di questi è più veloce nella realtà. Il secondo sembra migliore perché (probabilmente) meno query, ma ognuna di queste query deve eseguire un join con la tabella intermedia m2m. Nel primo caso esegui solo una query di join, quindi molte altre semplici. Alcuni backend di database funzionano meglio con molte piccole query rispetto a un numero inferiore di query più complesse.

Se il secondo è effettivamente molto più veloce per il tuo caso d'uso e sei disposto a fare un lavoro extra per ripulire il tuo codice, dovrebbe essere possibile scrivere un metodo di gestione personalizzato per il modello generico che " recupera " tutti i dati dei sottotipi dalle relative tabelle specifiche per un determinato queryset, usando solo una query per tabella dei sottotipi; simile a come questo snippet ottimizza le chiavi esterne generiche con un prefetch in blocco. Questo ti darebbe le stesse query della tua seconda opzione, con la sintassi DRYer della tua prima opzione.

Altri suggerimenti

Non è una risposta completa ma puoi evitare un gran numero di hit facendo questo

items= list(items)
for item in items:
    spec = getattr(item, item.get_my_specific_type())

invece di questo:

for item in items:
    spec = getattr(item, item.get_my_specific_type())

In effetti, forzando un cast in un elenco Python, forzi il django orm a caricare tutti gli elementi nel tuo queryset. Lo fa quindi in una query.

Ho accidentalmente inciampato nel seguente post, che praticamente risponde alla tua domanda:

http://lazypython.blogspot.com/2008 /11/timeline-view-in-django.html

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top