ORM之增删改查
Django自动为所有的模型提供了一套完善、方便、高效的API。
本节的内容基于如下的一个book应用模型:
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE) # to_field="id",不写默认关联主键
def __str__(self):
return self.name # 打印对象的name值。
class AuthorDetail(models.Model):
birthday = models.DateField()
addr = models.CharField(max_length=64)
def __str__(self):
return self.addr
class Publish(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author',)
def __str__(self):
return self.title
创建对象(增)
假设模型位于mysite/book/models.py
文件中,那么创建对象的方式如下:
>>>from book.models import Book,Publish,AuthorDetail,Author
>>> Publish_obj = Publish(name='haha出版社')
>>> Publish_obj.save()
在后台,这会运行一条SQL的INSERT语句。如果你不显式地调用save()方法,Django不会立刻将该操作反映到数据库中。save()方法没有返回值,它可以接受一些额外的参数。
如果想要一行代码完成上面的操作,可使用creat()
方法,它可以省略save的步骤:
b = Publish.objects.create(name='haha出版社') # 这个Publish.objects就像Publish表的管理器,提供了增删改查所有的方法。
print(b) #由于有__str__所以打印出的为: haha出版社
# 可用该对象进行值的更改。
b.name = 'New name'
b.save()
在后台,这会运行一条SQL的UPDATE语句。如果你不显式地调用save()方法,Django不会立刻将该操作反映到数据库中。
若Publish有多个字段,可用**的打散创建:
dic1 = {'name':'','':''...}
book.objects.create(**dic1)
批量创建
Publish_list = []
for i in range(10):
Pl_obj = models.Publish(
name='haha%s' % i,
)
Publish_list.append(Pl_obj)
Publish.objects.bulk_create(Publish_list) # 批量插入,速度快
bulk_create
:批量插入,速度快
update_or_create
:有就更新,没有就创建 。
get_or_create
:有就查询出来,没有就创建。
保存外键和多对多字段
多对一:保存一个外键字段和保存普通字段没什么区别,只是要注意值的类型要正确。
方式1:
publish_obj = Publish.objects.get(id=1) # 拿到id为1的出版社对象
book_obj = Book.objects.create(title="book1", price=100, publish=publish_obj) # 出版社对象作为值给publish,其实就是自动将publish字段变成publish_id,然后将publish_obj的id给取出来赋值给publish_id字段,注意如果不是publish类的对象肯定会报错的。
方式2:
book_obj=Book.objects.create(title="book1",price=100,publish_id=1) # 直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,publish:book中的属性,id:publish表中的字段,用_连接。
多对多字段的保存稍微有点区别,需要调用一个add()
方法,而不是直接给属性赋值,但它不需要调用save方法。如下例所示:
book_obj=Book.objects.create(title="book1",price=200,publish_id=1)# 当前生成的书籍对象
# 为书籍绑定的作者对象
lun1=Author.objects.filter(name="lun1").first() # 注意取的是author的model对象
lun2=Author.objects.filter(name="lun2").first() #我们不能通过models获取自动生成的第三张表,因此不能直接给第三张表添加数据,如果是手动添加则可以过原生sql语句直接给第三张表添加数据。
# 绑定多对多关系,即向关系表book_authors中添加纪录,给书添加两个作者,下面的语法就是通过orm间接给第三张表添加两条数据
方式一:
book_obj.authors.add(yuan,egon) # 将某些特定的model对象添加到被关联对象集合中。
#看用*的打散:book_obj.authors.add(*[]) authors是book表里面那个多对多的关系字段名称。orm先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去
方式二:
book_obj.authors.add(1,2)
book_obj.authors.add(*[1,2]) # 这种方式用的最多,多对多一般在前端页面上使用的时候是多选下拉框的样子来给用户选择多个数据,这里可以让用户选择多个书籍,多个作者。
如果你指定或添加了错误类型的对象,Django会抛出异常。
检索对象(查)
想要从数据库内检索对象,你需要基于模型类,通过管理器(Manager)构造一个查询结果集(QuerySet)。
每个QuerySet代表一些数据库对象的集合。它可以包含零个、一个或多个过滤器(filters)。Filters缩小查询结果的范围。在SQL语法中,一个QuerySet相当于一个SELECT语句,而filter则相当于WHERE或者LIMIT一类的子句。
通过模型的Manager获得QuerySet,每个模型至少具有一个Manager,默认情况下,它被称作objects
,可以通过模型类直接调用它,但不能通过模型类的实例调用它,以此实现“表级别”操作和“记录级别”操作的强制分离。如下所示:
>>> Book.objects
<django.db.models.manager.Manager at 0x1f14c1faec8>
>>> b = Book(title='book1', price=200)
>>> b.objects
Traceback:
...
AttributeError: Manager isn't accessible via Book instances
检索所有对象
使用all()
方法,可以获取某张表的所有记录。
>>> all_books = Book.objects.all()
过滤对象
有两个方法可以用来过滤QuerySet的结果,分别是:
filter(kwargs)`:返回一个根据**指定参数查询出来的QuerySet</li> <li>
exclude(kwargs)`:返回除了根据**指定参数查询出来结果的QuerySet
其中,**kwargs
参数的格式必须是Django设置的一些字段格式。
例如:
Book.objects.filter(price=200)
它等同于:
Book.objects.all().filter(price=200)
链式过滤:filter和exclude的结果依然是个QuerySet,因此它可以继续被filter和exclude,这就形成了链式过滤。
注意:当在进行跨关系的链式过滤时,结果可能和你想象的不一样,参考下面的跨多值关系查询。
被过滤的QuerySets都是唯一的,每一次过滤,你都会获得一个全新的QuerySet,它和之前的QuerySet没有任何关系,可以完全独立的被保存,使用和重用。例如:
>>> q1 = Book.objects.filter(title__startswith="b")
>>> q2 = q1.exclude(price__in(100,200))
>>> q3 = q1.filter(name='book1')
例子中的q2和q3虽然由q1得来,是q1的子集,但是都是独立自主存在的。同样q1也不会受到q2和q3的影响。
QuerySets都是懒惰的
一个创建QuerySets的动作不会立刻导致任何的数据库行为。一直不断地进行filter动作,Django不会运行任何实际的数据库查询动作,直到QuerySets被提交(evaluated)。即,只有碰到某些特定的操作,Django才会将所有的操作体现到数据库内,否则它们只是保存在内存和Django的层面中。这是一种提高数据库查询效率,减少操作次数的优化设计。看下面的例子:
>>> q = Book.objects.filter(headline__startswith="b")
>>> q = q.filter(price=210)
>>> q = q.exclude(title="book2")
>>> print(q)
上面的例子,看起来执行了3次数据库访问,实际上只是在print语句时才执行1次访问。通常情况,QuerySets的检索不会立刻执行实际的数据库查询操作,直到出现类似print的请求,也就是所谓的evaluated。
检索单一对象
filter方法始终返回的是QuerySets,那怕只有一个对象符合过滤条件,返回的也是包含一个对象的QuerySets,这是一个集合类型对象,可以简单的理解为Python列表,可迭代可循环可索引。
如果检索只会获得一个对象,那么可以使用Manager的get()方法来直接返回这个对象。
>>> one_entry = Entry.objects.get(pk=1) # pk:主键
在get方法中你可以使用任何filter方法中的查询参数,用法也是一模一样。
注意:使用get()方法和使用filter()方法然后通过[0]的方式分片,有着不同的地方。看似两者都是获取单一对象。但是,如果在查询时没有匹配到对象,那么get()方法将抛出DoesNotExist异常。这个异常是模型类的一个属性,在上面的例子中,如果不存在主键为1的book对象,那么Django将抛出Entry.DoesNotExist
异常。
类似地,在使用get()方法查询时,如果结果超过1个,则会抛出MultipleObjectsReturned异常,这个异常也是模型类的一个属性。
QuerySet使用限制
使用类似Python对列表进行切片的方法可以对QuerySet进行范围取值。它相当于SQL语句中的LIMIT和OFFSET子句。参考下面的例子:
>>> Book.objects.all()[:5] # 返回前5个对象
>>> Book.objects.all()[5:10] # 返回第6个到第10个对象
注意:不支持负索引。例如 Entry.objects.all()[-1]是不允许的
通常情况,切片操作会返回一个新的QuerySet,并且不会被立刻执行。但是有一个例外,那就是指定步长的时候,查询操作会立刻在数据库内执行,如下:
>>> Book.objects.all()[:10:2]
若要获取单一的对象而不是一个列表(例如,SELECT foo FROM bar LIMIT 1),可以简单地使用索引而不是切片。例如,下面的语句返回数据库中根据价格排序后的第一本book:
>>> Book.objects.order_by('price')[0]
它相当于:
>>> Book.objects.order_by('price')[0:1].get()
注意:如果没有匹配到对象,那么第一种方法会抛出IndexError异常,而第二种方式会抛出DoesNotExist异常。也就是说在使用get和切片的时候,要注意查询结果的元素个数。
字段查询
字段查询其实就是filter()、exclude()和get()等方法的关键字参数。 其基本格式是:field__lookuptype=value
,注意其中是双下划线。 例如:
>>> Book.objects.filter(price__lte=100)
# 相当于:
SELECT * FROM book_Book WHERE price <= 100;
其中的字段必须是模型中定义的字段之一。但是有一个例外,那就是ForeignKey字段,你可以为其添加一个“_id”后缀(单下划线)。这种情况下键值是外键模型的主键原生值。例如:
>>> Book.objects.filter(Author_id=4)
如果你传递了一个非法的键值,查询函数会抛出TypeError异常。
exact
默认类型。如果不提供查询类型,或者关键字参数不包含一个双下划线,那么查询类型就是这个默认的exact。
>>> Book.objects.get(title__exact="book1")
# 相当于
# SELECT ... WHERE title = 'book1';
# 下面两个相当
>>> Book.objects.get(id__exact=14) # Explicit form
>>> Book.objects.get(id=14) # __exact is implied.
多对一(外键)
正向查询:
直接通过圆点加属性,访问外键对象:
>>> b = Book.objects.get(id=2)
>>> b.publish # 返回关联的Publish对象
要注意的是,对外键的修改,必须调用save方法进行保存,例如:
>>> b = Book.objects.get(id=2)
>>> b.publish=somePublish # 返回关联的Publish对象
>>> b.save()
如果一个外键字段设置有null=True
属性,那么可以通过给该字段赋值为None的方法移除外键值:
>>> b = Book.objects.get(id=2)
>>> b.publish = None
>>> b.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
在第一次对一个外键关系进行正向访问的时候,关系对象会被缓存。随后对同样外键关系对象的访问会使用这个缓存,例如:
>>> b = Book.objects.get(id=2)
>>> print(b.publish) # 访问数据库,获取实际数据
>>> print(b.publish) # 不会访问数据库,直接使用缓存的版本
请注意QuerySet的select_related()
方法会递归地预填充所有的一对多关系到缓存中。例如:
>>> b = Book.objects.select_related().get(id=2) # 访问数据库
>>> print(b.publish) # 不会访问数据库,直接使用缓存
>>> print(b.publish) # 不会访问数据库,直接使用缓存
反向查询:
一个Book对象b可以通过publish属性b.publish
获取关联的Publish对象。反过来,Publish对象p可以通过book_set
属性p.book_set.all()
访问与它关联的所有Book对象。
如果一个模型有ForeignKey,那么该ForeignKey所指向的外键模型的实例可以通过一个管理器进行反向查询,返回源模型的所有实例。默认情况下,这个管理器的名字为FOO_set
,其中FOO是源模型的小写名称。该管理器返回的查询集可以用前面提到的方式进行过滤和操作。
>>> p = Publish.objects.get(id=1)
>>> p.book_set.all() # Returns all book objects related to Publish.
# p.book_set is a Manager that returns QuerySets.
>>> p.book_set.filter(title__contains='b')
>>> p.book_set.count()
可以在ForeignKey字段的定义中,通过设置related_name
来重写FOO_set
的名字。举例说明,如果你修改Entry模型publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE,related_name='book')
,那么上面的例子会变成下面的样子:
>>> p = Publish.objects.get(id=1)
>>> p.book.all() # Returns all book objects related to Publish.
# p.book_set is a Manager that returns QuerySets.
>>> p.book.filter(title__contains='b')
>>> p.book.count()
处理关联对象的其它方法:
除了在前面定义的QuerySet方法之外,ForeignKey管理器还有其它方法用于处理关联的对象集合。下面是每个方法的概括。
add(obj1, obj2, ...):添加指定的模型对象到关联的对象集中。
create(**kwargs):创建一个新的对象,将它保存并放在关联的对象集中。返回新创建的对象。
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除:book_obj.authors.remove(*[1,2]),将多对多的关系数据删除
book_obj.authors.clear() # 清空被关联对象集合
book_obj.authors.set() # 先清空再设置
若要一次性给关联的对象集赋值,使用set()方法,并给它赋值一个可迭代的对象集合或者一个主键值的列表。例如:
p = Publish.objects.get(id=1)
p.book_set.set([b1, b2])
在这个例子中,b1和b2可以是完整的Book实例,也可以是整数的主键值。
如果clear()方法可用,那么在将可迭代对象中的成员添加到集合中之前,将从book_set
中删除所有已经存在的对象。如果clear()方法不可用,那么将直接添加可迭代对象中的成员而不会删除所有已存在的对象。
这节中的每个反向操作都将立即在数据库内执行。所有的增加、创建和删除操作也将立刻自动地保存到数据库内。
多对多
多对多关系的两端都会自动获得访问另一端的API。这些API的工作方式与前面提到的“反向”一对多关系的用法一样。
唯一的区别在于属性的名称:定义ManyToManyField的模型使用该字段的属性名称,而“反向”模型使用源模型的小写名称加上'_set' (和一对多关系一样)。
b = Book.objects.get(id=3)
b.authors.all() # Returns all Author objects for this Book.
b.authors.count()
b.authors.filter(name__contains='lun')
#
a = Author.objects.get(id=5)
a.book_set.all() # Returns all Entry objects for this Author.
与外键字段中一样,在多对多的字段中也可以指定related_name
名。
(注:在一个模型中,如果存在多个外键或多对多的关系指向同一个外部模型,必须给他们分别加上不同的related_name
,用于反向查询)
与普通的多对多不一样,使用自定义中间表的多对多不能使用add(), create(),remove(),和set()方法来创建、删除关系。
因为上面的方法无法提供加入时间、邀请原因等中间模型需要的字段内容。唯一的办法只能是通过创建中间模型的实例来创建这种类型的多对多关联。但是,clear()方法是有效的,它能清空所有的多对多关系。
>>> # 甲壳虫乐队解散了
>>> beatles.members.clear()
>>> # 删除了中间模型的对象
>>> Membership.objects.all()
<QuerySet []>
一旦你通过创建中间模型实例的方法建立了多对多的关联,你立刻就可以像普通的多对多那样进行查询操作:
# 查找组内有Paul这个人的所有的组(以Paul开头的名字)
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>
可以使用中间模型的属性进行查询:
# 查找甲壳虫乐队中加入日期在1961年1月1日之后的成员
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>
可以像普通模型一样使用中间模型:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'
对于中间表,默认情况下,中间模型只能包含一个指向源模型的外键关系,上面例子中,也就是在Membership中只能有Person和Group外键关系各一个,不能多。否则,你必须显式的通过ManyToManyField.through_fields
参数指定关联的对象。参考下面的例子:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
一对一
一对一非常类似多对一关系,可以简单的通过模型的属性访问关联的模型。
d = Author.objects.get(id=2)
print(d.AuthorDetail.addr)
不同之处在于反向查询的时候。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合:
authorDet=AuthorDetail.objects.filter(addr="beijing")[0]
authorDet.author.name
如果没有对象赋值给这个关系,Django将抛出一个DoesNotExist异常。 可以给反向关联进行赋值,方法和正向的关联一样:
authorDet.Author = 2
反向关联的实现方式
一些ORM框架需要你在关系的两端都进行定义。Django的开发者认为这违反了DRY (Don’t Repeat Yourself)原则,所以在Django中你只需要在一端进行定义。
那么这是怎么实现的呢?因为在关联的模型类没有被加载之前,一个模型类根本不知道有哪些类和它关联。
答案在app registry
。在Django启动的时候,它会导入所有INSTALLED_APPS
中的应用和每个应用中的模型模块。每创建一个新的模型时,Django会自动添加反向的关系到所有关联的模型。如果关联的模型还没有导入,Django将保存关联的记录并在关联的模型导入时添加这些关系。
由于这个原因,将模型所在的应用都定义在INSTALLED_APPS
的应用列表中就显得特别重要。否则,反向关联将不能正确工作。
通过关联对象进行查询
涉及关联对象的查询与正常值的字段查询遵循同样的规则。当指定查询需要匹配的值时,可以使用一个对象实例或者对象的主键值。
例如,有一个id=5的Publish对象p,下面的三个查询将是完全一样的:
Book.objects.filter(Publish=p) # 使用对象实例
Book.objects.filter(Publish=p.id) # 使用实例的id
Book.objects.filter(Publish=5) # 直接使用id
跨越关系查询
要跨越某个关联,只需使用关联的模型字段名称,并使用双下划线分隔,直至想要的字段(可以链式跨越,无限跨度)。例如:
# 返回所有Book的Author为lun的Book对象
# 一定要注意,返回的是Book对象,而不是Author对象。
# objects前面用的是哪个class,返回的就是哪个class的对象。
>>> Book.objects.filter(authors__name='lun') # authors: book表中的多对多关联的模型字段名称
<QuerySet [<Book: book1>, <Book: book1>]>
反之亦然,如果要引用一个反向关联,只需要使用模型的小写名
# 找到书价格为20的作者对象。
>>> Author.objects.filter(book__price='20')# book是Book类名小写。
基于双下划线的查询:正向查询按字段,反向查询按表名小写(一对一、一对多、多对多都是一个写法)注意,写orm查询的时候,哪个表在前哪个表在后都没问题,因为走的是join连表操作。
如果在多级关联中进行过滤而且其中某个中间模型没有满足过滤条件的值,Django将把它当做一个空的(所有的值都为NULL)但是合法的对象,不会抛出任何异常或错误。通常,这是比较符合逻辑的处理方式。
但当使用isnull
的时候:
Book.objects.filter(entry__authors__name__isnull=True)
这将返回Blog对象,它关联的entry对象的author字段的name字段为空,以及Entry对象的author字段为空。如果你不需要后者,你可以这样写:
Blog.objects.filter(entry__authors__isnull=False,entry__authors__name__isnull=True)
跨越多值的关系查询
filter和exclude的关键字参数有多个,且是跨越外键或者多对多的情况下,那么and、or、非的关系就会变得不明确,建议在碰到跨关系的多值查询时,尽量使用Q查询。
使用Q对象查询
普通filter函数里的条件都是“and”逻辑,可以用Q对象查询实现或与非逻辑。
Q来自django.db.models.Q
,用于封装关键字参数的集合,可以作为关键字参数用于filter、exclude和get等函数。 例如:
from django.db.models import Q
Q(question__startswith='What')
可以使用&
或者|
或~
来组合Q对象,分别表示与或非逻辑。它将返回一个新的Q对象。
bookList=Book.objects.filter(Q(authors__name="lun")|Q(authors__name="luns"))
可以组合&
和|
操作符以及使用括号进行分组来编写任意复杂的Q
对象。同时,Q
对象可以使用~
操作符取反,这允许组合正常的查询和取反(NOT
) 查询:
bookList=Book.objects.filter(Q(authors__name="lun") & ~Q(price=20)).values_list("title")
bookList=Book.objects.filter(Q(Q(authors__name="lun") & ~Q(price=20))&Q(id__gt=6)).values_list("title") # 可以进行Q嵌套,多层Q嵌套等,在工作中比较常用
默认情况下,以逗号分隔的都表示AND关系:
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
# 它相当于
# SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
当关键字参数和Q对象组合使用时,Q对象必须放在前面,如下例子:
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),question__startswith='Who',)
如果关键字参数放在Q对象的前面,则会报错。
使用F表达式引用模型的字段
使用F表达式将模型的一个字段与同一个模型的另外一个字段进行比较。
例如,在book表里面加上两个字段:评论数:commentNum,收藏数:KeepNum,查询评论数大于收藏数的书籍,可以构造一个F()
对象,并在查询中使用该F()对象:
>>> from django.db.models import F
>>> Book.objects.filter(commentNum__lt=F('keepNum'))
Django支持对F()对象进行加、减、乘、除、取模以及幂运算等算术操作。两个操作数可以是常数和其它F()对象。例如查询评论数大于收藏数2倍的书籍:
>>> Book.objects.filter(commentNum__lt=F('keepNum')*2)
还可以在F()中使用双下划线来进行跨表查询。例如,查询author的名字与Publish名字相同的Book:
>>> Book.objects.filter(authors__name=F('publish__name'))
对于date和date/time字段,还可以加或减去一个timedelta对象。
F()对象还支持.bitand()
、.bitor()
、.bitrightshift()
和.bitleftshift()
4种位操作,例如:
>>> F('somefield').bitand(16)
修改操作也可以使用F函数,比如将每一本书的价格提高30元:
Book.objects.all().update(price=F("price")+30)
主键的快捷查询方式:pk
pk就是primary key
的缩写。通常情况下,一个模型的主键为“id”,所以下面三个语句的效果一样:
>>> Book.objects.get(id__exact=14) # Explicit form
>>> Book.objects.get(id=14) # __exact is implied
>>> Book.objects.get(pk=14) # pk implies id__exact
可以联合其他类型的参数:
# Get blogs entries with id 1, 4 and 7
>>> Book.objects.filter(pk__in=[1,4,7])
# Get all blog entries with id > 14
>>> Book.objects.filter(pk__gt=14)
可以跨表操作:
>>> Book.objects.filter(publish__id__exact=3)
>>> Book.objects.filter(publish__id=3)
>>> Book.objects.filter(publish__pk=3)
当主键不是id的时候,则无法使用。
比较对象
要比较两个模型实例,只需要使用python提供的双等号比较符就可以了。在后台,其实比较的是两个实例的主键的值。下面两种方法是等同的:
>>> some_obj == other_obj
>>> some_obj.id == other_obj.id
如果模型的主键不叫做“id”也没关系,后台总是会使用正确的主键名字进行比较,例如,如果一个模型的主键的名字是“name”,那么下面是相等的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
删除对象
删除对象使用的是对象的delete()
方法。该方法将返回被删除对象的总数量和一个字典,字典包含了每种被删除对象的类型和该类型的数量。如下所示:
>>> Book.objects.get(id=2).delete()
(2, {'book.Book_authors': 1, 'book.Book': 1})
# 当Django删除一个对象时,它默认使用SQL的ON DELETE CASCADE约束,也就是说,任何有外键指向要删除对象的对象将一起被删除。这种级联的行为可以通过的ForeignKey的on_delete参数自定义。
也可以批量删除。每个QuerySet都有一个delete()方法,它能删除该QuerySet的所有成员。例如:
>>> Book.objects.filter(price=200).delete()
需要注意的是,有可能不是每一个对象的delete方法都被执行。如果改写了delete方法,为了确保对象被删除,你必须手动迭代QuerySet进行逐一删除操作。
注意,delete()
是唯一没有在管理器上暴露出来的方法。这是刻意设计的一个安全机制,用来防止意外地请求类似Entry.objects.delete()
的动作,而不慎删除了所有的条目。如果确实想删除所有的对象,则必须明确地请求一个完全的查询集,像下面这样:
Book.objects.all().delete()
复制模型实例
没有内置的方法用于复制模型的实例,但可以创建一个新的实例并将原实例的所有字段都拷贝过来。最简单的方法是将原实例的pk设置为None,这会创建一个新的实例copy。示例如下:
book = Book(title='book1', price='100')
book.save() # blog.pk == 1
#
book.pk = None
book.save() # blog.pk == 2
但是在使用继承的时候,情况会变得复杂,基于继承的工作机制,你必须同时将pk和id设为None。
对于外键和多对多关系,复制条目后,必须为新条目设置多对多关系。
对于OneToOneField,还要复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。
批量更新对象
使用update()
方法可以批量为QuerySet中所有的对象进行更新操作。
# 更新所有2007年发布的entry的headline
Book.objects.filter(title='book1').update(price=100)
只可以对普通字段和ForeignKey字段使用这个方法。若要更新一个普通字段,只需提供一个新的常数值。若要更新ForeignKey字段,需设置新值为你想指向的新模型实例。例如:
>>> b = Publish.objects.get(pk=1)
# 修改所有的Entry,让他们都属于b
>>> Book.objects.all().update(publish=b)
update方法会被立刻执行,并返回操作匹配到的行的数目(有可能不等于要更新的行的数量,因为有些行可能已经有这个新值了)。唯一的约束是:只能访问一张数据库表。可以根据关系字段进行过滤,但只能更新模型主表的字段。例如:
>>> b = Publish.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> Book.objects.select_related().filter(Publish=b).update(price=100)
要注意的是update()方法会直接转换成一个SQL语句,并立刻批量执行。它不会运行模型的save()方法,或者产生pre_save
或post_save
信号(调用save()
方法产生)或者服从auto_now
字段选项。如果想保存QuerySet中的每个条目并确保每个实例的save()方法都被调用,则不需要使用任何特殊的函数来处理。只需要迭代它们并调用save()方法:
for item in my_queryset:
item.save()
update方法可以配合F表达式。然而,与filter和exclude子句中的F()对象不同,在update中不可以使用F()对象进行跨表操作,只可以引用正在更新的模型的字段。如果尝试使用F()对象引入另外一张表的字段,将抛出FieldError异常。
聚合查询
aggregate(*args, **kwargs)
,要与聚合函数结合。
# 计算所有图书的平均价格
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price')) # 或者给它起名字:aggretate(a=Avg('price'))
{'price__avg': 34.35} # {'a': 34.35}
aggregate()
是QuerySet
的一个终止子句,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
如果希望生成不止一个聚合可以向aggregate()
子句中添加另一个参数。
# 所有图书价格的最大值和最小值
>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) # count('id'),count(1)也可以统计个数,Book.objects.all().aggregete和Book.objects.aggregate(),都可以
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
分组查询
annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
queryResult= Publish.objects
.annotate(MinPrice=Min("book__price"))
.values_list("name","MinPrice")
print(queryResult)
<QuerySet [('haha出版社', Decimal('100.00'))]>
根据一本图书作者数量的多少对查询集 QuerySet进行排序:
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
统计不止一个作者的图书:
queryResult=Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1) # filter也是也可以是querset来调用
统计每一本书的作者个数
ret=models.Book.objects.annotate(authorsNum=Count('authors__name')).values('title','authorsNum') #注意写法,values里面写的个数的别名
ret=models.Book.objects.annotate(a=Count('author__name')).filter(a__gt=2).values('title','a')
缓存与查询集
每个QuerySet都包含一个缓存,用于减少对数据库的实际操作。
对于新创建的QuerySet,它的缓存是空的。当QuerySet第一次被提交后,数据库执行实际的查询操作,Django会把查询的结果保存在QuerySet的缓存内,随后的对于该QuerySet的提交将重用这个缓存的数据。
要想高效的利用查询结果,降低数据库负载,必须善于利用缓存。看下面的例子,这会造成2次实际的数据库操作,加倍数据库的负载,同时由于时间差的问题,可能在两次操作之间数据被删除或修改或添加,导致脏数据的问题:
>>> print([b.price for b in Book.objects.all()])
>>> print([b.title for b in Book.objects.all()])
为了避免上面的问题,好的使用方式如下,这只产生一次实际的查询操作,并且保持了数据的一致性:
>>> queryset = Book.objects.all()
>>> print([b.price for b in queryset]) # 提交查询
>>> print([b.title for b in queryset])# 重用查询缓存
何时不会被缓存
有一些操作不会缓存QuerySet,例如切片和索引。这就导致这些操作没有缓存可用,每次都会执行实际的数据库查询操作。例如:
>>> queryset = Book.objects.all()
>>> print(queryset[5]) # 查询数据库
>>> print(queryset[5]) # 再次查询数据库
但是,如果已经遍历过整个QuerySet,那么就相当于缓存过,后续的操作则会使用缓存,例如:
>>> queryset = Book.objects.all()
>>> [entry for entry in queryset] # 查询数据库
>>> print(queryset[5]) # 使用缓存
>>> print(queryset[5]) # 使用缓存
下面的这些操作都将遍历QuerySet并建立缓存:
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)
注意:简单的打印QuerySet并不会建立缓存,因为__repr__()
调用只返回全部查询集的一个切片。
在LIKE语句中转义百分符号和下划线
在原生SQL语句中%
符号有特殊的作用。Django帮你自动转义了百分符号和下划线,可以和普通字符一样使用它们,如下所示:
>>> Entry.objects.filter(headline__contains='%')
# 它和下面的一样
# SELECT ... WHERE headline LIKE '%\%%';
使用原生SQL语句
如果你发现需要编写的Django查询语句太复杂,你可以回归到手工编写SQL语句。Django对于编写原生的SQL查询有许多选项。
最后,需要注意的是Django的数据库层只是一个数据库接口。你可以利用其它的工具、编程语言或数据库框架来访问数据库,Django没有强制指定你非要使用它的某个功能或模块。
Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。
执行原生查询
raw()语法查询必须包含主键。这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例。
例:
class Person(models.Model):
first_name = models.CharField(...)
last_name = models.CharField(...)
birth_date = models.DateField(...)
可以像下面这样执行原生SQL语句
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
... print(p)
raw()方法自动将查询字段映射到模型字段。还可以通过translations参数指定一个把查询的字段名和ORM对象实例的字段名互相对应的字典
d = {'tname': 'haha'}
ret = models.Student.objects.raw('select * from app02_teacher', translations=d)
for i in ret:
print(i.id, i.sname, i.haha)
原生SQL还可以使用参数,注意不要自己使用字符串格式化拼接SQL语句,防止SQL注入!
d = {'tname': 'haha'}
ret = models.Student.objects.raw('select * from app02_teacher where id > %s', translations=d, params=[1,])
for i in ret:
print(i.id, i.sname, i.haha)
直接执行自定义SQL
有时候不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库,完全避开模型层。
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
ret = cursor.fetchone()