Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Django views.py Version von SQL Join mit Multi-Table-Abfrage

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 %}