Nun, das sind einige unklare Tabellen- und Feldnamen, aber am besten kann ich sagen, dass die Abfrage in etwa so aussehen würde:
(Restaurant.objects.filter(city=8,
cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])
Aber wenn Sie nicht an dieses Datenbankschema gebunden sind, sehen Ihre Modelle besser so aus:
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)
Dann würde die Abfrage eher wie folgt aussehen:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]
OK, lassen Sie uns Ihre Abfrage durchgehen, wobei wir davon ausgehen, dass sich Ihr Code nicht ändert. Wir beginnen mit der Unterabfrage.
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian'
Wir sehen uns die WHERE-Klausel an und sehen, dass wir einen JOIN brauchen. Um einen Join durchzuführen, müssen Sie ein relationales Feld in einem der verbundenen Modelle deklarieren (Django fügt eine umgekehrte Relation hinzu, die wir benennen sollten). Also gleichen wir cuisine.cuisineid
ab mit `cuisinetype.cuisineid. Das ist eine schreckliche Namensgebung.
Das ist eine Viele-zu-Viele-Beziehung, also brauchen wir ein ManyToManyField
. Nun, wenn wir uns die Cuisine
ansehen Modell, es ist wirklich der Fügetisch für diesen M2M. Django erwartet, dass eine Join-Tabelle zwei ForeignKey
hat Felder, von denen eines auf jede Seite des Gelenks zeigt. Normalerweise erstellt es dies für Sie, um den Verstand zu retten. Anscheinend hast du nicht so viel Glück. Sie müssen es also manuell anschließen.
Es scheint, dass das "GID"-Feld ein (nutzloses) ID-Feld für den Datensatz ist, also nehmen wir an, dass es sich um eine automatisch inkrementierte Ganzzahl handelt. (Überprüfen Sie zur Sicherheit die CREATE TABLE-Befehle.) Jetzt können wir die Cuisine
umschreiben Modell in etwas annähernd Gesundes:
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'
Die Modellnamen werden in Anführungszeichen gesetzt, da die Modelle noch nicht definiert wurden (sie befinden sich später in der Datei). Jetzt ist es nicht mehr erforderlich, dass die Django-Feldnamen mit den Spaltennamen übereinstimmen, also ändern wir sie in etwas Lesbareres. Das Datensatz-ID-Feld heißt normalerweise einfach id
, und Fremdschlüssel werden normalerweise nach dem benannt, worauf sie sich beziehen:
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, wir sind mit der Definition unserer gemeinsamen Tabelle fertig. Wo wir gerade dabei sind, lassen Sie uns dasselbe auf unseren Cuisinetype
anwenden Modell. Beachten Sie den korrigierten Camel-Case-Klassennamen:
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'
So kommen wir endlich zu unserem Restaurant
Modell. Beachten Sie, dass der Name Singular; ist ein Objekt repräsentiert nur einen Datensatz.
Ich bemerke, dass es keine dp_table
gibt oder db_column
Sachen, also gehe ich auf die Beine und vermute, dass Django es erstellt. Das heißt, wir können es die id
erstellen lassen Feld für uns und wir können es aus unserem Code weglassen. (Wenn das nicht der Fall ist, dann fügen wir es einfach wie bei den anderen Modellen hinzu. Aber Sie sollten wirklich keine Nullable-Datensatz-ID haben.) Und hier setzt unser Küchentyp ManyToManyField
Leben:
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)
Beachten Sie, dass der Name für das M2M-Feld im Plural steht, da diese Beziehung zu mehreren Datensätzen führt.
Eine weitere Sache, die wir diesem Modell hinzufügen möchten, sind Namen für die umgekehrten Beziehungen. Mit anderen Worten, wie Sie von den anderen Modellen zurück zu Restaurant
gelangen . Dazu fügen wir related_name
hinzu Parameter. Es ist nicht ungewöhnlich, dass sie gleich sind.
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")
Jetzt sind wir endlich fertig. Schauen wir uns also Ihre Abfrage an:
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
Da dies FROM restaurants
ist beginnen wir mit dem standardmäßigen Objektmanager dieses Modells, objects
:
Restaurant.objects
Das WHERE
-Klausel ist in diesem Fall ein filter()
call, also fügen wir es für den ersten Term hinzu:
Restaurant.objects.filter(city=8)
Sie können entweder einen Primärschlüsselwert oder einen City
haben Objekt auf der rechten Seite dieses Begriffs. Der Rest der Abfrage wird jedoch komplexer, da er den JOIN
benötigt . Ein Join in Django sieht einfach aus wie eine Dereferenzierung durch das Beziehungsfeld. In einer Abfrage bedeutet das, die relevanten Feldnamen mit einem doppelten Unterstrich zu verbinden:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")
Django weiß, welche Felder verknüpft werden müssen, da dies in Cuisine
deklariert ist Tabelle, die von through=Cuisine
eingezogen wird Parameter in cuisine_types
. Es weiß auch, dass eine Unterabfrage durchgeführt werden muss, weil Sie eine M2M-Beziehung durchlaufen.
Damit erhalten wir das SQL-Äquivalent zu:
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')
Auf halbem Wege. Jetzt brauchen wir SELECT DISTINCT
damit wir nicht mehrere Kopien desselben Datensatzes erhalten:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
Und Sie müssen die Küchentypen für die Anzeige abrufen. Es stellt sich heraus, dass die Abfrage, die Sie haben, dort ineffizient ist, da Sie nur zur Join-Tabelle gelangen und Sie weitere Abfragen ausführen müssen, um den zugehörigen CuisineType
zu erhalten Aufzeichnungen. Ratet mal:Django hat alles im Griff.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types"))
Django führt zwei Abfragen aus:eine wie Ihre, um die gemeinsamen IDs zu erhalten, und eine weitere, um den zugehörigen CuisineType
zu erhalten Aufzeichnungen. Dann müssen Zugriffe über das Abfrageergebnis nicht auf die Datenbank zurückgehen.
Die letzten beiden Dinge sind die Reihenfolge:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
Und das LIMIT
:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
Und da ist Ihre Abfrage (und die zugehörige Abfrage) in zwei Python-Zeilen gepackt. Wohlgemerkt, zu diesem Zeitpunkt wurde die Abfrage noch nicht einmal ausgeführt. Sie müssen es in etwas wie eine Vorlage einfügen, bevor es irgendetwas tut:
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])
})
Vorlage:
{% 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 %}