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