Е, това са някои неясни имена на таблици и полета, но най-добре мога да кажа, че заявката ще изглежда нещо като:
(Restaurant.objects.filter(city=8,
cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])
Но освен ако не сте заключени в тази схема на база данни, вашите модели ще изглеждат по-добре като:
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)
Тогава заявката би била по-скоро:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]
Добре, нека преминем през вашата заявка, като приемем, че няма промени в кода ви. Ще започнем с подзаявката.
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian'
Разглеждаме клаузата WHERE и виждаме, че имаме нужда от JOIN. За да направите присъединяване, трябва да декларирате релационно поле в един от присъединените модели (Django ще добави обратна връзка, която трябва да назовем). Така че съпоставяме cuisine.cuisineid
с `cuisinetype.cuisineid. Това е ужасно именуване.
Това е релация много към много, така че имаме нужда от ManyToManyField
. Е, гледайки Cuisine
модел, това наистина е свързващата маса за този M2M. Django очаква присъединяваща се таблица да има два ForeignKey
полета, по едно насочено към всяка страна на ставата. Обикновено това ще създаде това за вас, за да запазите здравия разум. Явно не си толкова късметлия. Така че трябва да го свържете ръчно.
Изглежда, че полето "GID" е (безполезно) поле за идентификация за записа, така че нека приемем, че е цяло число с автоматично увеличение. (За да сте сигурни, проверете командите CREATE TABLE.) Сега можем да пренапишем Cuisine
модел в нещо, което се доближава до разумно:
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'
Имената на моделите са цитирани, защото моделите все още не са дефинирани (те са по-късно във файла). Сега няма изискване имената на полетата в Django да съвпадат с имената на колоните, така че нека ги променим на нещо по-четливо. Полето за идентификатор на запис обикновено се нарича просто id
, а външните ключове обикновено се наименуват според това, с което се отнасят:
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'
Добре, приключихме с дефинирането на общата ни маса. Докато сме на това, нека приложим същите неща към нашия Cuisinetype
модел. Обърнете внимание на коригираното име на клас с камила:
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'
И така най-накрая стигаме до нашия Restaurant
модел. Имайте предвид, че името е единствено число; обект представлява само един запис.
Забелязвам, че липсва всякаква dp_table
или db_column
неща, така че излизам на крайник и предполагам, че Django го създава. Това означава, че можем да му позволим да създаде id
поле за нас и можем да го пропуснем от нашия код. (Ако случаят не е такъв, просто го добавяме както при другите модели. Но наистина не трябва да имате нулев идентификационен номер на запис.) И това е мястото, където нашата кухня тип ManyToManyField
животи:
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)
Имайте предвид, че името на полето M2M е множествено число, тъй като тази връзка води до множество записи.
Още нещо, което ще искаме да добавим към този модел, са имената на обратните връзки. С други думи, как да се върнем от другите модели обратно към Restaurant
. Правим това, като добавяме related_name
параметри. Не е необичайно да са еднакви.
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")
Сега най-накрая сме готови. Така че нека разгледаме вашата заявка:
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
Тъй като това е FROM restaurants
, ще започнем с обектния мениджър по подразбиране на този модел, objects
:
Restaurant.objects
WHERE
клаузата в този случай е filter()
обаждане, така че го добавяме за първия термин:
Restaurant.objects.filter(city=8)
Можете да изтриете стойност на първичен ключ или City
обект от дясната страна на този термин. Останалата част от заявката става по-сложна, защото се нуждае от JOIN
. Присъединяването в Django просто изглежда като дерефериране през полето за релация. В заявка това означава свързване на съответните имена на полета с двойно долно подчертаване:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")
Django знае към кои полета да се присъедини, защото това е декларирано в Cuisine
таблица, която се изтегля от through=Cuisine
параметър в cuisine_types
. също така знае да направи подзаявка, защото преминавате през M2M релация.
Така че това ни прави SQL еквивалентен на:
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')
На половината път. Сега ни трябва SELECT DISTINCT
така че не получаваме множество копия на един и същ запис:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
И трябва да изтеглите видовете кухня за показване. Оказва се, че заявката, която имате, е неефективна там, защото ви отвежда само до таблицата за присъединяване и трябва да изпълнявате допълнителни заявки, за да получите свързания CuisineType
записи. Познайте какво:Django ви е покрил.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types"))
Django ще изпълни две заявки:една като вашата, за да получи съвместните идентификатори и още една, за да получи свързания CuisineType
записи. След това достъпът чрез резултата от заявката не е необходимо да се връща към базата данни.
Последните две неща са подреждането:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
И LIMIT
:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
И ето вашата заявка (и свързаната с нея заявка), опакована в два реда на Python. Имайте предвид, че към този момент заявката дори не е изпълнена. Трябва да го поставите в нещо, като шаблон, преди да направи нещо:
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])
})
Шаблон:
{% 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 %}