No, to jsou některé nejasné názvy tabulek a polí, ale nejlépe mohu říci, že dotaz bude vypadat nějak takto:
(Restaurant.objects.filter(city=8,
cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])
Ale pokud nejste uzamčeni do tohoto databázového schématu, vaše modely by vypadaly lépe jako:
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)
Pak by dotaz byl spíše:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]
Dobře, pojďme si projít váš dotaz, za předpokladu, že se váš kód nezmění. Začneme poddotazem.
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian'
Podíváme se na klauzuli WHERE a uvidíme, že potřebujeme JOIN. Chcete-li provést spojení, musíte deklarovat relační pole v jednom ze spojených modelů (Django přidá obrácenou relaci, kterou bychom měli pojmenovat). Takže srovnáváme cuisine.cuisineid
s `cuisinetype.cuisineid. To je nějaké hrozné pojmenování.
To je vztah many-to-many, takže potřebujeme ManyToManyField
. Podívejte se na Cuisine
model, je to opravdu spojovací stůl pro tento M2M. Django očekává, že spojovací tabulka bude mít dva ForeignKey
pole, jedno směřující na každou stranu spoje. Normálně to pro vás vytvoří, abyste zachránili zdravý rozum. Zřejmě takové štěstí nemáte. Musíte jej tedy připojit ručně.
Zdá se, že pole „GID“ je (neužitečné) pole ID pro záznam, takže předpokládejme, že jde o celé číslo s automatickým přírůstkem. (Pro jistotu zkontrolujte příkazy CREATE TABLE.) Nyní můžeme přepsat Cuisine
modelovat do něčeho, co se blíží zdravému:
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'
Názvy modelů jsou uvedeny, protože modely ještě nebyly definovány (jsou v souboru později). Nyní není vyžadováno, aby se názvy polí Django shodovaly s názvy sloupců, takže je změňme na něco čitelnějšího. Pole ID záznamu má obvykle název id
a cizí klíče jsou obvykle pojmenovány podle toho, k čemu se vztahují:
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, dokončili jsme definování našeho společného stolu. Když už jsme u toho, použijme totéž na náš Cuisinetype
Modelka. Všimněte si opraveného názvu třídy velbloudů:
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'
Tak se konečně dostáváme do naší Restaurant
Modelka. Všimněte si, že název je singulární; objekt představuje pouze jeden záznam.
Všiml jsem si, že v něm chybí dp_table
nebo db_column
věci, takže jdu na úd a hádám, že to vytváří Django. To znamená, že mu můžeme nechat vytvořit id
pole pro nás a můžeme jej z našeho kódu vynechat. (Pokud tomu tak není, pak to prostě přidáme jako u ostatních modelů. Ale opravdu byste neměli mít nulovatelné ID záznamu.) A tady je typ naší kuchyně ManyToManyField
žije:
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)
Všimněte si, že název pole M2M je množné číslo, protože tento vztah vede k více záznamům.
Ještě jedna věc, kterou budeme chtít k tomuto modelu přidat, jsou názvy pro obrácené vztahy. Jinými slovy, jak přejít z ostatních modelů zpět do Restaurant
. To provedeme přidáním related_name
parametry. Není neobvyklé, že jsou stejní.
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")
Nyní jsme konečně připraveni. Podívejme se tedy na váš dotaz:
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
Protože toto je FROM restaurants
, začneme s výchozím správcem objektů tohoto modelu, objects
:
Restaurant.objects
WHERE
klauzule je v tomto případě filter()
zavolejte, tak to přidáme pro první termín:
Restaurant.objects.filter(city=8)
Můžete mít hodnotu primárního klíče nebo City
objekt na pravé straně tohoto termínu. Zbytek dotazu je však složitější, protože potřebuje JOIN
. Spojení v Django prostě vypadá jako dereferencování přes pole vztahu. V dotazu to znamená spojení příslušných názvů polí dvojitým podtržením:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")
Django ví, ke kterým polím se má připojit, protože to je deklarováno v Cuisine
tabulka, která je vtažena pomocí through=Cuisine
parametr v cuisine_types
. také ví, že má provést dílčí dotaz, protože procházíte vztahem M2M.
Takže dostaneme SQL ekvivalentní k:
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')
Napůl cesty tam. Nyní potřebujeme SELECT DISTINCT
takže nezískáme více kopií stejného záznamu:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
A pro zobrazení musíte vytáhnout typy kuchyní. Ukázalo se, že dotaz, který máte, je tam neefektivní, protože vás dostane pouze do tabulky spojení a musíte spustit další dotazy, abyste získali související CuisineType
evidence. Hádej co:Django tě pokryl.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types"))
Django spustí dva dotazy:jeden, jako je ten váš, aby získal společné ID, a druhý pro získání souvisejícího CuisineType
evidence. Potom se přístupy přes výsledek dotazu nemusí vracet do databáze.
Poslední dvě věci jsou uspořádání:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
A LIMIT
:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
A tam je váš dotaz (a související dotaz) zabalený do dvou řádků Pythonu. Uvědomte si, že v tuto chvíli nebyl dotaz ani proveden. Než to něco udělá, musíte to do něčeho vložit, například do šablony:
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])
})
Šablona:
{% 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 %}