查询操作
查找是数据库操作中一个非常重要的技术。查询一般就是使用filter、exclude以及get三个方法来实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在ORM层面,这些查询条件都是使用field+__+condition的方式来使用的。以下将那些常用的查询条件来一一解释。
查询条件
exact:
使用精确的=进行查找。如果提供的是一个None,那么在SQL层面就是被解释为NULL。示例代码如下:
article=Article.objects.get(id__exact=14)
article=Article.objects.get(id__exact=None)
以上的两个查找在翻译为SQL语句为如下:
select...fromarticlewhereid=14;
select...fromarticlewhereidISNULL;
iexact:
使用like进行查找。示例代码如下:
article = Article.objects.filter(title__iexact='hello world')
那么以上的查询就等价于以下的SQL语句:
select...fromarticlewheretitlelike'hello world';
注意上面这个sql语句,因为在MySQL中,没有一个叫做ilike的。所以exact和iexact的区别实际上就是LIKE和=的区别,在大部分collation=utf8_general_ci情况下都是一样的(collation是用来对字符串比较的)。
contains:
大小写敏感,判断某个字段是否包含了某个数据。示例代码如下:
articles=Article.objects.filter(title__contains='hello')
在翻译成SQL语句为如下:
select...wheretitlelikebinary'%hello%';
要注意的是,在使用contains的时候,翻译成的sql语句左右两边是有百分号的,意味着使用的是模糊查询。而exact翻译成sql语句左右两边是没有百分号的,意味着使用的是精确的查询。
icontains:
大小写不敏感的匹配查询。示例代码如下:
articles=Article.objects.filter(title__icontains='hello')
在翻译成SQL语句为如下:
select...wheretitlelike'%hello%';
in:
提取那些给定的field的值是否在给定的容器中。容器可以为list、tuple或者任何一个可以迭代的对象,包括QuerySet对象。示例代码如下:
articles=Article.objects.filter(id__in=[1,2,3])
以上代码在翻译成SQL语句为如下:
select...whereidin(1,3,4)
当然也可以传递一个QuerySet对象进去。示例代码如下:
inner_qs=Article.objects.filter(title__contains='hello')
categories=Category.objects.filter(article__in=inner_qs)
以上代码的意思是获取那些文章标题包含hello的所有分类。将翻译成以下SQL语句,示例代码如下:
select...fromcategorywherearticle.idin(selectidfromarticlewheretitlelike'%hello%');
gt:
某个field的值要大于给定的值。示例代码如下:
articles=Article.objects.filter(id__gt=4)
以上代码的意思是将所有id大于4的文章全部都找出来。将翻译成以下SQL语句:
select...whereid >4;
gte:
类似于gt,是大于等于。
lt:
类似于gt是小于。
lte:
类似于lt,是小于等于。
startswith:
判断某个字段的值是否是以某个值开始的。大小写敏感。示例代码如下:
articles=Article.objects.filter(title__startswith='hello')
以上代码的意思是提取所有标题以hello字符串开头的文章。将翻译成以下SQL语句:
select...wheretitlelike'hello%'
istartswith:
类似于startswith,但是大小写是不敏感的。
endswith:
判断某个字段的值是否以某个值结束。大小写敏感。示例代码如下:
articles=Article.objects.filter(title__endswith='world')
以上代码的意思是提取所有标题以world结尾的文章。将翻译成以下SQL语句:
select...wheretitlelike'%world';
iendswith:
类似于endswith,只不过大小写不敏感。
range:
判断某个field的值是否在给定的区间中。示例代码如下:
fromdjango.utils.timezoneimportmake_aware
fromdatetimeimportdatetime
start_date=make_aware(datetime(year=2018,month=1,day=1))
end_date=make_aware(datetime(year=2018,month=3,day=29,hour=16))
articles=Article.objects.filter(pub_date__range=(start_date,end_date))
以上代码的意思是提取所有发布时间在2018/1/1到2018/12/12之间的文章。将翻译成以下的SQL语句:
select...fromarticlewherepub_timebetween'2018-01-01'and'2018-12-12'。
需要注意的是,以上提取数据,不会包含最后一个值。也就是不会包含2018/12/12的文章。而且另外一个重点,因为我们在settings.py中指定了USE_TZ=True,并且设置了TIME_ZONE='Asia/Shanghai',因此我们在提取数据的时候要使用django.utils.timezone.make_aware先将datetime.datetime从navie时间转换为aware时间。make_aware会将指定的时间转换为TIME_ZONE中指定的时区的时间。
date:
针对某些date或者datetime类型的字段。可以指定date的范围。并且这个时间过滤,还可以使用链式调用。示例代码如下:
articles=Article.objects.filter(pub_date__date=date(2018,3,29))
以上代码的意思是查找时间为2018/3/29这一天发表的所有文章。将翻译成以下的sql语句:
select...WHEREDATE(CONVERT_TZ(`front_article`.`pub_date`,'UTC','Asia/Shanghai')) =2018-03-29
注意,因为默认情况下MySQL的表中是没有存储时区相关的信息的。因此我们需要下载一些时区表的文件,然后添加到Mysql的配置路径中。如果你用的是windows操作系统。那么在http://dev.mysql.com/downloads/timezones.html下载timezone_2018d_posix.zip - POSIX standard。然后将下载下来的所有文件拷贝到C:\ProgramData\MySQL\MySQL Server 5.7\Data\mysql中,如果提示文件名重复,那么选择覆盖即可。如果用的是linux或者mac系统,那么在命令行中执行以下命令:mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -D mysql -u root -p,然后输入密码,从系统中加载时区文件更新到mysql中。
year:
根据年份进行查找。示例代码如下:
articles=Article.objects.filter(pub_date__year=2018)
articles=Article.objects.filter(pub_date__year__gte=2017)
以上的代码在翻译成SQL语句为如下:
select...wherepub_datebetween'2018-01-01'and'2018-12-31';
select...wherepub_date >='2017-01-01';
month:
同year,根据月份进行查找。
day:
同year,根据日期进行查找。
week_day:
Django 1.11新增的查找方式。同year,根据星期几进行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。
time:
根据时间进行查找。示例代码如下:
articles=Article.objects.filter(pub_date__time=datetime.time(12,12,12));
以上的代码是获取每一天中12点12分12秒发表的所有文章。更多的关于时间的过滤,请参考Django官方文档:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#range。
isnull:
根据值是否为空进行查找。示例代码如下:
articles=Article.objects.filter(pub_date__isnull=False)
以上的代码的意思是获取所有发布日期不为空的文章。将来翻译成SQL语句如下:
select...wherepub_dateisnotnull;
regex和iregex:
大小写敏感和大小写不敏感的正则表达式。示例代码如下:
articles=Article.objects.filter(title__regex=r'^hello')
以上代码的意思是提取所有标题以hello字符串开头的文章。将翻译成以下的SQL语句:
select...wheretitle regexp binary'^hello';
iregex是大小写不敏感的。
根据关联的表进行查询:
假如现在有两个ORM模型,一个是Article,一个是Category。代码如下:
classCategory(models.Model):
"""文章分类表"""
name=models.CharField(max_length=100)
classArticle(models.Model):
"""文章表"""
title=models.CharField(max_length=100,null=True)
category=models.ForeignKey("Category",on_delete=models.CASCADE)
比如想要获取文章标题中包含"hello"的所有的分类。那么可以通过以下代码来实现:
categories=Category.object.filter(article__title__contains("hello"))
聚合函数:
如果你用原生SQL,则可以使用聚合函数来提取数据。比如提取某个商品销售的数量,那么可以使用Count,如果想要知道商品销售的平均价格,那么可以使用Avg。聚合函数是通过aggregate方法来实现的。在讲解这些聚合函数的用法的时候,都是基于以下的模型对象来实现的。
fromdjango.dbimportmodels
classAuthor(models.Model):
"""作者模型"""
name=models.CharField(max_length=100)
age=models.IntegerField()
email=models.EmailField()
classMeta:
db_table='author'
classPublisher(models.Model):
"""出版社模型"""
name=models.CharField(max_length=300)
classMeta:
db_table='publisher'
classBook(models.Model):
"""图书模型"""
name=models.CharField(max_length=300)
pages=models.IntegerField()
price=models.FloatField()
rating=models.FloatField()
author=models.ForeignKey(Author,on_delete=models.CASCADE)
publisher=models.ForeignKey(Publisher,on_delete=models.CASCADE)
classMeta:
db_table='book'
classBookOrder(models.Model):
"""图书订单模型"""
book=models.ForeignKey("Book",on_delete=models.CASCADE)
price=models.FloatField()
classMeta:
db_table='book_order'
Avg:求平均值。比如想要获取所有图书的价格平均值。那么可以使用以下代码实现。
fromdjango.db.modelsimportAvg
result=Book.objects.aggregate(Avg('price'))
print(result)
以上的打印结果是:
{"price__avg":23.0}
其中price__avg的结构是根据field__avg规则构成的。如果想要修改默认的名字,那么可以将Avg赋值给一个关键字参数。示例代码如下:
fromdjango.db.modelsimportAvg
result=Book.objects.aggregate(my_avg=Avg('price'))
print(result)
那么以上的结果打印为:
{"my_avg":23}
Count:获取指定的对象的个数。示例代码如下:
fromdjango.db.modelsimportCount
result=Book.objects.aggregate(book_num=Count('id'))
以上的result将返回Book表中总共有多少本图书。Count类中,还有另外一个参数叫做distinct,默认是等于False,如果是等于True,那么将去掉那些重复的值。比如要获取作者表中所有的不重复的邮箱总共有多少个,那么可以通过以下代码来实现:
fromdjang.db.modelsimportCount
result=Author.objects.aggregate(count=Count('email',distinct=True))
Max和Min:获取指定对象的最大值和最小值。比如想要获取Author表中,最大的年龄和最小的年龄分别是多少。那么可以通过以下代码来实现:
fromdjango.db.modelsimportMax,Min
result=Author.objects.aggregate(Max('age'),Min('age'))
如果最大的年龄是88,最小的年龄是18。那么以上的result将为:
{"age__max":88,"age__min":18}
Sum:求指定对象的总和。比如要求图书的销售总额。那么可以使用以下代码实现:
fromdjang.db.modelsimportSum
result=Book.objects.annotate(total=Sum("bookstore__price")).values("name","total")
以上的代码annotate的意思是给Book表在查询的时候添加一个字段叫做total,这个字段的数据来源是从BookStore模型的price的总和而来。values方法是只提取name和total两个字段的值。
更多的聚合函数请参考官方文档:https://docs.djangoproject.com/en/2.0/ref/models/querysets/#aggregation-functions
aggregate和annotate的区别:
aggregate:返回使用聚合函数后的字段和值。 (字典)
annotate:在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)。比如以上Sum的例子,如果使用的是annotate,那么将在每条图书的数据上都添加一个字段叫做total,计算这本书的销售总额。而如果使用的是aggregate,那么将求所有图书的销售总额。
F表达式和Q表达式:
F表达式:
F表达式是用来优化ORM操作数据库的。比如我们要将公司所有员工的薪水都增加1000元,如果按照正常的流程,应该是先从数据库中提取所有的员工工资到Python内存中,然后使用Python代码在员工工资的基础之上增加1000元,最后再保存到数据库中。这里面涉及的流程就是,首先从数据库中提取数据到Python内存中,然后在Python内存中做完运算,之后再保存到数据库中。示例代码如下:
employees=Employee.objects.all()
foremployeeinemployees:
employee.salary+=1000
employee.save()
而我们的F表达式就可以优化这个流程,他可以不需要先把数据从数据库中提取出来,计算完成后再保存回去,他可以直接执行SQL语句,就将员工的工资增加1000元。示例代码如下:
fromdjang.db.modelsimportF
Employee.object.update(salary=F("salary")+1000)
F表达式并不会马上从数据库中获取数据,而是在生成SQL语句的时候,动态的获取传给F表达式的值。
比如如果想要获取作者中,name和email相同的作者数据。如果不使用F表达式,那么需要使用以下代码来完成:
authors=Author.objects.all()
forauthorinauthors:
ifauthor.name==author.email:
print(author)
如果使用F表达式,那么一行代码就可以搞定。示例代码如下:
fromdjango.db.modelsimportF
authors=Author.objects.filter(name=F("email"))
Q表达式:
如果想要实现所有价格高于100元,并且评分达到9.0以上评分的图书。那么可以通过以下代码来实现:
books=Book.objects.filter(price__gte=100,rating__gte=9)
以上这个案例是一个并集查询,可以简单的通过传递多个条件进去来实现。但是如果想要实现一些复杂的查询语句,比如要查询所有价格低于10元,或者是评分低于9分的图书。那就没有办法通过传递多个条件进去实现了。这时候就需要使用Q表达式来实现了。示例代码如下:
fromdjango.db.modelsimportQ
books=Book.objects.filter(Q(price__lte=10)|Q(rating__lte=9))
以上是进行或运算,当然还可以进行其他的运算,比如有&和~(非)等。一些用Q表达式的例子如下:
fromdjango.db.modelsimportQ
# 获取id等于3的图书
books=Book.objects.filter(Q(id=3))
# 获取id等于3,或者名字中包含文字"记"的图书
books=Book.objects.filter(Q(id=3)|Q(name__contains("记")))
# 获取价格大于100,并且书名中包含"记"的图书
books=Book.objects.filter(Q(price__gte=100)&Q(name__contains("记")))
# 获取书名包含“记”,但是id不等于3的图书
books=Book.objects.filter(Q(name__contains='记')&~Q(id=3))
QuerySet API:
我们通常做查询操作的时候,都是通过模型名字.objects的方式进行操作。其实模型名字.objects是一个django.db.models.manager.Manager对象,而Manager这个类是一个“空壳”的类,他本身是没有任何的属性和方法的。他的方法全部都是通过Python动态添加的方式,从QuerySet类中拷贝过来的。所以我们如果想要学习ORM模型的查找操作,必须首先要学会QuerySet上的一些API的使用。
返回新的QuerySet的方法:
在使用QuerySet进行查找操作的时候,可以提供多种操作。比如过滤完后还要根据某个字段进行排序,那么这一系列的操作我们可以通过一个非常流畅的链式调用的方式进行。比如要从文章表中获取标题为123,并且提取后要将结果根据发布的时间进行排序,那么可以使用以下方式来完成:
articles=Article.objects.filter(title='123').order_by('create_time')
可以看到order_by方法是直接在filter执行后调用的。这说明filter返回的对象是一个拥有order_by方法的对象。而这个对象正是一个新的QuerySet对象。因此可以使用order_by方法。
那么以下将介绍在那些会返回新的QuerySet对象的方法。
filter:将满足条件的数据提取出来,返回一个新的QuerySet。具体的filter可以提供什么条件查询。请见查询操作章节。
exclude:排除满足条件的数据,返回一个新的QuerySet。示例代码如下:
Article.objects.exclude(title__contains='hello')
以上代码的意思是提取那些标题不包含hello的图书。
annotate:给QuerySet中的每个对象都添加一个使用查询表达式(聚合函数、F表达式、Q表达式、Func表达式等)的新字段。示例代码如下:
articles=Article.objects.annotate(author_name=F("author__name"))
以上代码将在每个对象中都添加一个author__name的字段,用来显示这个文章的作者的年龄。
order_by:指定将查询的结果根据某个字段进行排序。如果要倒叙排序,那么可以在这个字段的前面加一个负号。示例代码如下:
# 根据创建的时间正序排序
articles=Article.objects.order_by("create_time")
# 根据创建的时间倒序排序
articles=Article.objects.order_by("-create_time")
# 根据作者的名字进行排序
articles=Article.objects.order_by("author__name")
# 首先根据创建的时间进行排序,如果时间相同,则根据作者的名字进行排序
articles=Article.objects.order_by("create_time",'author__name')
一定要注意的一点是,多个order_by,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:
articles=Article.objects.order_by("create_time").order_by("author__name")
他会根据作者的名字进行排序,而不是使用文章的创建时间。
values:用来指定在提取数据出来,需要提取哪些字段。默认情况下会把表中所有的字段全部都提取出来,可以使用values来进行指定,并且使用了values方法后,提取出的QuerySet中的数据类型不是模型,而是在values方法中指定的字段和值形成的字典:
articles=Article.objects.values("title",'content')
forarticleinarticles:
print(article)
以上打印出来的article是类似于{"title":"abc","content":"xxx"}的形式。如果在values中没有传递任何参数,那么将会返回这个恶模型中所有的属性。
values_list:类似于values。只不过返回的QuerySet中,存储的不是字典,而是元组。示例代码如下:
articles=Article.objects.values_list("id","title")
print(articles)
那么在打印articles后,结果为
等。如果在values_list中只有一个字段。那么你可以传递flat=True来将结果扁平化。示例代码如下: articles1=Article.objects.values_list("title")
>>
articles2=Article.objects.values_list("title",flat=True)
>>
all:获取这个ORM模型的QuerySet对象。
(重点)select_related:在提取某个模型的数据的同时,也提前将相关联的数据提取出来。比如提取文章数据,可以使用select_related将author信息提取出来,以后再次使用article.author的时候就不需要再次去访问数据库了。可以减少数据库查询的次数。示例代码如下:
article=Article.objects.get(pk=1)
>>article.author# 重新执行一次查询语句
article=Article.objects.select_related("author").get(pk=2)
>>article.author# 不需要重新执行查询语句了
selected_related只能用在一对多或者一对一中,不能用在多对多或者多对一中。比如可以提前获取文章的作者,但是不能通过作者获取这个作者的文章,或者是通过某篇文章获取这个文章所有的标签。
prefetch_related:这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法是为了解决多对一和多对多的关系的查询问题。比如要获取标题中带有hello字符串的文章以及他的所有标签,示例代码如下:
fromdjango.dbimportconnection
articles=Article.objects.prefetch_related("tag_set").filter(title__contains='hello')
print(articles.query)# 通过这条命令查看在底层的SQL语句
forarticleinarticles:
print("title:",article.title)
print(article.tag_set.all())
# 通过以下代码可以看出以上代码执行的sql语句
forsqlinconnection.queries:
print(sql)
但是如果在使用article.tag_set的时候,如果又创建了一个新的QuerySet那么会把之前的SQL优化给破坏掉。比如以下代码:
tags=Tag.obejcts.prefetch_related("articles")
fortagintags:
articles=tag.articles.filter(title__contains='hello')#因为filter方法会重新生成一个QuerySet,因此会破坏掉之前的sql优化
# 通过以下代码,我们可以看到在使用了filter的,他的sql查询会更多,而没有使用filter的,只有两次sql查询
forsqlinconnection.queries:
print(sql)
那如果确实是想要在查询的时候指定过滤条件该如何做呢,这时候我们可以使用django.db.models.Prefetch来实现,Prefetch这个可以提前定义好queryset。示例代码如下:
tags=Tag.objects.prefetch_related(Prefetch("articles",queryset=Article.objects.filter(title__contains='hello'))).all()
fortagintags:
articles=tag.articles.all()
forarticleinarticles:
print(article)
forsqlinconnection.queries:
print('='*30)
print(sql)
因为使用了Prefetch,即使在查询文章的时候使用了filter,也只会发生两次查询操作。
defer:在一些表中,可能存在很多的字段,但是一些字段的数据量可能是比较庞大的,而此时你又不需要,比如我们在获取文章列表的时候,文章的内容我们是不需要的,因此这时候我们就可以使用defer来过滤掉一些字段。这个字段跟values有点类似,只不过defer返回的不是字典,而是模型。示例代码如下:
articles=list(Article.objects.defer("title"))
forsqlinconnection.queries:
print('='*30)
print(sql)
在看以上代码的sql语句,你就可以看到,查找文章的字段,除了title,其他字段都查找出来了。当然,你也可以使用article.title来获取这个文章的标题,但是会重新执行一个查询的语句。示例代码如下:
articles=list(Article.objects.defer("title"))
forarticleinarticles:
# 因为在上面提取的时候过滤了title
# 这个地方重新获取title,将重新向数据库中进行一次查找操作
print(article.title)
forsqlinconnection.queries:
print('='*30)
print(sql)
defer虽然能过滤字段,但是有些字段是不能过滤的,比如id,即使你过滤了,也会提取出来。
only:跟defer类似,只不过defer是过滤掉指定的字段,而only是只提取指定的字段。
get:获取满足条件的数据。这个函数只能返回一条数据,并且如果给的条件有多条数据,那么这个方法会抛出MultipleObjectsReturned错误,如果给的条件没有任何数据,那么就会抛出DoesNotExit错误。所以这个方法在获取数据的只能,只能有且只有一条。
create:创建一条数据,并且保存到数据库中。这个方法相当于先用指定的模型创建一个对象,然后再调用这个对象的save方法。示例代码如下:
article=Article(title='abc')
article.save()
# 下面这行代码相当于以上两行代码
article=Article.objects.create(title='abc')
get_or_create:根据某个条件进行查找,如果找到了那么就返回这条数据,如果没有查找到,那么就创建一个。示例代码如下:
obj,created=Category.objects.get_or_create(title='默认分类')
如果有标题等于默认分类的分类,那么就会查找出来,如果没有,则会创建并且存储到数据库中。这个方法的返回值是一个元组,元组的第一个参数obj是这个对象,第二个参数created代表是否创建的。
bulk_create:一次性创建多个数据。示例代码如下:
Tag.objects.bulk_create([
Tag(name='111'),
Tag(name='222'),
])
count:获取提取的数据的个数。如果想要知道总共有多少条数据,那么建议使用count,而不是使用len(articles)这种。因为count在底层是使用select count(*)来实现的,这种方式比使用len函数更加的高效。
first和last:返回QuerySet中的第一条和最后一条数据。
aggregate:使用聚合函数。
exists:判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists,这比使用count或者直接判断QuerySet更有效得多。示例代码如下:
ifArticle.objects.filter(title__contains='hello').exists():
print(True)
比使用count更高效:
ifArticle.objects.filter(title__contains='hello').count()>0:
print(True)
也比直接判断QuerySet更高效:
ifArticle.objects.filter(title__contains='hello'):
print(True)
distinct:去除掉那些重复的数据。这个方法如果底层数据库用的是MySQL,那么不能传递任何的参数。比如想要提取所有销售的价格超过80元的图书,并且删掉那些重复的,那么可以使用distinct来帮我们实现,示例代码如下:
books=Book.objects.filter(bookorder__price__gte=80).distinct()
需要注意的是,如果在distinct之前使用了order_by,那么因为order_by会提取order_by中指定的字段,因此再使用distinct就会根据多个字段来进行唯一化,所以就不会把那些重复的数据删掉。示例代码如下:
orders=BookOrder.objects.order_by("create_time").values("book_id").distinct()
那么以上代码因为使用了order_by,即使使用了distinct,也会把重复的book_id提取出来。
update:执行更新操作,在SQL底层走的也是update命令。比如要将所有category为空的article的article字段都更新为默认的分类。示例代码如下:
Article.objects.filter(category__isnull=True).update(category_id=3)
注意这个方法走的是更新的逻辑。所以更新完成后保存到数据库中不会执行save方法,因此不会更新auto_now设置的字段。
delete:删除所有满足条件的数据。删除数据的时候,要注意on_delete指定的处理方式。
切片操作:有时候我们查找数据,有可能只需要其中的一部分。那么这时候可以使用切片操作来帮我们完成。QuerySet使用切片操作就跟列表使用切片操作是一样的。示例代码如下:
books=Book.objects.all()[1:3]
forbookinbooks:
print(book)
切片操作并不是把所有数据从数据库中提取出来再做切片操作。而是在数据库层面使用LIMIE和OFFSET来帮我们完成。所以如果只需要取其中一部分的数据的时候,建议大家使用切片操作。
什么时候Django会将QuerySet转换为SQL去执行:
生成一个QuerySet对象并不会马上转换为SQL语句去执行。比如我们获取Book表下所有的图书:
books=Book.objects.all()
print(connection.queries)
我们可以看到在打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并没有真正的执行。在以下情况下QuerySet会被转换为SQL语句执行:
迭代:在遍历QuerySet对象的时候,会首先先执行这个SQL语句,然后再把这个结果返回进行迭代。比如以下代码就会转换为SQL语句:
forbookinBook.objects.all():
print(book)
使用步长做切片操作:QuerySet可以类似于列表一样做切片操作。做切片操作本身不会执行SQL语句,但是如果如果在做切片操作的时候提供了步长,那么就会立马执行SQL语句。需要注意的是,做切片后不能再执行filter方法,否则会报错。
调用len函数:调用len函数用来获取QuerySet中总共有多少条数据也会执行SQL语句。
调用list函数:调用list函数用来将一个QuerySet对象转换为list对象也会立马执行SQL语句。
判断:如果对某个QuerySet进行判断,也会立马执行SQL语句。
ORM模型迁移
迁移命令:
makemigrations:将模型生成迁移脚本。模型所在的app,必须放在settings.py中的INSTALLED_APPS中。这个命令有以下几个常用选项:
app_label:后面可以跟一个或者多个app,那么就只会针对这几个app生成迁移脚本。如果没有任何的app_label,那么会检查INSTALLED_APPS中所有的app下的模型,针对每一个app都生成响应的迁移脚本。
--name:给这个迁移脚本指定一个名字。
--empty:生成一个空的迁移脚本。如果你想写自己的迁移脚本,可以使用这个命令来实现一个空的文件,然后自己再在文件中写迁移脚本。
migrate:将新生成的迁移脚本。映射到数据库中。创建新的表或者修改表的结构。以下一些常用的选项:
app_label:将某个app下的迁移脚本映射到数据库中。如果没有指定,那么会将所有在INSTALLED_APPS中的app下的模型都映射到数据库中。
app_label migrationname:将某个app下指定名字的migration文件映射到数据库中。
--fake:可以将指定的迁移脚本名字添加到数据库中。但是并不会把迁移脚本转换为SQL语句,修改数据库中的表。
--fake-initial:将第一次生成的迁移文件版本号记录在数据库中。但并不会真正的执行迁移脚本。
showmigrations:查看某个app下的迁移文件。如果后面没有app,那么将查看INSTALLED_APPS中所有的迁移文件。
sqlmigrate:查看某个迁移文件在映射到数据库中的时候,转换的SQL语句。
migrations中的迁移版本和数据库中的迁移版本对不上怎么办?
找到哪里不一致,然后使用python manage.py --fake [版本名字],将这个版本标记为已经映射。
删除指定app下migrations和数据库表django_migrations中和这个app相关的版本号,然后将模型中的字段和数据库中的字段保持一致,再使用命令python manage.py makemigrations重新生成一个初始化的迁移脚本,之后再使用命令python manage.py makemigrations --fake-initial来将这个初始化的迁移脚本标记为已经映射。以后再修改就没有问题了。
更多关于迁移脚本的。请查看官方文档:https://docs.djangoproject.com/en/2.0/topics/migrations/
根据已有的表自动生成模型:
在实际开发中,有些时候可能数据库已经存在了。如果我们用Django来开发一个网站,读取的是之前已经存在的数据库中的数据。那么该如何将模型与数据库中的表映射呢?根据旧的数据库生成对应的ORM模型,需要以下几个步骤:
Django给我们提供了一个inspectdb的命令,可以非常方便的将已经存在的表,自动的生成模型。想要使用inspectdb自动将表生成模型。首先需要在settings.py中配置好数据库相关信息。不然就找不到数据库。示例代码如下:
DATABASES= {
'default': {
'ENGINE':'django.db.backends.mysql',
'NAME':"migrations_demo",
'HOST':'127.0.0.1',
'PORT':'3306',
'USER':'root',
'PASSWORD':'root'
}
}
比如有以下表:
article表:
tag表:
article_tag表:
front_user表:
那么通过python manage.py inspectdb,就会将表转换为模型后的代码,显示在终端:
fromdjango.dbimportmodels
classArticleArticle(models.Model):
title=models.CharField(max_length=100)
content=models.TextField(blank=True,null=True)
create_time=models.DateTimeField(blank=True,null=True)
author=models.ForeignKey('FrontUserFrontuser',models.DO_NOTHING,blank=True,null=True)
classMeta:
managed=False
db_table='article_article'
classArticleArticleTags(models.Model):
article=models.ForeignKey(ArticleArticle,models.DO_NOTHING)
tag=models.ForeignKey('ArticleTag',models.DO_NOTHING)
classMeta:
managed=False
db_table='article_article_tags'
unique_together= (('article','tag'),)
classArticleTag(models.Model):
name=models.CharField(max_length=100)
classMeta:
managed=False
db_table='article_tag'
classFrontUserFrontuser(models.Model):
username=models.CharField(max_length=100)
telephone=models.CharField(max_length=11)
classMeta:
managed=False
db_table='front_user_frontuser'
以上代码只是显示在终端。如果想要保存到文件中。那么可以使用>重定向输出到指定的文件。比如让他输出到models.py文件中。示例命令如下:
python manage.py inspectdb > models.py
以上的命令,只能在终端执行,不能在pycharm->Tools->Run manage.py Task...中使用。
如果只是想要转换一个表为模型。那么可以指定表的名字。示例命令如下:
python manage.py inspectdb article_article > models.py
修正模型:新生成的ORM模型有些地方可能不太适合使用。比如模型的名字,表之间的关系等等。那么以下选项还需要重新配置一下:
模型名:自动生成的模型,是根据表的名字生成的,可能不是你想要的。这时候模型的名字你可以改成任何你想要的。
模型所属app:根据自己的需要,将相应的模型放在对应的app中。放在同一个app中也是没有任何问题的。只是不方便管理。
模型外键引用:将所有使用ForeignKey的地方,模型引用都改成字符串。这样不会产生模型顺序的问题。另外,如果引用的模型已经移动到其他的app中了,那么还要加上这个app的前缀。
让Django管理模型:将Meta下的managed=False删掉,如果保留这个,那么以后这个模型有任何的修改,使用migrate都不会映射到数据库中。
当有多对多的时候,应该也要修正模型。将中间表注视了,然后使用ManyToManyField来实现多对多。并且,使用ManyToManyField生成的中间表的名字可能和数据库中那个中间表的名字不一致,这时候肯定就不能正常连接了。那么可以通过db_table来指定中间表的名字。示例代码如下:
classArticle(models.Model):
title=models.CharField(max_length=100,blank=True,null=True)
content=models.TextField(blank=True,null=True)
author=models.ForeignKey('front.User',models.SET_NULL,blank=True,null=True)
# 使用ManyToManyField模型到表,生成的中间表的规则是:article_tags
# 但现在已经存在的表的名字叫做:article_tag
# 可以使用db_table,指定中间表的名字
tags=models.ManyToManyField("Tag",db_table='article_tag')
classMeta:
db_table='article'
表名:切记不要修改表的名字。不然映射到数据库中,会发生找不到对应表的错误。
执行命令python manage.py makemigrations生成初始化的迁移脚本。方便后面通过ORM来管理表。这时候还需要执行命令python manage.py migrate --fake-initial,因为如果不使用--fake-initial,那么会将迁移脚本会映射到数据库中。这时候迁移脚本会新创建表,而这个表之前是已经存在了的,所以肯定会报错。此时我们只要将这个0001-initial的状态修改为已经映射,而不真正执行映射,下次再migrate的时候,就会忽略他。
将Django的核心表映射到数据库中:Django中还有一些核心的表也是需要创建的。不然有些功能是用不了的。比如auth相关表。如果这个数据库之前就是使用Django开发的,那么这些表就已经存在了。可以不用管了。如果之前这个数据库不是使用Django开发的,那么应该使用migrate命令将Django中的核心模型映射到数据库中。