好吧,这些是一些不清楚的表和字段名称,但我可以说,查询看起来像这样:
(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”字段是记录的(无用的)ID 字段,所以我们假设它是自动递增整数。 (可以肯定的是,请检查 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字段通常只是命名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
or db_column
东西,所以我冒险猜测 Django 正在创建它。这意味着我们可以让它创建id
字段对我们来说,我们可以从我们的代码中省略它。 (如果情况并非如此,那么我们只需像其他模型一样添加它。但是您确实不应该有可为空的记录 ID。)这就是我们的美食类型ManyToManyField
lives:
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
The 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 将运行两个查询:一个与您的查询类似,用于获取联合 ID,另一个查询用于获取相关的 ID。CuisineType
记录。那么通过查询结果访问就不需要返回数据库了。
最后两件事是顺序:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
And the 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 %}