【django基础之ORM,增删改查】
一、定义
1.什么是ORM?
ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
2.ORM的优缺点
优点:摆脱复杂的SQL操作,适应快速开发;让数据结构变得简洁;数据库迁移成本更低(如从mysql->oracle)
缺点:性能较差、不适用于大型应用;复杂的SQL操作还需通过SQL语句实现
映射关系:
表名 <-------> 类名 字段 <-------> 属性 表记录 <------->类实例对象
二、创建表(建立模型)
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
models.py
from django.db import models # Create your models here. class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) publishDate = models.DateField() price = models.DecimalField(max_digits=5, decimal_places=2) read_num=models.IntegerField(default=0) commnet_num=models.IntegerField(default=0) poll_num=models.IntegerField(default=0) # publish:与当前书籍对象关联的的出版社对象,与Publish建立一对多的关系,外键字段建立在多的一方 publish=models.ForeignKey(to="Publish",to_field="id") # authors: 与当前书籍关联的所有作者的集合 # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors=models.ManyToManyField(to="Author") def __str__(self): return self.title class Author(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() class AuthorDetail(models.Model): tel=models.CharField(max_length=32) email=models.CharField(max_length=32) #与Author建立一对一的关系 author=models.OneToOneField("Author") class Publish(models.Model): name=models.CharField(max_length=32) email=models.CharField(max_length=32) def __str__(self): return self.name # #收到创建第三张表 # class Author2Book(models.Model): # author=models.ForeignKey(to="Author") # book=models.ForeignKey(to="Book")
语法
#与Author建立一对一的关系 author=models.OneToOneField("Author")
# 与Publish建立一对多的关系,
#publish:与当前书籍对象关联的的出版社对象,外键字段建立在多的一方
publish=models.ForeignKey(to="Publish",to_field="id")
# authors: 与当前书籍关联的所有作者的集合
# 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
authors=models.ManyToManyField(to="Author")
通过logging可以查看翻译成的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', }, } }
注意事项:
1、 表的名称myapp_modelName
,是根据 模型中的元数据自动生成的,也可以覆写为别的名称
2、id
字段是自动添加的
3、对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
4、这个例子中的CREATE TABLE
SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。
5、定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py
所在应用的名称。
6、外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
字段选项
每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR
数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的:
(1)null 如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False. (1)blank 如果为True,该字段允许不填。默认为False。 要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。 (2)default 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。 (3)primary_key 如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True, Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为, 否则没必要设置任何一个字段的primary_key=True。 (4)unique 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的 (5)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', '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'
二、添加表记录
1.普通字段(单表操作)
方式
1
publish_obj=Publish(name="人民出版社",city="北京",email="renMin@163.com") publish_obj.save() # 将数据保存到数据库
方式2
返回值publish_obj是添加的记录对象 publish_obj=Publish.objects.create(name="人民出版社",city="北京",email="renMin@163.com")<br><br>方式3<br>表.objects.create(**request.POST.dict())
2.外键字段
表关系为一对一:在任何一张表中加入一个关联字段,但关联字段必须唯一约束
表关系为一对多:在多的一张表中加关联字段
方式1: publish_obj=Publish.objects.get(nid=1) Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish=publish_obj) 方式2: Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish_id=1)
3.多对多字段
创建第三张表,主键,两个关联字段。
book_obj=Book.objects.create(title="追风筝的人",publishDate="2012-11-12",price=69,pageNum=314,publish_id=1)
author_yuan=Author.objects.create(name="yuan",age=23,authorDetail_id=1) author_egon=Author.objects.create(name="egon",age=32,authorDetail_id=2)
book_obj.authors.add(author_egon,author_yuan) # 将某个特定的 model 对象添加到被关联对象集合中。 ======= book_obj.authors.add(*[author_egon,author_yuan])
#添加筛选条件的关联,与年龄大于20的关联
author_list=Author.objects.filter(age_gt=20)
bool_obj.authors.add(*author_list)
book_obj.authors.create() #创建并保存一个新对象,然后将这个对象加被关联对象的集合中,然后返回这个新对象。
解除关系:
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 ====== book_obj.authors.remove(*[]) book_obj.authors.clear() #清空被关联对象集合。
例如
def add_book(request): if request.method=="POST": title=request.POST.get("title") price=request.POST.get("price") pub_date=request.POST.get("pub_date") publish_id=request.POST.get("publish_id") # 一对多的添加方式 # 方式1: #book_obj=Book.objects.create(title=title,price=price,publishDate=pub_date,publish_id=publish_id) # 方式2: # publish_obj=Publish.objects.filter(id=2).first() # book_obj=Book.objects.create(title=title, price=price, publishDate=pub_date,publish=publish_obj) # 多对多关系的创建 book_obj = Book.objects.create(title=title, price=price, publishDate=pub_date, publish_id=publish_id) print("==",book_obj.authors.all()) # <QuerySet []> # add 方法 绑定多对多的关系 #jing = Author.objects.filter(name="景丽洋").first() #alex = Author.objects.filter(name="alex").first() #book_obj.authors.add(jing,alex) #book_obj.authors.add(*[jing,alex]) #author_list=Author.objects.filter(age__gt=20) #book_obj.authors.add(*author_list) # remove 方法 绑定多对多的关系 book_obj=Book.objects.get(nid=9) #alex = Author.objects.filter(name="alex").first() #book_obj.authors.remove(alex) book_obj.authors.clear() #print("==", book_obj.authors.all()) # 失败的原因:找不到第三张关系表的名字 # book_authors.objects.create(book_id=book_obj.id,author_id=jing.id) # book_authors.objects.create(book_id=book_obj.id,author_id=alex.id) return redirect("/index/") publish_list=Publish.objects.all() return render(request,"add_book.html",locals())
三、修改表记录
Book.objects.filter().update(price=50,.....)
四、删除表记录
删除方法就是 delete()。它运行时立即删除对象而不返回任何值。例如:
Book.objects.filter().delete() # 默认级联删除
你也可以一次性删除多个对象。每个 QuerySet 都有一个 delete() 方法,它一次性删除 QuerySet 中所有的对象。
例如,下面的代码将删除 pub_date 是2005年的 Entry 对象:
Entry.objects.filter(pub_date__year=2005).delete()
五、查询表记录
1.查询相关API
<1> all(): 查询所有结果,返回的是queryset,集合 <2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象,返回的是queryset,集合 <3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个, 如果符合筛选条件的对象超过一个或者没有都会抛出错误。返回的是对象 <5> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 <4> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 <9> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 <6> order_by(*field): 对查询结果排序 <7> reverse(): 对查询结果反向排序 <8> distinct(): 从返回结果中剔除重复纪录 <10> count(): 返回数据库中匹配查询(QuerySet)的对象数量。 <11> first(): 返回第一条记录 <12> last(): 返回最后一条记录 <13> exists(): 如果QuerySet包含数据,就返回True,否则返回False
2.双下划线之单表查询
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]) # not in models.Tb1.objects.filter(name__contains="ven") models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and startswith,istartswith, endswith, iendswith
3.基于对象的跨表操作(子查询)
正向查询:通过关联字段所在的表查询另外一张关联表
反向查询:通过一张关联表查询关联字段所在的表
-
一对多
正向查询,按字段:publish
反向查询,按表名小写_set
#查询java的出版社的邮箱(正向查询:按字段book_obj.publish) book_obj=Book.objects.filter(title="java").first() print(book_obj.publish.email)
#查询人民出版社出版过的所有的书籍名称(反向查询:按表名小写_set publish_obj.book_set) publish_obj=Publish.objects.filter(name="人民出版社").first() print("---",publish_obj.book_set.all())
- 多对多
正向查询,按字段:authors
反向查询,按表名小写_set
#查询php这本书籍的所有作者的名字以及年龄(正向查询:按字段book_obj.authors) book_obj=Book.objects.filter(title="php").first() print(book_obj.authors.all()) for obj in book_obj.authors.all(): print(obj.name,obj.age) #查询alex出版社过的所有书籍的名称和价格(反向查询,按表名小写_set) alex=Author.objects.filter(name="alex").first() print(alex.book_set.all()) for obj in alex.book_set.all(): print(obj.title,obj.price)
-
一对一
正向查询,按字段:author
反向查询,按表名小写
# 一对一查询 查询tel=789的作者的名字 正向查询按字段 ad=AuthorDetail.objects.filter(tel="789").first() print(ad.author.name) # 查询alex的手机号是多少 alex=Author.objects.filter(name='alex').first() print(alex.authordetail.tel) # 789
4.基于QuerySet的跨表查询(join查询)
正向查询,按字段:publish
反向查询,按表名小写
-
一对多
例1: 查询java这本书的出版社的邮箱(正向查询) ret=Book.objects.filter(nid__gt=6).values("publish__name") sql: SELECT "app01_publish"."name" FROM "app01_book" INNER JOIN "app01_publish" ON ("app01_book"."publish_id" = "app01_publish"."id") WHERE "app01_book"."nid" > 6 LIMIT 21; values实现机制: 对调用的QuerySet集合对象里面的每一个对象循环遍历,取出每一个对象的显示字段的值,组成新的字典,放在一个新的QuerySet中,返回 例2: # 查询人民出版社出版过的所有的书籍名称(反向查询) ret=Publish.objects.filter(name="人民出版社").values("book__title") print(ret) #<QuerySet [{'book__title': 'golang'}, {'book__title': 'linux2'}, {'book__title': 'qq'}, {'book__title': 'www'}]>
-
多对多
#查询php这本书籍的所有作者的名字以及年龄 ret=Book.objects.filter(title="php").values("authors__name","authors__age") print(ret) #查询alex出版社过的所有书籍的名称和价格 ret=Author.objects.filter(name="alex").values("book__title","book__price") print(ret)
-
一对一
#查询tel=789的作者的名字 ret=AuthorDetail.objects.filter(tel="789").values("author__name") # 查询alex的手机号是多少 ret=Author.objects.filter(name="alex").values("authordetail__tel")
扩展
扩展: #查询人民出版社出版过的所有的书籍名称(两种查询思路,基表不同) ret=Publish.objects.filter(name="人民出版社").values("book__title") ret=Book.objects.filter(publish__name="人民出版社").values("title") # 手机号以151开头的作者出版过的所有书籍名称以及出版社名称 ret=Book.objects.filter(authors__authordetail__tel__startswith="7").values("title","publish__name") print(ret) SELECT "app01_book"."title", "app01_publish"."name" FROM "app01_book" INNER JOIN "app01_book_authors" ON ("app01_book"."nid" = "app01_book_authors"."book_id") INNER JOIN "app01_author" ON ("app01_book_authors"."author_id" = "app01_author"."id") INNER JOIN "app01_authordetail" ON ("app01_author"."id" = "app01_authordetail"."author_id") INNER JOIN "app01_publish" ON ("app01_book"."publish_id" = "app01_publish"."id") WHERE "app01_authordetail"."tel" LIKE '7%' ESCAPE '\' LIMIT 21; args=('7%',)
5.聚合查询与分组查询
聚合函数:Sum Count Avg Min Max 单表: emp: id name salary dep 1 张三 2000 销售部 2 李四 5000 IT部 3 王五 6000 销售部 4 赵六 5000 人事部 5 主七 2000 人事部 select Count(*),dep from emp group by dep select * from emp group by id,name,salary,dep 跨表: emp: dep: id name salary dep_id id name 1 张三 2000 1 1 销售部 2 李四 5000 2 2 IT部 3 王五 6000 1 3 人事部 4 赵六 5000 3 5 主七 2000 2 dep---emp id dep.id dep.name emp.id emp.name emp.salary emp.dep_id 1 1 销售部 1 张三 2000 1 2 1 销售部 3 王五 6000 1 3 2 IT部 2 李四 5000 2 4 2 IT部 5 主七 2000 2 5 3 人事部 4 赵六 5000 3 select Count(*) from emp select * from dep group by id SELECT "app01_publish"."name", COUNT("app01_book"."title") AS "c" FROM "app01_publish" LEFT OUTER JOIN "app01_book" ON ("app01_publish"."id" = "app01_book"."publish_id") GROUP BY "app01_publish"."id", "app01_publish"."name", "app01_publish"."email" LIMIT 21; args=()
-
聚合:aggregate(*args, **kwargs)
# 计算所有图书的平均价格 >>> from django.db.models import Avg >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35}
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
>>> from django.db.models import Avg, Max, Min >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
例
# 聚合函数 aggregate from django.db.models import Sum,Count,Max,Min,Avg # ret=Book.objects.all().aggregate(Sum("price")) # print(ret)
-
分组:annotate()
为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
统计每一本书的作者个数
bookList=Book.objects.annotate(authorsNum=Count('authors')) for book_obj in bookList: print(book_obj.title,book_obj.authorsNum)
SELECT "app01_book"."nid", "app01_book"."title", "app01_book"."publishDate", "app01_book"."price", "app01_book"."pageNum", "app01_book"."publish_id", COUNT("app01_book_authors"."author_id") AS "authorsNum" FROM "app01_book" LEFT OUTER JOIN "app01_book_authors" ON ("app01_book"."nid" = "app01_book_authors"."book_id") GROUP BY "app01_book"."nid", "app01_book"."title", "app01_book"."publishDate", "app01_book"."price", "app01_book"."pageNum", "app01_book"."publish_id" sql
Book.objects.annotate(authorsNum=Count('authors')) 拆分解析: Book.objects等同于Book.objects.all(),翻译成的sql类似于: select id,name,.. from Book 这样得到的对象一定是每一本书对象,有n本书籍记录,就分n个组,不会有重复对象,每一组再由annotate分组统计。
(2) 如果想对所查询对象的关联对象进行聚合:
统计每一个出版社的最便宜的书
publishList=Publish.objects.annotate(MinPrice=Min("book__price")) for publish_obj in publishList: print(publish_obj.name,publish_obj.MinPrice)
annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
queryResult= Publish.objects .annotate(MinPrice=Min("book__price")) .values_list("name","MinPrice") print(queryResult)
统计每一本以py开头的书籍的作者个数:
queryResult=Book.objects .filter(title__startswith="Py") .annotate(num_authors=Count('authors'))
统计不止一个作者的图书
queryResult=Book.objects .annotate(num_authors=Count('authors')) .filter(num_authors__gt=1)
例
# 查询每一个出版社出版社出版的书籍个数 # ret=Publish.objects.all().annotate(c=Count("book__title")).values("name","c") # print(ret) # <QuerySet [<Publish: 人民出版社>, <Publish: 机械出版社>, <Publish: 北京出版社>]> # 查询每一本书的作者个数 #ret=Book.objects.all().annotate(author_num=Count("authors")).values("author_num","title") # ret=Book.objects.all().annotate(author_num=Count("authors")).filter(author_num__gt=0) # print(ret)
6.F查询和Q查询
- F查询
F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
# 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum'))
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
# 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commnetNum__lt=F('keepNum')*2)
修改操作也可以使用F函数,比如将每一本书的价格提高30元:
Book.objects.all().update(price=F("price")+30)
-
Q查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
from django.db.models import Q Q(title__startswith='Py')
Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))
等同于下面的SQL WHERE 子句:WHERE name
=
"yuan"
OR name
=
"egon"
你可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(publishDate__year=2017)).values_list("title")
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:
bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017), title__icontains="python" )
# 评论数大于点赞数的书籍 from django.db.models import F ,Q # ret=Book.objects.filter(poll_num__gt=F("commnet_num")*2) # print(ret) #Book.objects.all().update(price=F("price")+100) # 查询 2018-01-09出版的或者价格大于150的书籍 ret=Book.objects.filter(Q(publishDate="2018-01-17")&~Q(price__gt=300)) print(ret) return HttpResponse("OK")