Django ORM 跨表查询与数据增删改全指南
本文详解 Django 模型中一对一、一对多及多对多关系的建立与查询方法。涵盖基于对象和双下划线的跨表查询技巧,深入解析 aggregate 聚合查询与 annotate 分组统计。同时介绍 F 查询与 Q 查询的高级用法,帮助开发者高效处理复杂数据库操作。
创建数据表模型
定义书籍模型:书籍包含书名和出版日期字段。由于一本书可能由多位作者共同撰写,而一位作者也可能创作多本书籍,因此作者与书籍之间构成多对多(many-to-many)关联关系。
每本书仅由单一出版商负责出版,因此出版商与书籍之间构成一对多(one-to-many)关联关系。
建立一对一关系:使用 OneToOne("关联表名") 定义。
建立一对多关系:使用 ForeignKey("关联表名") 定义。
建立多对多关系:使用 ManyToMany("关联表名") 定义,Django 会自动创建中间表以维护该关系。
class Book(models.Model):
nid = models.AutoField(primary_key=True) # 自增id(可以不写,默认会有自增id)
title = models.CharField(max_length=32)
publishDdata = models.DateField() # 出版日期
price = models.DecimalField(max_digits=5, decimal_places=2) # 一共5位,保留两位小数
#一个出版社有多本书,关联字段要写在多的一方
# 不用命名为publish_id,因为django为我们自动就加上了_id
publish = models.ForeignKey("Publish") #foreignkey(表名)建立的一对多关系
# publish是实例对象关联的出版社对象
authorlist = 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 AuthorDeital(models.Model):
tel = models.IntegerField()
addr = models.CharField(max_length=32)
author = models.OneToOneField("Author") #建立的一对一的关系
注意:若为已有模型临时添加字段,需评估现有数据情况并设置默认值,以避免数据插入错误。
wordNum = models.IntegerField(default=0)
可通过配置 logging 模块查看 Django 生成的 SQL 语句,便于调试。
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
注意事项:
1、表名默认由应用名和模型名小写组合生成(如 myapp_modelName),也可通过元数据自定义表名。
2、id 字段由 Django 自动添加为主键。
3、对于外键字段,Django 在数据库列名中自动追加 "_id" 后缀。
4、示例中的 CREATE TABLE SQL 使用 PostgreSQL 语法,Django 会根据 settings 配置的数据库类型自动生成对应方言的 SQL。
5、定义好模型后,需在项目的 settings 文件中修改 INSTALLED_APPS,添加包含 models.py 的应用名称,以启用该模型。
6、外键字段 ForeignKey 可设置 null=True,允许该字段接受空值 NULL,此时可赋值为 None。
字段选项配置
每个字段类型拥有特定参数,例如 CharField 需指定 max_length 以定义 VARCHAR 长度。此外还有适用于所有字段的通用参数,文档中有详细定义,此处仅介绍常用项:
1)null
若设为 True,Django 在数据库中存储空值时使用 NULL。默认值为 False。
(1)blank
若设为 True,表单验证允许该字段为空。默认值为 False。 需注意,null 仅涉及数据库层面,而 blank 涉及数据验证层面。 若字段 blank=True,表单允许留空;若 blank=False,则该字段为必填项。
(2)default
字段的默认值,可为具体值或可调用对象。若为可调用对象,每次创建新实例时都会执行该调用。
(3)primary_key
若设为 True,该字段即为主键。若未指定任何字段的 primary_key=True,Django 会自动添加一个 IntegerField 作为主键。除非需覆盖默认主键行为,否则无需手动设置。
(4)unique
若设为 True,该字段在整张表中的值必须唯一。
(5)choices 由二元组构成的可迭代对象(如列表或元组),用于提供字段选项。若设置 choices, 默认表单将渲染为选择框而非文本框,选项内容取自 choices。
choices 列表示例:
YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
)
每个元组的第一个元素为存入数据库的值,第二个元素为管理界面或 ModelChoiceField 中显示的文本。 在模型实例中获取 choices 字段的显示值,需调用 get_FOO_display 方法(FOO 为字段名)。例如:
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large
建立数据模型后,Django 自动生成数据库抽象 API,支持对表记录进行增删改查操作。
添加记录操作
一对多关系添加记录:
# 一对多的添加 # 方式一:如果是这样直接指定publish_id字段去添加值,前提是你的主表里面必须有数据 # 主表:没有被关联的(因为book表是要依赖于publish这个表的)也就是publish表 # 子表:关联的表 models.Book.objects.create(title="追风筝的人",publishDdata="2015-5-8",price="111",publish_id=1) # 方式二:推荐 pub_obj = models.Publish.objects.filter(name="人民出版社")[0] print(pub_obj) models.Book.objects.create(title = "简爱",publishDdata="2000-6-6",price="222",publish=pub_obj) # 方式三:save pubObj= models.Publish.objects.get(name="人民出版社") #只有一个的时候用get,拿到的直接就是一个对象 bookObj = models.Book(title = "真正的勇士",publishDdata="2015-9-9",price="50",publish=pubObj) bookObj.save()
多对多关系添加记录:
书与作者为多对多关系:一本书可有多位作者,一位作者可出版多本书。
操作步骤:首先获取书对象。
其次获取目标作者对象。
将作者对象绑定至书对象(使用 add 方法),即建立多对多关联。
# 多对多的添加的两种方式 # 方式一: # 先创建一本书: pub_obj=models.Publish.objects.filter(name="万能出版社").first() book_obj = models.Book.objects.create(title="醉玲珑",publishDdata="2015-4-10",price="222",publish=pub_obj) # #通过作者的名字django默认找到id haiyan_obj = models.Author.objects.filter(name="haiyan")[0] egon_obj = models.Author.objects.filter(name="egon")[0] xiaoxiao_obj = models.Author.objects.filter(name="xiaoxiao")[0] # 绑定多对多的关系、 book_obj.authorlist.add(haiyan_obj, egon_obj, xiaoxiao_obj) # 方式二=========,查出所有的作者 pub_obj = models.Publish.objects.filter(name="万能出版社").first() book_obj = models.Book.objects.create(title="醉玲珑", publishDdata="2015-4-10", price="222", publish=pub_obj) authers = models.Author.objects.all() # #绑定多对多关系 book_obj.authorlist.add(*authers)
解除绑定:使用 remove。
# 从关联对象集合中移除特定对象。 ====== book_obj.authors.remove(*[])
# 解除多对多的关系(remove) book_obj=models.Book.objects.filter(title="醉玲珑").last() #找到书对象 authers=models.Author.objects.filter(id__lt=3) #找到符合条件的作者对象 book_obj.authorlist.remove(*authers) #因为清除的是多条,得加个*
清除绑定:使用 clear # 清空所有关联对象集合。
# 清除关系方法(clear)
book_obj= models.Book.objects.filter(title="红楼梦")
for book_obj_item in book_obj:#把所有红楼梦的都给清空了
book_obj_item.authorlist.clear()
总结:remove 与 clear 的区别 remove:需筛选出待移除的数据,然后执行移除操作。 clear:无需筛选,直接清空所有关联数据。 两者适用于不同场景。
三、基于对象的查询(相当于 SQL 中的子查询)
一对一关系查询:author 与 authordetile 为一对一关系。
正向查询(按字段 author):
反向查询(按表名 authordeital):因为一对一关系,无需使用 _set 后缀。
# 一对一的查询 # 正向查询:手机号为13245的作者的姓名 deital_obj = models.AuthorDeital.objects.filter(tel="13245").first() print(deital_obj.author.name) # 反向查询:查询egon的手机号 egon_obj = models.Author.objects.filter(name="egon").first() print(egon_obj.authordeital.tel)
一对多关系查询
正向查询(按字段:publish):
反向查询(按表名:book_set):
# 正向查询:查询真正的勇士这本书的出版社的地址
book_obj = models.Book.objects.filter(title="真正的勇士")[0] # 找对象
print("======", book_obj.publish) # 拿到的是关联出版社的对象
print(book_obj.publish.addr)
# 反向查询:查询人民出版社出版过的所有的书的价格和名字
pub_obj = models.Publish.objects.filter(name="人民出版社")[0]
book_dic = pub_obj.book_set.all().values("price", "title")[0]
print(book_dic)
print(book_dic["price"])
# 查询 人民出版社出版过的所有书籍
publish=models.Publish.objects.get(name="人民出版社") #get得到的直接是一个对象,不过get只能查看有一条记录的
book_list=publish.book_set.all() # 与人民出版社关联的所有书籍对象集合
for book_obj in book_list:
print(book_obj.title)
注意,此处使用 for 循环或 values、valueslist 均可实现。
多对多关系查询
正向查询(按字段 authorlist)
反向查询(按表名 book_set)
# 多对多的查询
# 正向查询:查询追风筝的人的这本书的所有的作者的姓名和年龄
book_obj = models.Book.objects.filter(title="红楼梦")[0]
print(book_obj.authorlist.all().values("name", "age")) # 这本书关联的所有作者对象的集合
# 反向查询:查询作者是haiyan的这个人出了哪几本书的信息
haiyan_obj = models.Author.objects.filter(name="haiyan")[0]
print("bookinfo====", haiyan_obj.book_set.all().first().title) # 与该作者关联的所有书对象的集合
return HttpResponse("ok")
可通过在 ForeignKey() 或 ManyToManyField 中设置 related_name 来覆盖默认的 FOO_set 名称。例如,若 Article 模型中修改为: publish = ForeignKey(Blog, related_name='bookList'),则查询时将显示为:
# 查询 人民出版社出版过的所有书籍 publish=Publish.objects.get(name="人民出版社") book_list=publish.bookList.all() # 与人民出版社关联的所有书籍对象集合
基于双下划线的跨表查询
Django 提供直观高效的关联查询方式,自动确认 SQL JOIN 连接。跨关系查询使用两个下划线链接模型间关联字段名,直至目标 model。(相当于 SQL 的 join 连接,可在 settings 中配置查看 SQL 语句)。
一对多查询示例:
练习1:查询人民出版社出版的所有书的价格和名字。
# 基于双下划线的方式查询1================一对多
# 第一种查法
ret = models.Publish.objects.filter(name="人民出版社").values("book__price","book__title")
print(ret)
# 第二种查法
ret2 = models.Book.objects.filter(publish__name="人民出版社").values("price","title")
print(ret2)
练习2:查询《linux》这本书的出版社地址:先使用 filter 过滤,再使用 values 显示指定字段。
第一种查法
ret = models.Book.objects.filter(title="linux").values("publish__addr")
print(ret)
第二种查法
ret2 = models.Publish.objects.filter(book__title="linux").values("addr")
print(ret2)
多对多查询示例
练习1:查询作者 egon 出版的所有书的名字。
#方式一
ret = models.Author.objects.filter(name="egon").values("book__title")
print(ret)
#方式二:两种方式也就是逻辑不一样
ret2 = models.Book.objects.filter(authorlist__name="egon").values("title")
print(ret2)
练习2:查询手机号以 151 开头的作者出版的所有书的名称及出版社名称。
# 方式一:
author_obj = models.AuthorDeital.objects.filter(tel__startswith="151").first()
print(author_obj.author.book_set.all().values("title","publish__name"))
# 方式二:
ret = models.Book.objects.filter(authorlist__author_deital__tel__startswith="151").values("title","publish__name")
print(ret)
聚合查询与分组查询(重要)
聚合查询:aggregate(*args, **kwargs),仅对单一组进行聚合计算。
from django.db.models import Avg,Sum,Count,Max,Min
# 1、查询所有图书的平均价格 print(models.Book.objects.all().aggregate(Avg("price")))
aggregate() 是 QuerySet 的终止子句(返回不再为 QuerySet),结果为一个包含键值对的字典。键名为聚合标识符,值为计算结果。键名由字段名和聚合函数名自动生成。若需指定名称,可向聚合子句提供参数。
from django.db.models import Avg,Sum,Count,Max,Min
# 1、查询所有图书的平均价格
print(models.Book.objects.all().aggregate(avgprice = Avg("price")))
若需生成多个聚合结果,可向 aggregate() 添加多个参数。例如,若需同时获取所有图书价格的最大值和最小值,查询方式如下:
print(models.Book.objects.all().aggregate(Avg("price"),Max("price"),Min("price")))
#打印的结果是: {'price__avg': 174.33333333333334, 'price__max': Decimal('366.00'), 'price__min': Decimal('12.00')}
分组查询 :annotate():为 QuerySet 中每个对象生成独立的汇总值,是对分组后结果的聚合操作。
1、统计每本书的作者数量。
# 方式一:
print(models.Book.objects.all().annotate(authorNum = Count("authorlist__name")).values("authorNum"))
# 方式二:
booklist =models.Book.objects.all().annotate(authorNum=Count("authorlist__name"))
for book_obj in booklist:
print(book_obj.title,book_obj.authorNum)
2、统计每个出版社最便宜的书。
# 2、统计每一个出版社的最便宜的书
# 方式一:
print(models.Book.objects.values("publish__name").annotate(nMinPrice=Min('price'))) 注意:values内的字段即group by的字段,,也就是分组条件
# 方式二:
print(models.Publish.objects.all().annotate(minprice=Min("book__price")).values("name","minprice"))
# 方式三
publishlist = models.Publish.objects.annotate(minprice = Min("book__price"))
for publish_obj in publishlist:
print(publish_obj.name,publish_obj.minprice)
3、统计每本以 py 开头的书籍的作者数量:
print(models.Book.objects.filter(title__startswith="py").annotate(authNum = Count("authorlist__name")).values("authNum"))
4、统计作者数量超过一本的图书:
print(models.Book.objects.annotate(num_authors=Count('authorlist__name')).filter(num_authors__gt=1).values("title","num_authors"))
5、根据图书作者数量对查询集 QuerySet 进行排序:
print(models.Book.objects.all().annotate(authorsNum=Count("authorlist__name")).order_by("authorsNum"))
6、查询各作者出版书籍的总价格:
# 方式一
print(models.Author.objects.all().annotate(priceSum = Sum("book__price")).values("name","priceSum"))
# 方式二
print(models.Book.objects.values("authorlist__name").annotate(priceSum=Sum("price")).values("authorlist__name","priceSum"))
F 查询和 Q 查询
F 查询:
前述过滤器仅将字段值与常量比较。若需比较两个字段的值,应如何处理?
Django 提供 F() 进行此类比较。F() 实例可在查询中引用字段,用于比较同一 model 实例中两个不同字段的值。
1、查询评论数大于阅读数的书。
from django.db.models import F,Q
print(models.Book.objects.filter(commentNum__gt=F("readNum")))
2、修改操作也可使用 F 函数,例如将 id 大于 1 的所有书价格上调 100 元。
print(models.Book.objects.filter(nid__gt=1).update(price=F("price")+100))
3、Django 支持 F() 对象之间以及 F() 对象与常数之间的加减乘除和取模运算。
# 查询评论数大于收藏数2倍的书籍
models.Book.objects.filter(commnetNum__lt=F('keepNum')*2)
Q 查询:
filter() 等方法中的关键字参数查询默认进行“AND”操作。若需执行更复杂查询(如 OR 语句),可使用 Q 对象。
1、查询 id 大于 1 且评论数大于 100 的书。
print(models.Book.objects.filter(nid__gt=1,commentNum__gt=100)) print(models.Book.objects.filter(nid__gt=1).filter(commentNum__gt=100)) print(models.Book.objects.filter(Q(nid__gt=1)&Q(commentNum__gt=100)))
2、查询评论数大于 100 或阅读数小于 200 的书。
print(models.Book.objects.filter(Q(commentNum__gt=100)|Q(readNum__lt=200)))
Q 对象可使用 & 和 | 操作符组合。当操作符作用于两个 Q 对象时,生成新的 Q 对象。
3、查询年份等于 2017 年或价格大于 200 的书。
print(models.Book.objects.filter(Q(publishDdata__year=2017)|Q(price__gt=200)))
4、查询年份不是 2017 年或价格大于 200 的书。
print(models.Book.objects.filter(~Q(publishDdata__year=2017)&Q(price__gt=200)))
注意:
查询函数可混合使用 Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或 Q 对象)均会被“AND”连接。但若出现 Q 对象,其必须位于所有关键字参数之前。例如:
bookList=models.Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),
title__icontains="python"
)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













