Bene, quelli sono alcuni nomi di tabelle e campi poco chiari, ma meglio che posso dire che la query sarebbe simile a:
(Restaurant.objects.filter(city=8,
cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])
Ma a meno che tu non sia bloccato in quello schema del database, i tuoi modelli sembrerebbero migliori come:
class CuisineType(models.Model):
name = models.CharField(max_length=50)
class Meta:
db_table = 'cuisinetype'
class Restaurants(models.Model):
city = models.ForeignKey("City", null=True, blank=True) # Apparently defined elsewhere. Should be part of location?
name = models.CharField(max_length=50)
location = models.ForeignKey("Location", null=True, blank=True) # Apparently defined elsewhere.
cuisines = models.ManyToManyField(CuisineType)
Quindi la query sarebbe più simile a:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]
OK, esaminiamo la tua query, supponendo che non ci siano modifiche al tuo codice. Inizieremo con la sottoquery.
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian'
Esaminiamo la clausola WHERE e vediamo che abbiamo bisogno di un JOIN. Per fare un join, devi dichiarare un campo relazionale in uno dei modelli uniti (Django aggiungerà una relazione inversa, che dovremmo nominare). Quindi stiamo abbinando cuisine.cuisineid
con `cuisinetype.cuisineid. È una denominazione orribile.
Questa è una relazione molti-a-molti, quindi abbiamo bisogno di un ManyToManyField
. Bene, guardando la Cuisine
modello, è davvero il tavolo di unione per questo M2M. Django si aspetta che una tabella di unione abbia due ForeignKey
campi, uno che punta a ciascun lato del giunto. Normalmente creerà questo per salvare la sanità mentale. A quanto pare non sei così fortunato. Quindi devi collegarlo manualmente.
Sembra che il campo "GID" sia un campo ID (inutile) per il record, quindi supponiamo che sia un intero con incremento automatico. (Per essere sicuri, controlla i comandi CREATE TABLE.) Ora possiamo riscrivere la Cuisine
modellare in qualcosa che si avvicina al sano:
class Cuisine(models.Model):
cuisinegid = models.AutoField(primary_key=True, db_column='CuisineGID')
cuisineid = models.ForeignKey("Cuisinetype", null=True,
db_column='CuisineID', blank=True)
res_id = models.ForeignKey("Restaurant", null=True, db_column='Res_ID',
blank=True)
class Meta:
db_table = 'cuisine'
I nomi dei modelli sono citati perché i modelli non sono stati ancora definiti (sono più avanti nel file). Ora non è necessario che i nomi dei campi Django corrispondano ai nomi delle colonne, quindi cambiamoli in qualcosa di più leggibile. Il campo dell'ID del record di solito è semplicemente denominato id
e le chiavi esterne di solito prendono il nome da ciò a cui si riferiscono:
class Cuisine(models.Model):
id = models.AutoField(primary_key=True, db_column='CuisineGID')
cuisine_type = models.ForeignKey("CuisineType", null=True,
db_column='CuisineID', blank=True)
restaurant = models.ForeignKey("Restaurant", null=True, db_column='Res_ID',
blank=True)
class Meta:
db_table = 'cuisine'
OK, abbiamo finito di definire il nostro tavolo comune. Già che ci siamo, applichiamo le stesse cose al nostro Cuisinetype
modello. Nota il nome della classe Camel-case corretto:
class CuisineType(models.Model):
id = models.AutoField(primary_key=True, db_column='CuisineID')
name = models.CharField(max_length=50, db_column='Cuisine', blank=True)
class Meta:
db_table = 'cuisinetype'
Quindi arriviamo finalmente al nostro Restaurant
modello. Nota che il nome è singolare; un oggetto rappresenta solo un record.
Noto che manca qualsiasi dp_table
o db_column
roba, quindi sto andando fuori di testa e indovinando che Django lo sta creando. Ciò significa che possiamo lasciare che crei l'id
campo per noi e possiamo ometterlo dal nostro codice. (Se non è così, lo aggiungiamo come con gli altri modelli. Ma non dovresti avere un ID record nullable.) Ed è qui che il nostro tipo di cucina ManyToManyField
vive:
class Restaurants(models.Model):
city_id = models.ForeignKey(null=True, blank=True)
name = models.CharField(max_length=50, blank=True)
location = models.ForeignKey(null=True, blank=True)
cuisine_types = models.ManyToManyField(CuisineType, through=Cuisine,
null=True, blank=True)
Nota che il nome per il campo M2M è plurale, poiché tale relazione porta a più record.
Un'altra cosa che vorremo aggiungere a questo modello sono i nomi per le relazioni inverse. In altre parole, come tornare dagli altri modelli a Restaurant
. Lo facciamo aggiungendo related_name
parametri. Non è insolito che siano uguali.
class Restaurant(models.Model):
city_id = models.ForeignKey(null=True, blank=True,
related_name="restaurants")
name = models.CharField(max_length=50, blank=True)
location = models.ForeignKey(null=True, blank=True,
related_name="restaurants")
cuisine_types = models.ManyToManyField(CuisineType, through=Cuisine,
null=True, blank=True, related_name="restaurants")
Ora siamo finalmente a posto. Quindi diamo un'occhiata alla tua domanda:
SELECT restaurants.`name`, restaurants.`address`, cuisinetype.`cuisine`
FROM restaurants
JOIN cuisinetype ON cuisinetype.cuisineid = restaurants.`cuisine`
WHERE city_id = 8 AND restaurants.id IN (
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian')
ORDER BY restaurants.`name`
LIMIT 20
Poiché questo è FROM restaurants
, inizieremo con il gestore oggetti predefinito di quel modello, objects
:
Restaurant.objects
Il WHERE
in questo caso è un filter()
call, quindi lo aggiungiamo per il primo termine:
Restaurant.objects.filter(city=8)
Puoi avere un valore di chiave primaria o una City
oggetto sul lato destro di quel termine. Il resto della query diventa più complesso, però, perché necessita di JOIN
. Un join in Django sembra semplicemente dereferenziare attraverso il campo delle relazioni. In una query, ciò significa unire i nomi dei campi rilevanti con un doppio trattino basso:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")
Django sa a quali campi partecipare perché è dichiarato in Cuisine
tabella che viene richiamata da through=Cuisine
parametro in cuisine_types
. sa anche fare una sottoquery perché stai attraversando una relazione M2M.
Quindi questo ci porta SQL equivalente a:
SELECT restaurants.`name`, restaurants.`address`
FROM restaurants
WHERE city_id = 8 AND restaurants.id IN (
SELECT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian')
A metà strada. Ora abbiamo bisogno di SELECT DISTINCT
quindi non otteniamo più copie dello stesso record:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
E devi inserire i tipi di cucina per la visualizzazione. Si scopre che la query che hai è inefficiente lì, perché ti porta solo alla tabella di join e devi eseguire ulteriori query per ottenere il relativo CuisineType
record. Indovina cosa:Django ti copre.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types"))
Django eseguirà due query:una come la tua per ottenere gli ID congiunti e un'altra per ottenere il relativo CuisineType
record. Quindi gli accessi tramite il risultato della query non devono tornare al database.
Le ultime due cose sono l'ordine:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
E il LIMIT
:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
E c'è la tua query (e la query correlata) racchiusa in due righe di Python. Intendiamoci, a questo punto, la query non è nemmeno stata eseguita. Devi inserirlo in qualcosa, come un modello, prima che faccia qualsiasi cosa:
def cuisinesearch(request, cuisine):
return render_to_response('cuisinesearch.html', {
'restaurants': (Restaurant.objects.filter(city=8,
cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
})
Modello:
{% for restaurant in cuisinesearch %}
<h2>{{ restaurant.name }}</h2>
<div class="location">{{ restaurant.location }}</div>
<h3>Cuisines:</h3>
<ul class="cuisines">{% for ct in restaurant.cuisine_types.all %}
<li>{{ ct.name }}</li>{% endfor %}
</ul>
{% endfor %}