Django 2.0 学习(16):Django ORM 数据库操作(下)

时间:2022-07-11 14:35:14

Django ORM数据库操作(下)

一、增加表记录

对于表单有两种方式:

# 方式一:实例化对象就是一条表记录france_obj = models.Student(name="海地", course="Python", birth="2000-10-1", grade=90)
france_obj.save() # 方式二:
models.Student.objects.create(name="海地", course="Python", birth="2000-10-1", grade=90)

二、查询表记录

查询相关API:

# 查询相关API
# 1.all():查询所有
students = models.Student.objects.all()
print(students) # 打印结果是QuerySet集合 # 2.filter():可以实现且关系,但是或关系需要借助Q查询实现,查不到时会报错
print(models.Student.objects.filter(name="Frank") # 查询名字是Frank的数据
print(models.Student.objects.filter(name="Frank", grade=90) # 查询名字是Frank且分数是90的数据 # 3.get():如果找不到就会报错,如果有多个值也会报错,只能查询有一个值的数据
print(models.Student.objects.get(name="Frank") # 查询名字是Frank的model对象
print(models.Student.objects.get(nid=2) # 查询Id为2的model对象 # 4.exclude():排除条件
print(models.Student.objects.exclude(name="海地") # 查询除了名字是海地的数据 # 5.values():是QuerySet的一个方法(将对象转换成字典形式)
print(models.Student.objects.filter(name="海地").values("nid", "course") # 查询名字是海地的编号和课程
# 打印结果:<QuerySet[{"nid": 2, "course": "python"}, {"nid": 24, "course": "python"}]> # 6.values_list():是QuerySet的一个方法(将对象转换成元组形式)
print(models.Student.objects.filter(name="海地").values_list("nid", "course")
# 打印结果:<QuerySet[(2, "python"), (24, "python")] # 7.order_by():排序
print(models.Student.objects.all().order_by("grade")) # 8.reverse():倒序
print(models.Student.objects.all().reverse()) # 9.distinct():去重(只要结果里面有重复的)
print(models.Student.objects.filter(course="python").values("grade").distinct()) # 10.count():查看数据条数
print(models.Student.objects.filter(name="海地").count()

双下划线值单表查询

models.Tb1.objects.filter(id__lt=10, id__gt=1)		# 查询id大于1且小于10的数据
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 查询id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # 查询id不等于11、22、33的数据
models.Tb1.objects.filter(name__contains="ven") # 查询名字包括ven的数据
models.Tb1.objects.filter(name__icontains="ven") # 查询名字包括ven的数据,大小写不敏感
models.Tb1.objects.filter(id__range=[1, 2]) # 查询范围between and

三、修改表记录

# 方式一:
author = Author.objects.get(id=5)
author.name = "eric"
author.save() # 方式二:
Publisher.objects.filter(id=2).update(name="America") # 不能用.get(id=2)

注意:

  • 第二种方式修改不能用get的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是QuerySet对象(filter里面的条件可能有多个条件符合,比如name="eric",可能有两个name="eric"的数据);
  • 模型的save()方法,会更新一行里的所有列,而某些情况下,我们只需要更新行里的某几列;

此外,update()方法对于任何结果集(QuerySet)均有效,这意味着我们可以同时更新多条记录,update()方法会返回一个整形数值,表示受影响的记录条数;因为update返回的是一个整数,所以无法使用query属性,对于每次创建一个对象,想显示对应的原始SQL,需要在settings加上日志记录部分。

四、删除表记录

删除方法就是:delete(),它运行时立即删除对象,返回被删除对象数量和删除对象与数量字典(返回为元组)

# 删除数据
models.Student.objects.filter(nid="2").delete()

注意:无论在什么情况下,QuerySet中的delete()方法都只使用一条SQL语句一次性删除所有对象,而并不是分别删除每个对象。如果想使用在model中自定义的delete()方法,就要自行调用每个对象的delete()方法。(例如:遍历QuerySet,在每个对象上调用delete()方法,而不是使用QuerySet中的delete()方法)

在Django删除对象时,会模仿SQL约束ON DELETE CASCADE的行为,换句话说,删除一个对象时也会删除与它相关联的外键对象,例如:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

注意:delete()方法是QuerySet上的方法,但并不适用于Manager本身。这是一种保护机制,是为了避免意外地调用Entry.objects.delete()方法导致所有的记录被去删除,如果确认要删除所有的对象,必须显示地调用:

Entry.objects.all().delete()

五、模型关系及其字段

书籍模型:书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(Many-to-Many),一本书只应该由一个出版商出版,所以出版商和书籍是一对多的关联关系(One-to-Many);

  • 创建一对一的关联关系:OneToOne("要绑定关系的表名");
  • 创建多对一的关联关系:ForeignKey("要绑定关系的表名");
  • 创建多对多的关联关系:ManyToMany("要绑定关系的表名");
from django.db import models

class Book(models.Model):
nid = models.AutoField(primary_key=True) # 自增id(可以不写,默认会有自增id)
title = models.CharField(max_length=32)
publish_date = models.DateField() # 出版日期
price = models.DecimalField(max_digits=5, decimal_places=2) # 一共5位数,保留2位小数 # 一个出版商有多本书,关联字段要写在多的一方
# 不用命名为publish_id,因为Django会自动为我们加上_id
publish = models.ForeignKey('Publish') # ForeignKey(表名):建立当前模型和远程多对一的关联关系
# publish是实例对象关联的出版社对象
author_list = models.ManyToManyField("Author") # 建立多对多的关联关系 def __str__(self): # __str__方法是用来把对象转换成字符串的,返回什么内容就打印什么内容
return self.title class Publish(models.Model):
# 不写id的时候数据库会自动创建自增id
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32) def __str__(self):
return self.name class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField() class AuthorDetail(models.Model):
tel = models.IntegerField()
addr = models.CharField(max_length=32)
author = models.OneToOneField("Author") # 建立一对一的关联关系

注意:临时添加的字段,首先需要考虑之前的数据有没有,可以设置个默认值。

word_number = models.IntegerField(default=0)

通过日志可以查看执行的SQL语句,在项目settings.py文件中添加如下代码:

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level': 'DEBUG',
},
}
}

注意事项:

  • 数据库表名appname_modelname,是根据模型中的元数据自动生成的,也可以复写为别的名称;
  • id字段是自动添加的;
  • 对于外键字段,Django会在字段名上添加"_id"来创建数据库中的列名;
  • Django会根据settings中指定的数据库类型来使用相应的SQL语句;
  • 定义好模型之后,需要告诉Django使用这些模型。通过修改settings文件中的INSTALLED_APPS设置,在其中添加models.py所在应用的名称;
  • 外键字段ForeignKey有一个null=True的设置(它允许外键接受空值NULL),可以赋给它空值None;

字段选项

每个字段(模型类的属性)有一些特有的参数,例如:CharField需要max_length参数来指定VARCHAR数据库字段的大小,还有一些适用于所有字段的通用参数。这些参数在官方文档中有详细的定义,这里我们只简单介绍些最常用的:

# 1.null
null是针对数据库而言,如果null=True,表示数据库的该字段可以为空(空值将会被存储为NULL)。Django用NULL来存储空值,默认为False。日期型、时间型和数字型字段不接受空字符串,所以设置IntegerField、DateTimeField型字段可以为空时,需要将blank、null均设为True;如果想设置BooleanField为空时,可以选用NullBooleanField型字段; # 2.blank
blank是针对表单的,如果blank=True,表示表单填写时该字段可以不填,比如:admin界面下增加model一条记录时。直观的看到就是该字段不是粗体(blank=True,字段可以为空;blank=False,字段是必填;默认字段必填); # 3.default
字段的默认值,可以是一个值或者可调用对象。如果是可调用对象,每当有新对象被创建时它都会被调用; # 4.primary_key
如果为True,那么这个字段就是模型的主键。如果没有指定任何一个字段的primary_key=True,Django就会自动添加一个IntegerField字段作为主键,所以除非我们想要覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True; # 5.unique
如果字段的该值设置为True,这个数据字段的值在整张表中必须是唯一的; # 6.choices
由二元元组组成的一个可迭代对象(列表或元组),用来给字段提供选择项。如果设置了choices,默认的表单将是一个选择而不是标准的文本框,而且这个选择框的选项就是choices中的选项,这是关于choices列表的例子: YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或ModelChoiceField中用作显示的内容。在一个给定的model类的实例中,想得到某个choices字段的显示值,就调用get_FOO_display方法(这里的FOO就是choices字段的名称)。例如: from django.db import models class Person(models.Model):
SHIRT_SIZES = (
("S", "Small"),
("M", "Middle"),
("L", "Large"),
)
name = models.CharField(max_length=32)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES) person = Person(name="eric", shirt_seze="L")
person.save()
print(person.shirt_size)
print(persion.get_shirt_size_display())

一旦创建好数据模型之后,Django会自动生成一套数据库抽象的API,我们可以执行关于表记录的增删改查操作。

六、添加表记录

多对一添加纪录:

# 方式一:如果是这样直接指定publish_id字段去添加值,前提是主表里面必须有数据
# 主表:没有被关联的(因为book表是要依赖于publish这个表的),也就是publish表;
# 子表:关联的表,也就是book表;
models.Book.objects.create(title="围城", publish_date="2000-3-17", price="78.6", publish_id=1) # 方式二:推荐使用该方式
publish_book = models.Publish.object.filter(name="人民出版社")[0]
print(publish_book)
models.Book.objects.create(title="红楼梦", publish_date="1976-5-8", price="90", publish=publish_book) # 方式三:save
pub_book = models.Publish.objects.get(name="人民出版社") # 只有一个的时候用get查询,获取得到的就是一个对象
book = models.Book(title="落花生", publish_date="1980-9-12", price="42", publish=pub_book)
book.save()

多对多添加纪录:

书和作者是多对多的关系:一本书可以由多个作者,一个作者可以著作多本书;

步骤:首先,找到书对象;其次,再找到需要的作者对象;最后,给书对象绑定作者对象(用add方法),也就是绑定多对多的关联关系;

# 方式一:
# 首先,创建一本书
publish = models.Publish.objects.filter("工业出版社").first()
book = models.Book.objects.create(title="璇玑", publish_date="2017-1-25", price="75.8", publish=publish)
# 其次,通过作者的名字Django默认找到id
elise = models.Author.objects.filter(name="elise")[0]
eric = models.Author.objects.filter(name="eric ")[0]
smith= models.Author.objects.filter(name="smith")[0]
# 最后,绑定多对多的关联关系
book.author_list.add(elise, eric, smith) # 方式二:查出所有的作者
pub_info = models.Publish.objects.filter(name"邮电出版社").first()
books = models.Book.objects.create(title="天之痕", publish_date="2016-7-6", price="80", publish=pub_info)
authors = models.Author.objects.all()
# 绑定多对多关联关系
books.author_list.add(*authors)

解除绑定(remove):将某个特定的对象从被关联对象集合中去除(books.authors.remove(*[]))

# 解除多对多的关联关系
books = models.Books.objects.filter(title="天之痕").last() # 找到书对象
authors = models.Author.objects.filter(id__lt=3) # 找到符合条件的作者对象
books.author_list.remove(*authors) # 因为解除的是多条,所以需要使用*

清楚绑定(clear):清空被关联对象集合

# 清空关系方法(clear)
books = models.Book.objects.filter(name="和平世界")
for book in books: # 把所有和平世界都清空
book.author_list.clear()

总结:

  • remove:先将要清除的数据筛选出来,然后解除关联关系;
  • clear:不用查询,直接就把数据清空;

七、基于对象的查询(相当于SQL语句的where子查询)

一对一查询记录:author和authordetail是一对一的关系

正向查询:按字段author

反向查询:按表名authordetail,因为是一对一的关系,就不用_set了;

# 正向查询:手机号为911的作者的姓名
detail = models.AuthorDetail.objects.filter(tel="911").first()
print(detail.author.name) # 反向查询:查询eric的手机号
eric = models.Author.objects.filter(name="eric").first()
print(eric.authordetail.tel)

一对多查询记录

正向查询:按字段publish

反向查询:按表名book_set

# 正向查询:查询天之痕这本书的出版社的地址
book = models.Book.object.filter(name="天之痕")[0] # 找对象
print("*"*8, book.publish) # 查询到的是关联出版社的对象
print(book.publish.addr) # 反向查询:查询人民出版社出版过的所有的书籍的价格和名字
publish = models.Publish.objects.filter(name="人民出版社")[0]
books = publish.book_set.all().values("price", "title")[0]
print(books, books["price"])
# 查询人民出版社出版过的所有书籍
pub_books = models.Publish.objects.get(name="人民出版社") # get查询到的是一个对象,不过get只能查看有一条记录的
book_list = pub_books.book_set.all() # 与人民出版社关联的所有数据对象集合
for book in book_list:
print(book.title)
# 这里使用for循环或values、values_list都是可以的

多对多查询记录

正向查询:按字段author_list

反向查询:按表名book_set

# 正向查询:查询追风筝的人这本书所有作者的姓名和年龄
book = models.Book.objects.filter(title="追风筝的人")[0]
print(book.author_list.all().values("name", "age")) # 这本书关联的所有作者对象的集合
# 反向查询:查询作者是鲁迅的这个人出版书的信息
luxun = models.Author.objects.filter(name="luxun")[0]
print("*"*10, luxun.book_set.all().first().title() # 与该作者关联的所有书对象的集合
return HttpResponse("ok")

我们可以通过在ForeignKey()和ManyToManyField的定义中设置related_name的值来复写FOO_set的名称。例如:如果Article model中做一下更改:publish = ForeginKey(Blog, related_name="book_list"),那么接下来就会如我们看到这般:

# 查询人民出版社出版过的所有书籍
publish = models.Publish.object.get(name="人民出版社")
books = publish.book_list.all() # 与人民出版社关联的所有书籍对象集合

八、基于双下划线的跨表查询

Django还提供了一种直观且搞笑的方式在查询中标识关联关系,它能自动确认SQL JOIN联系。要做跨关系查询,就使用两个下划线来链接模型间关联字段的名称,直到最终链接到你想要的模型为止(相当于sql语句用join连接的方式,可以在settings中设置,以查看sql语句)。

多对一查询

practice 1.查询人民出版社出版过的所有书籍的价格和名称:

# 方法1
result = models.Publish.objects.filter(name="人民出版社").values("book__price", "book_title")
print(result) # 方法2
tmp_res = models.Book.objects.filter(publish__name="人民出版社").values("price", "title")
print(tmp_res)

practice 2.查询Linux这本书的出版社地址:filter先过滤,values显示要求的字段

# 方法1
res_one = models.Book.objects.filter(title="Linux").values("publish__addr")
print(res_one)
# 方法2
res_two = models.Publish.objects.filter(book__title="Linux").values("addr")

多对多查询

practice 1.查询eric出过的所有书的名字:

# 方法1
res = models.Author.objects.filter(name="eric").values("book__title")
print(res)
# 方法2
result = models.Book.objects.filter(author_list__name="eric").values("title")
print(result)

practice 2.查询手机号以182开头的作者出版过的所有书籍的名称及出版社的名称

# 方法1
authors = models.AuthorDetail.objects.filter(tel_startwith="182").first()
print(authors.author.book_set.all().values("title", "publish__name")
# 方法2
result = models.Book.objects.filter(author_list__author_detail__tel__startwith="182").values("title", "publish__name")
print(result)

九、聚合查询与分组查询

聚合查询:aggregate(*args, **kwargs),只对一个组进行聚合

from django.db.models import Avg, Sum, Max, Min

# 查询所有图书的平均价格
print(models.Book.objects.all().aggregate(Avg("price")))

aggregate()是QuerySet的一个终止子句(也就是返回的不再是一个QuerySet集合),它返回的是一个字典,该字典的key是聚合值得标识符,value是计算出来的聚合值。key的名字是按照字段和聚合函数的名称自动生成的,如果想要为聚合值指定名称,可以向聚合子句提供它:

from django.db.models import Avg, Sum, Max, Min

# 查询所有图书的平均价格
print(models.Book.objects.all().aggregate(avgprice=Avg("price")))

分组查询:annotate(),为QuerySet中每个对象都生成一个独立的汇总值,是对分组完之后的结果进行聚合(返回QuerySet对象,可以继续使用filter、order_by等)

practice 1.统计每本书的作者数量

# 方法1
authors = models.Book.objects.all().annotate(authorNum=Count("author_list__name)).values("authorNum")
print(authors)
# 方法2
books = models.Book.objects.all().annotate(authorNum=Count("author_list__name")
for book in books:
print(book.title, book.authorNum)

practice 2.统计每个出版社最便宜的书籍

# 方法1
print(models.Book.objects.values("publish__name").annotate(cheaper=Min("price"))) # values内的字段即group by的字段,也就是分组条件
# 方法2
print(models.Publish.objects.all().annotate(cheaper=Min("book__price")).values("name", "cheaper"))
# 方法3
publish_list = models.Publish.objects.annotate(cheaper=Min("book__price"))
for publish in publish_list:
print(publish.name, publish.cheaper)

practice 3.统计每本以py开头书籍的作者数量:

print(models.Book.objects.filter(title__startwith="py").annotate(authNum=Count("author_list__name")).values("ahtuNum")

practice 4.统计不止一个作者的书籍:

print(models.Book.objects.annotate(num_auths=Count("author_list__name")).filter(num_auths__gt=1).values("title", "num_auths"))

practice 5.根据每本书籍数量的多少对查询结果进行排序:

print(models.Book.objects.all().annotate(auth_nums=Count("author_list__name")).order_by("auth_nums"))

practice 6.查询各个作者出的书籍的总价格:

# 方法1
print(models.Author.objects.all().annotate(price=Sum("book__price")).values("name", "price")
# 方法2
print(models.Book.objects.values("author_list__name").annotate(price=Sum("price")).values("author_list__name", "price"))

十、F查询和Q查询

F查询:在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较,如果我们要对两个字段的值作比较,那么该怎么做呢?

Django提供F()来做这样的比较,F()的实例可以在查询中引用字段,来比较同一个模型实例中两个不同字段的值:

practice 1.查看评论数大于阅读数的书籍:

from django import F, Q
print(models.Book.objects.filter(comments__gt=F("readers"))

practice 2.操作修改,将id大于1的所有书籍价格上涨10元:

print(models.Book.objects.filter(nid__gt=1).update(price=F("price")+10)

practice 3.Django支持F()对象之间及F()对象和常数之间的加减乘除和取模的操作:

# 查询评论数大于收藏数2倍的书籍
print(models.Book.objects.filter(comments__gt=F("collections")*2))

Q查询:Q查询可以组合使用"&","|"操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象,Q对象可以用"~"操作符放在前面标识否定,也可允许肯定与否定形式的组合。Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面

# 因为获取的结果是QuerySet,所以使用下标的方式获取结果
print(models.Book.objects.filter(Q(id=3))[0])
# 查询id=3或者标题是"Go"的书籍
print(models.Book.objects.filter(Q(id=3)|Q(title="Go"))[0])
# 查询价格大于等于70并且标题是"J"开头的书籍
print(models.Book.objects.filter(Q(price__gte=70)&Q(title__startwith="J")))
# 查询标题是"J"开头并且id不是3的书籍
print(models.Book.objects.filter(Q(title__startwith="J")&~Q(id=3)))
# Q对象可以与关键字参数查阅一起使用,必须把普通关键字查询放到Q对象查询的后面
print(models.Book.objects.filter(Q(price=70)|Q(title="python"), publish_date="2018-05-22"))
from django.db.models import Q

con = Q()
q1 = Q()
q1.connector = "AND"
q1.children.append(("email", "123@163.com"))
q1.children.append(("password", "abc123")) q2 = Q()
q2.connector = "AND"
q2.children.append(("username", "abc"))
q2.children.append(("password", "xyz123")) con.add(q1, "OR")
con.add(q2, "OR") # 查询email=123@163.com和password=abc123 或者 username=abc和password=xyz123的用户信息
obj = models.UserInfo.objects.filter(con).first()

上面的例子是一个典型的复杂查询,通过将Q对象实例化,然后增加各个条件之间的关系,而且这种写法用在我们不知道用户到底会传入多少个参数的时候很方便。