Way*_*ind 1 html python mysql sql django
需要一些Django版本的SQL多表查询帮助.该查询使用3个表来检索餐馆名称,地址Restaurants table和菜肴类型Cuisinetypes table.所有这些都基于通过URL传递的美食名称,美食ID存储在美食表中.
Models.py
class Restaurant(models.Model):
name = models.CharField(max_length=50, db_column='name', blank=True)
slugname = models.SlugField(max_length=50, blank=True)
address = models.CharField(max_length=100, blank=True)
city = models.ForeignKey('City', related_name="restaurants")
location = models.ForeignKey('Location', related_name="restaurants")
hood = models.ForeignKey('Hood', null=True, blank=True, related_name="restaurants")
listingrole = models.ForeignKey('Listingrole', related_name="restaurants")
cuisine_types = models.ManyToManyField('Cuisinetype', null=True, blank=True, related_name="restaurants")
class Meta:
db_table = 'restaurant'
class City(models.Model):
name = models.CharField(max_length=50, db_column='city')
state = models.CharField(max_length=50, blank=True, null=True)
switch = models.SmallIntegerField(null=True, blank=True, default='1')
class Meta:
db_table = 'city'
class Cuisinetype(models.Model):
name = models.CharField(max_length=50, db_column='cuisine', blank=True) # Field name made lowercase.
switch = models.SmallIntegerField(null=True, blank=True, default='1')
class Meta:
db_table = 'cuisinetype'
class Location(models.Model):
name = models.CharField(max_length=50, db_column='location', blank=False, null=False)
city = models.ForeignKey('City', related_name="locations")
switch = models.SmallIntegerField(null=True, blank=True, default='1')
class Meta:
db_table = 'location'
class Hood(models.Model):
name = models.CharField(max_length=50, db_column='hood')
city = models.ForeignKey('City', related_name='hoods')
location = models.ForeignKey('Location', related_name='hoods')
switch = models.SmallIntegerField(null=True, blank=True, default='1')
class Meta:
db_table = 'hood'
class Listingrole(models.Model):
id = models.AutoField(primary_key=True, db_column='id')
name = models.CharField(max_length=50, db_column='listingrole', blank=True) # Field name made lowercase.
switch = models.SmallIntegerField(null=True, blank=True, default='1')
class Meta:
db_table = 'listingrole'
Run Code Online (Sandbox Code Playgroud)
urls.py
url(r'^cuisine/(?P<cuisine>[-\w]+)/$', 'views.cuisinesearch'),
Run Code Online (Sandbox Code Playgroud)
views.py
def cuisinesearch(request, name='unknown'):
name = name.replace('-', ' ').capitalize()
return render_to_response('cuisinesearch.html',
{'cuisinesearch': Restaurant.objects.filter(city_id=8, switch=1, listingrole__in=[1,2,3,4], cuisine_types__name=name)
.distinct().prefetch_related("cuisine_types").order_by('listingrole', 'displayorder')[:50] })
Run Code Online (Sandbox Code Playgroud)
HTML
那么显示查询的正确方法是什么?
{% 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 %}
Run Code Online (Sandbox Code Playgroud)
好吧,那些是一些不清楚的表和字段名称,但最好我可以告诉该查询看起来像:
(Restaurant.objects.filter(city=8,
cuisine__cuisinetype__cuisine="Italian").distinct().order_by('name')[:20])
Run Code Online (Sandbox Code Playgroud)
但除非您被锁定在该数据库架构中,否则您的模型看起来会更好:
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)
Run Code Online (Sandbox Code Playgroud)
然后查询更像是:
Restaurant.objects.filter(city=8, cuisines__name="Italian").order_by('name')[:20]
Run Code Online (Sandbox Code Playgroud)
好的,假设您的代码没有任何更改,让我们一起浏览您的查询.我们将从子查询开始.
SELECT DISTINCT res_id FROM cuisine
JOIN cuisinetype ON cuisine.cuisineid = cuisinetype.`cuisineid`
WHERE cuisinetype.`cuisine` = 'Italian'
Run Code Online (Sandbox Code Playgroud)
我们查看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'
Run Code Online (Sandbox Code Playgroud)
引用模型名称是因为模型尚未定义(它们稍后在文件中).现在不要求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'
Run Code Online (Sandbox Code Playgroud)
好的,我们已经完成了我们的联合表的定义.当我们在这里时,让我们将相同的东西应用到我们的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'
Run Code Online (Sandbox Code Playgroud)
所以我们终于到了我们的Restaurant模型.请注意,名称是单数; 一个对象只代表一条记录.
我注意到,它没有任何dp_table或db_column东西,所以我要出去的肢体和猜测Django是创造它.这意味着我们可以让它id为我们创建领域,我们可以从代码中省略它.(如果不是这样,那么我们就像其他模型一样添加它.但是你真的不应该有一个可以为空的记录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)
Run Code Online (Sandbox Code Playgroud)
注意,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")
Run Code Online (Sandbox Code Playgroud)
现在我们终于确定了.那么让我们来看看你的查询:
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
Run Code Online (Sandbox Code Playgroud)
既然如此FROM restaurants,我们将从该模型的默认对象管理器开始objects:
Restaurant.objects
Run Code Online (Sandbox Code Playgroud)
WHERE本例中的子句是一个filter()调用,所以我们在第一个术语中添加它:
Restaurant.objects.filter(city=8)
Run Code Online (Sandbox Code Playgroud)
您可以City在该术语的右侧使主键值或对象枯萎.然而,查询的其余部分变得更加复杂,因为它需要JOIN.Django中的连接看起来像通过关系字段解除引用.在查询中,这意味着使用双下划线连接相关的字段名称:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian")
Run Code Online (Sandbox Code Playgroud)
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')
Run Code Online (Sandbox Code Playgroud)
中途那里.现在我们需要,SELECT DISTINCT所以我们不会得到同一记录的多个副本:
Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
Run Code Online (Sandbox Code Playgroud)
而且您需要提供美食类型以供展示.事实证明,您所拥有的查询效率低下,因为它只会将您带到连接表,并且您需要运行更多查询以获取相关CuisineType记录.猜猜看:Django让你满意.
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types"))
Run Code Online (Sandbox Code Playgroud)
Django将运行两个查询:一个像你一样获取联合ID,另一个查询获取相关CuisineType记录.然后通过查询结果访问不需要返回数据库.
最后两件事是订购:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name"))
Run Code Online (Sandbox Code Playgroud)
而且LIMIT:
(Restaurant.objects.filter(city=8, cuisine_type__name="Italian").distinct()
.prefetch_related("cuisine_types").order_by("name")[:20])
Run Code Online (Sandbox Code Playgroud)
并且您的查询(以及相关查询)被打包成两行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])
})
Run Code Online (Sandbox Code Playgroud)
模板:
{% 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 %}
Run Code Online (Sandbox Code Playgroud)