sql >> Databasteknik >  >> RDS >> Mysql

Django views.py Version av SQL Join with Multi Table Query

Tja, det är några oklara tabell- och fältnamn, men bäst jag kan säga att frågan skulle se ut ungefär så här:

(Restaurant.objects.filter(city=8, 
     cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])

Men om du inte är låst till det databasschemat, skulle dina modeller se bättre ut som:

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)

Då skulle frågan vara mer som:

Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]

OK, låt oss gå igenom din fråga, förutsatt att du inte ändrar din kod. Vi börjar med underfrågan.

SELECT DISTINCT res_id FROM cuisine 
        JOIN    cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
        WHERE   cuisinetype.`cuisine` = 'Italian'

Vi tittar på WHERE-klausulen och ser att vi behöver en JOIN. För att göra en sammanfogning måste du deklarera ett relationsfält i en av de sammanfogade modellerna (Django kommer att lägga till en omvänd relation, som vi bör namnge). Så vi matchar cuisine.cuisineid med `cuisinetype.cuisineid. Det är ett hemskt namn.

Det är en många-till-många-relation, så vi behöver ett ManyToManyField . Tja, tittar på Cuisine modell, det är verkligen sammanfogningsbordet för denna M2M. Django förväntar sig att en sammanfogningstabell har två ForeignKey fält, ett som pekar på varje sida av fogen. Normalt kommer det att skapa detta för dig för att rädda förståndet. Du har tydligen inte sån tur. Så du måste koppla upp den manuellt.

Det verkar som att "GID"-fältet är ett (onyttigt) ID-fält för posten, så låt oss anta att det är ett heltal med automatisk ökning. (För att vara säker, kontrollera CREATE TABLE-kommandona.) Nu kan vi skriva om Cuisine modellera till något som närmar sig sunt:

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'

Modellnamnen citeras eftersom modellerna inte har definierats ännu (de finns senare i filen). Nu finns det inget krav på att Django-fältnamnen matchar kolumnnamnen, så låt oss ändra dem till något mer läsbart. Post-ID-fältet heter vanligtvis bara id , och främmande nycklar är vanligtvis namngivna efter vad de relaterar till:

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, vi är klara med att definiera vårt gemensamma bord. Medan vi håller på med det här, låt oss tillämpa samma saker på vår Cuisinetype modell. Notera det korrigerade kamelfodralets klassnamn:

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'

Så vi kommer äntligen till vår Restaurant modell. Observera att namnet är singular; ett objekt representerar bara en post.

Jag märker att den saknar någon dp_table eller db_column grejer, så jag går ut och antar att Django skapar det. Det betyder att vi kan låta den skapa id fältet för oss och vi kan utelämna det från vår kod. (Om så inte är fallet lägger vi bara till det som med de andra modellerna. Men du borde verkligen inte ha ett nullbart post-ID.) Och det är här vårt kökstyp ManyToManyField liv:

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)

Observera att namnet för M2M-fältet är plural, eftersom den relationen leder till flera poster.

En sak till som vi kommer att vilja lägga till i den här modellen är namn på de omvända förhållandena. Med andra ord, hur man går från de andra modellerna tillbaka till Restaurant . Vi gör detta genom att lägga till related_name parametrar. Det är inte ovanligt att de är likadana.

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")

Nu är vi äntligen klara. Så låt oss titta på din fråga:

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

Eftersom detta är FROM restaurants , vi börjar med den modellens standardobjekthanterare, objects :

Restaurant.objects

WHERE klausul i detta fall är en filter() call, så vi lägger till det för den första termen:

Restaurant.objects.filter(city=8)

Du kan ha ett primärnyckelvärde eller en City objekt till höger om termen. Resten av frågan blir dock mer komplex eftersom den behöver JOIN . En koppling till Django ser bara ut som att hänvisa genom relationsfältet. I en fråga betyder det att de relevanta fältnamnen sammanfogas med ett dubbelt understreck:

Restaurant.objects.filter(city=8, cuisine_type__name="Italian")

Django vet vilka fält den ska gå med i eftersom det deklareras i Cuisine tabellen som dras in av through=Cuisine parameter i cuisine_types . den vet också att göra en underfråga eftersom du går igenom en M2M-relation.

Så det ger oss SQL som motsvarar:

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')

Halvvägs. Nu behöver vi SELECT DISTINCT så att vi inte får flera kopior av samma post:

Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()

Och du måste dra in kökstyperna för visning. Det visar sig att frågan du har är ineffektiv där, eftersom den bara tar dig till kopplingstabellen och du behöver köra ytterligare frågor för att få den relaterade CuisineType uppgifter. Gissa vad:Django har dig täckt.

(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types"))

Django kommer att köra två frågor:en som din för att få de gemensamma ID:n och en till för att få den relaterade CuisineType uppgifter. Då behöver åtkomster via frågeresultatet inte gå tillbaka till databasen.

De två sista sakerna är beställningen:

(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types").order_by("name"))

Och LIMIT :

(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
    .prefetch_related("cuisine_types").order_by("name")[:20])

Och där är din fråga (och den relaterade frågan) packad i två rader av Python. Kom ihåg att för närvarande har frågan inte ens utförts. Du måste lägga det i något, som en mall, innan det gör något:

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])
        })

Mall:

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



  1. EXCEPT körs snabbare än en JOIN när tabellkolumnerna är desamma

  2. Välja data från två olika servrar i SQL Server

  3. Hur använder jag en IF-sats i en MySQL-anslutningsfråga?

  4. Konvertera javascript till datumobjekt till mysql-datumformat (ÅÅÅÅ-MM-DD)