Nah, itu adalah beberapa nama tabel dan bidang yang tidak jelas, tetapi saya dapat mengatakan bahwa kueri akan terlihat seperti:
(Restaurant.objects.filter(city=8,
cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])
Tetapi kecuali Anda terkunci dalam skema database itu, model Anda akan terlihat lebih baik sebagai:
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)
Maka kuerinya akan lebih seperti:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]
Oke, mari kita telusuri kueri Anda, dengan asumsi tidak ada perubahan pada kode Anda. Kita akan mulai dengan subquery.
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian'
Kami melihat klausa WHERE dan melihat kami membutuhkan JOIN. Untuk melakukan penggabungan, Anda harus mendeklarasikan bidang relasional dalam salah satu model yang digabungkan (Django akan menambahkan hubungan terbalik, yang harus kita beri nama). Jadi kami mencocokkan cuisine.cuisineid
dengan `cuisinetype.cuisineid. Itu beberapa penamaan yang mengerikan.
Itu adalah relasi banyak ke banyak, jadi kita membutuhkan ManyToManyField
. Nah, melihat Cuisine
model, itu benar-benar tabel bergabung untuk M2M ini. Django mengharapkan tabel penggabungan memiliki dua ForeignKey
bidang, satu menunjuk ke setiap sisi sambungan. Biasanya itu akan membuat Anda untuk menyimpan kewarasan. Rupanya Anda tidak seberuntung itu. Jadi, Anda harus menghubungkannya secara manual.
Tampaknya bidang "GID" adalah bidang ID (tidak berguna) untuk dicatat, jadi mari kita asumsikan itu bilangan bulat kenaikan otomatis. (Untuk memastikannya, periksa perintah CREATE TABLE.) Sekarang kita dapat menulis ulang Cuisine
model menjadi sesuatu yang mendekati waras:
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'
Nama model dikutip karena modelnya belum ditentukan (mereka nanti ada di file). Sekarang tidak ada persyaratan bahwa nama bidang Django cocok dengan nama kolom, jadi mari kita mengubahnya menjadi sesuatu yang lebih mudah dibaca. Kolom ID catatan biasanya hanya bernama id
, dan kunci asing biasanya dinamai menurut apa yang terkait dengannya:
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, kita sudah selesai mendefinisikan tabel bersama kita. Saat ini, mari kita terapkan hal yang sama ke Cuisinetype
our model. Perhatikan nama kelas kotak unta yang dikoreksi:
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'
Akhirnya kami sampai di Restaurant
model. Perhatikan bahwa namanya tunggal; sebuah objek hanya mewakili satu record.
Saya perhatikan bahwa itu tidak memiliki dp_table
atau db_column
hal-hal, jadi saya mengambil risiko dan menebak Django yang menciptakannya. Itu berarti kita bisa membiarkannya membuat id
bidang untuk kami dan kami dapat menghilangkannya dari kode kami. (Jika bukan itu masalahnya, maka kami hanya menambahkannya seperti dengan model lain. Tapi Anda benar-benar tidak boleh memiliki ID catatan yang dapat dibatalkan.) Dan di sinilah masakan kami ketik ManyToManyField
hidup:
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)
Perhatikan bahwa nama bidang M2M adalah jamak, karena relasi tersebut mengarah ke beberapa record.
Satu hal lagi yang ingin kita tambahkan ke model ini adalah nama untuk hubungan terbalik. Dengan kata lain, bagaimana beralih dari model lain kembali ke Restaurant
. Kami melakukan ini dengan menambahkan related_name
parameter. Bukan hal yang aneh bagi mereka untuk menjadi sama.
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")
Sekarang kita akhirnya siap. Jadi mari kita lihat kueri Anda:
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
Karena ini FROM restaurants
, kita akan mulai dengan manajer objek default model itu, objects
:
Restaurant.objects
WHERE
klausa dalam hal ini adalah filter()
panggilan, jadi kami menambahkannya untuk istilah pertama:
Restaurant.objects.filter(city=8)
Anda dapat memiliki nilai kunci utama atau City
objek di sisi kanan istilah itu. Namun, sisa kueri menjadi lebih kompleks, karena memerlukan JOIN
. Gabung di Django hanya terlihat seperti dereferensi melalui bidang relasi. Dalam kueri, itu berarti menggabungkan nama bidang yang relevan dengan garis bawah ganda:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")
Django mengetahui bidang mana yang akan digabungkan karena itu dideklarasikan dalam Cuisine
tabel yang ditarik oleh through=Cuisine
parameter di cuisine_types
. itu juga tahu untuk melakukan subquery karena Anda sedang melalui hubungan M2M.
Sehingga membuat kita setara dengan 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')
Setengah jalan di sana. Sekarang kita membutuhkan SELECT DISTINCT
jadi kami tidak mendapatkan banyak salinan dari catatan yang sama:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
Dan Anda perlu menarik jenis masakan untuk dipajang. Ternyata kueri yang Anda miliki tidak efisien di sana, karena hanya membawa Anda ke tabel gabungan dan Anda perlu menjalankan kueri lebih lanjut untuk mendapatkan CuisineType
terkait catatan. Coba tebak:Django telah Anda liput.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types"))
Django akan menjalankan dua kueri:satu seperti milik Anda untuk mendapatkan ID gabungan, dan satu lagi untuk mendapatkan CuisineType
terkait catatan. Kemudian akses melalui hasil query tidak perlu kembali ke database.
Dua hal terakhir adalah urutannya:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
Dan LIMIT
:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
Dan ada kueri Anda (dan kueri terkait) yang dikemas ke dalam dua baris Python. Pikiran Anda, pada titik ini, kueri bahkan belum dieksekusi. Anda harus memasukkannya ke dalam sesuatu, seperti template, sebelum melakukan apa pun:
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])
})
Templat:
{% 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 %}