数据查询

一旦创建了数据模型后,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)

需要注意两点:

  1. 当get查询时,没有相关对象时,将产生DoesNotExist异常
  2. 当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都具有一个缓存,来减少和数据库的连接。其工作过程如下:

  1. 新创建的QuerySet的缓存是空的
  2. 当执行该QuerySet时将查询结果存入缓存
  3. 再次使用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
posted @ 2024-06-16 00:37  Python习者  阅读(8)  评论(0)    收藏  举报