Cóż, są to niejasne nazwy tabel i pól, ale najlepiej mogę powiedzieć, że zapytanie wyglądałoby mniej więcej tak:
(Restaurant.objects.filter(city=8,
cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])
Ale jeśli nie jesteś zablokowany w tym schemacie bazy danych, Twoje modele będą wyglądać lepiej, jak:
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)
Wtedy zapytanie będzie bardziej przypominać:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]
OK, przejdźmy przez Twoje zapytanie, zakładając brak zmian w kodzie. Zaczniemy od podzapytania.
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian'
Patrzymy na klauzulę WHERE i widzimy, że potrzebujemy JOIN. Aby wykonać złączenie, musisz zadeklarować pole relacyjne w jednym z łączonych modeli (Django doda relację odwrotną, którą powinniśmy nazwać). Więc dopasowujemy cuisine.cuisineid
z `typem kuchni.cuisineid. To okropne nazewnictwo.
To jest relacja wiele-do-wielu, więc potrzebujemy ManyToManyField
. Cóż, patrząc na Cuisine
model, to naprawdę stół łączący dla tego M2M. Django oczekuje, że stół łączący będzie miał dwa ForeignKey
pola, jeden skierowany na każdą stronę stawu. Normalnie stworzy to dla ciebie, aby zachować zdrowie psychiczne. Najwyraźniej nie masz tyle szczęścia. Musisz więc ręcznie go podłączyć.
Wygląda na to, że pole „GID” jest (bezużytecznym) polem identyfikatora rekordu, więc załóżmy, że jest to liczba całkowita zwiększająca się automatycznie. (Aby się upewnić, sprawdź polecenia CREATE TABLE.) Teraz możemy przepisać Cuisine
modelować w coś zbliżonego do zdrowego rozsądku:
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'
Nazwy modeli są cytowane, ponieważ modele nie zostały jeszcze zdefiniowane (znajdują się później w pliku). Teraz nie ma wymogu, aby nazwy pól Django pasowały do nazw kolumn, więc zmieńmy je na bardziej czytelne. Pole identyfikatora rekordu ma zwykle nazwę id
, a klucze obce są zwykle nazywane według tego, do czego się odnoszą:
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, skończyliśmy definiować nasz wspólny stół. Skoro już przy tym jesteśmy, zastosujmy to samo do naszego Cuisinetype
Model. Zwróć uwagę na poprawioną nazwę klasy wielbłąda:
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'
Więc w końcu dotarliśmy do naszej Restaurant
Model. Zwróć uwagę, że nazwa jest liczba pojedyncza; obiekt reprezentuje tylko jeden rekord.
Zauważyłem, że brakuje w nim jakiejkolwiek dp_table
lub db_column
rzeczy, więc wychodzę na kończynę i zgaduję, że to Django to tworzy. Oznacza to, że możemy pozwolić mu utworzyć id
pole dla nas i możemy je pominąć w naszym kodzie. (Jeśli tak nie jest, to po prostu dodajemy to tak, jak w przypadku innych modeli. Ale naprawdę nie powinieneś mieć identyfikatora rekordu dopuszczającego wartość null.) I tutaj nasza kuchnia wpisuje ManyToManyField
mieszka:
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)
Zwróć uwagę, że nazwa pola M2M jest w liczbie mnogiej, ponieważ ta relacja prowadzi do wielu rekordów.
Jeszcze jedną rzeczą, którą będziemy chcieli dodać do tego modelu, są nazwy relacji odwrotnych. Innymi słowy, jak przejść z innych modeli z powrotem do Restaurant
. Robimy to, dodając related_name
parametry. Nie jest niczym niezwykłym, że są takie same.
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")
Teraz w końcu jesteśmy gotowi. Spójrzmy więc na Twoje zapytanie:
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
Ponieważ to jest FROM restaurants
, zaczniemy od domyślnego menedżera obiektów tego modelu, objects
:
Restaurant.objects
WHERE
w tym przypadku klauzula to filter()
zadzwoń, więc dodajemy go na pierwszy semestr:
Restaurant.objects.filter(city=8)
Możesz mieć więdną wartość klucza głównego lub City
obiekt po prawej stronie tego terminu. Reszta zapytania staje się jednak bardziej złożona, ponieważ wymaga JOIN
. Złączenie w Django wygląda jak dereferencja poprzez pole relacji. W zapytaniu oznacza to połączenie odpowiednich nazw pól z podwójnym podkreśleniem:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")
Django wie, do których pól należy dołączyć, ponieważ jest to zadeklarowane w Cuisine
tabela, która jest pobierana przez through=Cuisine
parametr w cuisine_types
. wie również, jak wykonać podzapytanie, ponieważ przechodzisz przez relację M2M.
To daje nam SQL odpowiednik:
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')
W połowie drogi. Teraz potrzebujemy SELECT DISTINCT
więc nie otrzymujemy wielu kopii tego samego rekordu:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
I musisz pokazać rodzaje kuchni, aby je wyświetlić. Okazuje się, że zapytanie, które masz, jest tam nieefektywne, ponieważ prowadzi tylko do tabeli sprzężenia i musisz uruchomić dalsze zapytania, aby uzyskać powiązany CuisineType
dokumentacja. Zgadnij co:Django Cię obejmuje.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types"))
Django uruchomi dwa zapytania:jedno, takie jak twoje, aby uzyskać wspólne identyfikatory, a drugie, aby uzyskać powiązany CuisineType
dokumentacja. Wtedy dostęp poprzez wynik zapytania nie musi wracać do bazy danych.
Dwie ostatnie rzeczy to kolejność:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
Oraz LIMIT
:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
I oto twoje zapytanie (i powiązane zapytanie) upakowane w dwóch wierszach Pythona. Pamiętaj, że w tym momencie zapytanie nie zostało nawet wykonane. Musisz umieścić go w czymś, na przykład w szablonie, zanim cokolwiek zrobi:
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])
})
Szablon:
{% 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 %}