数据查询
一旦创建了数据模型后,Django会自动创建一个数据库API用来进行数据的增删改查。
将以如下的模型案例进行讲解。
# models.py
from datetime import date
from django.db import models
class Blog(models.Model):
name = models.CharField(max_lenght=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = modles.DateField()
mod_date = models.DateField(default=date.today)
authors = models.ManyToManyField(Author)
numbers_of_comments = models.IntegerField(default=0)
numbers_of_pingbacks = models.IntegerField(default=0)
rating = models.IntegerField(default=5)
def __str__(self):
return self.headline
创建对象
Django中利用一个模型类表示一个数据库表,一个模型类的实例对象代表一个数据库表中的记录。
所以可以通过创建一个模型类的实例对象,并通过save()方法将一条记录插入到数据库表中。
from blog.models import Blog
b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
b.save()
注意!
只有执行save()方法后,数据库中才会执行一条插入语句。
save()方法没有返回值
通过create()方法可以一步完成模型类的实例对象创建和数据库中插入数据。
from blog.models import Blog
b = Blog.objects.create(name="Beatles Blog", tagline="All the latest Beatles news.")
更新数据
利用save()方法可以更新数据库中已经存在的对象。例如更新刚刚插入数据库的数据的name值,可以利用下面的代码进行更新。
b.name = "New name"
b.save()
更新ForeignKey和ManyToManyField字段
更新ForeignKey字段和更新一般字段的方式一样,只需为该字段赋值一个正确类型的对象即可。
from blog.models import Blog, Entry
entry = Entry.objects.get(pk=1)
cheese_blog = Blog.objects.get(name="Cheddar Talk")
entry.blog = cheese_blog # 此处Entry的外键字段blog必须指向一个Blog的实例对象
entry.save()
更新ManyToManyField字段时,利用add()方法向其中添加一个关系。
from blog.models import Author
joe = Author.objects.create(name="Joe")
entry.authors.add(joe) # 在entry的authors中添加一条
当一次性向ManyToManyField字段中添加多条记录时,可以在add()方法中传入多个实例对象。
john = Author.objects.create(name="John")
paul = Author.objects.create(name="Paul")
entry.authors.add(john, paul) # 传入多个实例对象时,可以一次性添加多个
注意!
添加ForeignKey和ManyToManyField字段的记录时,对象类型必须和模型中定义的一致,否则将报错!
获取数据
利用模型类的Manager构造一个QuerySet类来获取数据库中的数据。
QuerySet代表数据库中一系列对象的集合。可以通过添加0个,1个或多个过滤条件,来过滤数据。在SQL中,一个QuerySet类似于一个SELECT语句,而一个过滤器相当于一个Where条件或Limit语句。
每个模型类至少拥有一个Manger,默认只能通过模型类的objects属性获取。
Blog.objects
获取全部数据
利用Manager的all方法可以获取数据表的全部数据。
all_entries = Entry.objects.all()
all方法返回包含所有实例对象的QuerySet。
过滤数据
最常用来用过滤数据的方法有两个:
- filter(**kwargs)
返回一个满足查询条件的QuerySet
- exclude(**kwargs)
返回一个不满足查询条件的QuerySet
查询条件需要满足一定个格式要求,例如
Entry.objects.filter(pub_date__year=2006)
链式过滤
QuerySet类过滤后的结果依旧是QuerySet类,所以还可以继续使用过滤方法进行过滤。
Entry.objects.filter(headline__startswith="What").exclude(
pub_date__gte=datetime.date.today()
).filter(
pub_date__gte=datetime.date(2025,1,30))
过滤的QuerySet是唯一的
每过滤完一次得到的QuerySet都是全新的一个,和之前的QuerySet相互独立,且没有关联。
QuerySet是懒加载的
QuerySet创建后并不是立马就和数据联系获取数据的,而是只有真正使用QuerySet时才和数据库联系。
q = Entry.objects.filter(headline__startswith="what")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q)
上面的例子中只执行了一次和数据库的连接操作,而不是三次。
利用get方法获取单个数据
filter方法使用返回一个QuerySet,即使只有一个对象满足条件的情况下,也是返回一个只包含一个对象的QuerySet。
如果清楚的知道只有一个对象满足查询要求,可以使用Manager的get方法直接返回那个对象。
one_entry = Entry.objects.get(pk=1)
需要注意两点:
- 当get查询时,没有相关对象时,将产生DoesNotExist异常
- 当get查询时,存在多于1个对象时,将产生MultipleObjectsReturned异常
限制QuerySet
利用QuerySet的切片来限制返回的数量,类似于SQL的LIMIT和OFFSET语句。
# 返回前五个
Entry.objects.all[:5]
# 返回6~10个
Entry.objects.all[5:10]
注意!
不支持负数索引的使用
通常切片QuerySet将返回一个新的QuerySet,并不执行查询。但是当使用带步长的切片时,则会执行查询。
切片的QuerySet不支持进一步的过滤和排序操作。
当获取单个对象而不是对象列表时,可以在利用索引获取。当数据不存在时,将产生IndexError异常。
Entry.objects.order_by("headline")[0]
# 等价于下面的语句
Entry.objects.order_by("headline")[0:1].get()
字段条件查询格式
通过向filter(),exclude()和get()方法中添加字段查询条件,可以达到过滤数据的作用。
基础的字段条件查询格式为:字段名__条件类型=目标值。
Entry.objects.filter(pub_date__lte="2006-01-01")
通常字段名必须时被查询模型类的字段名,但是ForeignKey字段时需要在字段名后带_id后缀,这种情况下就是用外键表的主键id来取值。
当字段名错误时,将产生TypeError异常。
常用的一些条件类型如下:
- exact:等于
- iexact:不区分大小写等于
- contains:包含
- icontains:不区分大小写包含
- startswith,endswith:以什么开头,以什么结尾
- istartswith,iendswith:不区分大小写,以什么开头,以什么结尾
- lt,lte,gt,gte:小于,小于等于,大于,大于等于
跨关系查询
Django利用SQL中的JOIN语句来支持关系查询。
通过跨模型的关联字段,来完成跨关系查询,关联字段和跨模型类的属性用双下划线隔开。
# 正向查询,Entry中定义了blog外键字段
Entry.objects.filter(blog__name="Beatles Blog")
# 反向查询,Blog中没有明确定义与Entry的关系)
Blog.objects.filter(entry__headline__contains="Lennon")
Django支持跨多个关系表的查询。
# 由Blog查到Entry,由Entry查到Author
Blog.objects.filter(entry__author__name="Lennon")
在联机跨关系查询中一般查询结果都正确,但是当条件类型是isnull时将产生歧义:
Blog.objects.filter(entry__authors__name__isnull=True)
将返回author name为空的Blog和entry中author为空的Blog。
为了避免这种歧义的发生,一般使用多条件查询:
Blog.objects.filter(entry__authors__isnull=False,entry_authors__name=True)
跨多值关系
当执行ManyToManyField或反向ForeignKey查询时,过滤多个属性时,会产生一个问题,是否需要在相同的相关对象中具有一致的属性。
# 返回Blog的entry同时满足Lennon和2008两个条件的Blog
Blog.objects.filter(entry__headline__contains="Lennon", entry__pub_date__year=2008)
# 返回Blog的entry满足Lennon或2008条件的Blog
Blog.objects.filter(entry__headline__contains="Lennon").filter(
entry__pub_date__year=2008)
注意!
如果每个过滤条件都查询相同的关联对象,应该将过滤条件写在同一个过滤函数中,否则将会产生错误。
跨表查询时每有一个filter就相当于做一次join,所以第二中方式将产生重复数据
同表不同字段的比较
Django中通过F类的实例对象完成同一个表中不同字段之间的比较。
from django.db.models import F
Entry.objects.filter(number_of_comments__gt=F("number_of_pingback")
F(另一个字段名)的方式引用同一个表中的另一个字段的值。
QuerySet效率问题
每个QuerySet都具有一个缓存,来减少和数据库的连接。其工作过程如下:
- 新创建的QuerySet的缓存是空的
- 当执行该QuerySet时将查询结果存入缓存
- 再次使用QuerySet时直接从缓存中取值
# 效率低
print([e.headline for e in Entry.objects.all()]
print([e.pub_date for e in Entry.objects.all()]
# 效率高
queryset = Entry.object.all()
print([p.headline for p in queryset])
print([p.pub_date for p in queryset])
QuerySet并非总是缓存结果。当执行QuerySet的部分数据时,会检查缓存是否存在,但是不会将其结果缓存。
这意味着执行切片或索引时不会执行缓存。
queryset = Entry.objects.all() # 创建了,但是没有执行
print(queryset[5]) # 只查询了部分,没有将该部分数据缓存
print(queryset[5]) # 没有走缓存
queryset = Entry.objects.all()
[entry for Entry.objects.all()] # 执行了全部queryset,会将其缓存
print(queryset[5]) # 从缓存中查询
print(queryset[5]) # 从缓存中查询
复杂条件查询
利用Q类的实例对象之间的逻辑运算实现复杂的条件查询。
from django.db.models import Q
Q(question__startswith="what")
Q的实例对象支持&,|,~的操作,类似与SQL中的AND, OR, NOT语句
Poll.objects.get(
Q(question__startswith="who"),
Q(pub_date=date(2005,5,2))|Q(pub_date=date(2005,5,6)))
注意!
当Q表达式和字段条件查询同时存在时,Q表达式必须写在前面,否则将报错
# 有效的
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith="Who",
)
# 无效的
Poll.objects.get(
question__startswith="Who",
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)
删除数据
删除数据使用delete方法,将即可删除数据,并返回受影响的记录数,和一个记录每个对象类删除的数量的字典。
# 单条删除
e.delete()
# 批量删除
Entry.objects.filter(pub_date__year=2005).delete()
注意!
删除数据时注意级联删除。
delete方法在Manager中不可用
复制实例对象数据
通过设置pk为None和_state.adding为True,就可以复制一个实例对象的内容了。
blog = Blog(name="my blog", tagline="blogging is easy")
blog.save()
blog.pk = None
blog._state.adding = True
blog.save()
这种方式不会复制关系字段的内容,需要手动在赋值。
entry = Entry.objects.first()
old_authors = entry.authors.all()
entry.pk = None
entry._state.add = True
entry.save()
entry.authors.set(old_authors)
批量更新
当将一个QuerySet的字段全部设定为某个值时,使用update方法。
Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")
update只能用于更新非关系型字段和ForeignKey字段。
该方法返回受影响的行数。
update只能更新模型类的数据,不能跨表更新。
关联的对象
当模型中的字段具有ForeignKey,OneToOneField,ManyToManyField时,模型类的实例对象都具有关联到相关对象的方法。
ForeignKey关系
正向查询
具有ForeignKey字段定义的模型实例对象可以该字段的属性可以访问到关联的对象。
e = Entry.objects.get(pk=2)
e.blog # 访问到与e相关的Blog实例
e.blog = some_blog # 更新相关对象
e.save()
首次正向查询ForeignKey的关联对象时,将缓存相关信息。后期访问相同实例的外键时将访问缓存。
e = Entry.get(id=2)
print(e.blog) # 访问数据库
print(e.blog) # 访问缓存
QuerySet的select_related()方法将所有的ForeignKey关系递归的缓存了。
e = Entry.objects.select_related().get(id=2)
print(e.blog) # 访问的缓存
print(e.blog) # 访问的缓存
反向查询
外键关联的实例对象的通过字段名_set访问相关的所有实例对象的Manager。
b = Blog.objects.get(id=1)
b.entry_set.all() # 返回所有与b Blog相关的Entry实例对象
此处的entry_set可以通过在模型设置中通过related_name进行覆盖设置。
例如如果设置blog = ForeignKey(Blog, on_delete=models.CASCADE,related_name='entries'),则上述代码应为
b = Blog.objects.get(id=1)
b.entries.all() # 返回所有与b Blog相关的Entry实例对象
反向查询Manager的额外方法
- add(obj1, obj2,...): 为关联对象,添加关联对象
- create(**kwargs): 为关联对象,创建新的关联对象,并返回该对象
- remove(obj1, obj2,...): 为关联对象,删除已关联对象
- clear(): 为关联对象,清空其关联对象
- set(objs): 替换关联对象的关联对象 objs 是[obj1, obj2]
ManyToManyField
该字段的正向和反向查询都是集合,正向查询时时字段名的Manager,反向查询时默认为字段名_set的Manager。
反向查询时Manager的名称可以通过related_name进行设置。
# 正向
e = Entry.objects.get(id=2)
e.authors.all()
e.authors.count()
e.authors.filter(name__contains="John")
# 反向
a = Author.objects.get(id=5)
a.entry_set.all()
OneToOneField
其访问方式和ForeignKey的方式相同。
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
# 正向
ed = EntryDetail.objects.get(id=2)
ed.entry
# 反向
e = Entry.objects.get(id=2)
e.entrydetail

浙公网安备 33010602011771号