8-模型层-单表操作
一 ORM简介 查询数据层次图解:如果操作mysql,ORM是在pymysq之上又进行了一层封装
MVC或者MTV框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
ORM是“对象-关系-映射”的简称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #sql 中的表 #创建表: CREATE TABLE employee( id INT PRIMARY KEY auto_increment , name VARCHAR (20 ), gender BIT default 1 , birthday DATA , department VARCHAR (20 ), salary DECIMAL (8 ,2 ) unsigned, ); #sql 中的表纪录 #添加一条表纪录: INSERT employee (name,gender,birthday,salary,department) VALUES ("alex",1 ,"1985-12-12",8000 ,"保洁部"); #查询一条表纪录: SELECT * FROM employee WHERE age= 24 ; #更新一条表纪录: UPDATE employee SET birthday= "1989-10-24" WHERE id= 1 ; #删除一条表纪录: DELETE FROM employee WHERE name= "alex" #python的类 class Employee(models.Model): id= models.AutoField(primary_key= True ) name= models.CharField(max_length= 32 ) gender= models.BooleanField() birthday= models.DateField() department= models.CharField(max_length= 32 ) salary= models.DecimalField(max_digits= 8 ,decimal_places= 2 ) #python的类对象 #添加一条表纪录: emp= Employee(name= "alex",gender= True ,birthday= "1985-12-12",epartment= "保洁部") emp.save() #查询一条表纪录: Employee.objects.filter(age= 24 ) #更新一条表纪录: Employee.objects.filter(id= 1 ).update (birthday= "1989-10-24") #删除一条表纪录: Employee.objects.filter(name= "alex").delete ()
二 单表操作 2.1 创建表 1 创建模型
创建名为book的app,在book下的models.py中创建模型:
1 2 3 4 5 6 7 8 9 10 from django.db import modelsclass Book (models.Model): id = models.AutoField(primary_key=True ) name = models.CharField(max_length=64 ) pub_data = models.DateField() price = models.DecimalField(max_digits=5 , decimal_places=2 ) publish = models.CharField(max_length=12 ) def __str__ (self ): return self.name
2 更多字段 每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR
数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 AutoField(Field) - int 自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id 的列 from django.db import models class UserInfo (models.Model): username = models.CharField(max_length=32 ) class Group (models.Model): nid = models.AutoField(primary_key=True ) name = models.CharField(max_length=32 ) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 自定义无符号整数字段 class UnsignedIntegerField (models.IntegerField): def db_type (self, connection ): return 'integer UNSIGNED' PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 'AutoField' : 'integer AUTO_INCREMENT' , 'BigAutoField' : 'bigint AUTO_INCREMENT' , 'BinaryField' : 'longblob' , 'BooleanField' : 'bool' , 'CharField' : 'varchar(%(max_length)s)' , 'CommaSeparatedIntegerField' : 'varchar(%(max_length)s)' , 'DateField' : 'date' , 'DateTimeField' : 'datetime' , 'DecimalField' : 'numeric(%(max_digits)s, %(decimal_places)s)' , 'DurationField' : 'bigint' , 'FileField' : 'varchar(%(max_length)s)' , 'FilePathField' : 'varchar(%(max_length)s)' , 'FloatField' : 'double precision' , 'IntegerField' : 'integer' , 'BigIntegerField' : 'bigint' , 'IPAddressField' : 'char(15)' , 'GenericIPAddressField' : 'char(39)' , 'NullBooleanField' : 'bool' , 'OneToOneField' : 'integer' , 'PositiveIntegerField' : 'integer UNSIGNED' , 'PositiveSmallIntegerField' : 'smallint UNSIGNED' , 'SlugField' : 'varchar(%(max_length)s)' , 'SmallIntegerField' : 'smallint' , 'TextField' : 'longtext' , 'TimeField' : 'time' , 'UUIDField' : 'char(32)' , BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both' ,"ipv4" ,"ipv6" unpack_ipv4, 如果指定为True ,则输入::ffff:192.0 .2 .1 时候,可解析为192.0 .2 .1 ,开启刺功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None , 正则匹配 recursive=False , 递归下面的文件夹 allow_files=True , 允许文件 allow_folders=False , 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None , 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10 进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
3 更多参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 (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 ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。 class UserInfo (models.Model): nid = models.AutoField(primary_key=True ) username = models.CharField(max_length=32 ) class Meta : db_table = "table_name" index_together = [ ("pub_date" , "deadline" ), ] unique_together = (("driver" , "restaurant" ),) verbose_name verbose_name_plural
4 settings配置 若想将模型转为mysql数据库中的表,需要在settings中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'NAME' : 'lqz' , 'USER' : 'root' , 'PASSWORD' : '123456' , 'HOST' : '127.0.0.1' , 'PORT' : 3306 , 'ATOMIC_REQUEST' : True , 'OPTIONS' : { "init_command" : "SET storage_engine=MyISAM" , } } } ''' 'NAME':要连接的数据库,连接前需要创建好 'USER':连接数据库的用户名 'PASSWORD':连接数据库的密码 'HOST':连接主机,默认本机 'PORT':端口 默认3306 'ATOMIC_REQUEST': True, 设置为True统一个http请求对应的所有sql都放在一个事务中执行(要么所有都成功,要么所有都失败)。 是全局性的配置, 如果要对某个http请求放水(然后自定义事务),可以用non_atomic_requests修饰器 'OPTIONS': { "init_command": "SET storage_engine=MyISAM", } 设置创建表的存储引擎为MyISAM,INNODB '''
注意1: NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。然后,启动项目,会报错:no module named MySQLdb 。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL 所以,我们只需要找到项目名文件下的init ,在里面写入:
1 2 import pymysqlpymysql.install_as_MySQLdb()
最后通过两条数据库迁移命令即可在指定的数据库中创建表 :
1 2 python manage.py makemigrations python manage.py migrate
**注意2:**确保配置文件中的INSTALLED_APPS中写入我们创建的app名称
1 2 3 4 5 6 7 8 9 INSTALLED_APPS = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , "book" ]
**注意3:**如果报错如下:
1 django .core.exceptions.ImproperlyConfigured: mysqlclient 1 .3 .3 or newer is required; you have 0 .7 .11 .None
MySQLclient目前只支持到python3.4,因此如果使用的更高版本的python,需要修改如下:
通过查找路径C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql 这个路径里的文件把
1 2 if version < (1 , 3 , 3 ): raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
注释掉就可以了
注意4: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 LOGGING = { 'version' : 1 , 'disable_existing_loggers' : False , 'handlers' : { 'console' :{ 'level' :'DEBUG' , 'class' :'logging.StreamHandler' , }, }, 'loggers' : { 'django.db.backends' : { 'handlers' : ['console' ], 'propagate' : True , 'level' :'DEBUG' , }, } }
5 增加,删除字段 删除,直接注释掉字段,执行数据库迁移命令即可
新增字段,在类里直接新增字段,直接执行数据库迁移命令会提示输入默认值,此时需要设置
1 publish = models.CharField(max_length=12 ,default ='人民出版社' ,null =True )
注意:
1 数据库迁移记录都在 app01下的migrations里
2 使用showmigrations命令可以查看没有执行migrate的文件
3 makemigrations是生成一个文件,migrate是将更改提交到数据库
2.2 添加表纪录 方式1
1 2 book_obj=Book.objects.create(title="python葵花宝典" ,state=True ,price=100 ,publish="苹果出版社" ,pub_date="2012-12-12" )
方式2
1 2 book_obj=Book(title="python葵花宝典" ,state=True ,price=100 ,publish="苹果出版社" ,pub_date="2012-12-12" ) book_obj.save()
2.3 查询表纪录 查询API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 <1> all(): 查询所有结果 <2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 <3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。 <4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 <5> order_by(*field): 对查询结果排序('-id') <6> reverse(): 对查询结果反向排序 <8> count(): 返回数据库中匹配查询(QuerySet)的对象数量。 <9> first(): 返回第一条记录 <10> last(): 返回最后一条记录 <11> exists(): 如果QuerySet包含数据,就返回True,否则返回False <12> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 <13> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 <14> distinct(): 从返回结果中剔除重复纪录 def index(request): # 添加表记录++++++++++++++++++++++++++++++++++ # 方式一 # book=Book(name='红楼梦',pub_data='2015-10-12',price=88,publish='老男孩出版社') # book.save() # 方式二 # Book.objects.create(name='Python红宝书',pub_data='2010-10-12',price=100,publish='人民出版社') # 查询表记录++++++++++++++++++++++++++++++++++ # QUerySet数据类型(类似于一个列表,里面放着一些对象) # 1 方法的返回值是什么 # 2 方法的调用者 # (1) all方法 返回一个QuerySet对象 # book_list=Book.objects.all() # print(book_list[1].name) # print(book_list) # for obj in book_list: # print(obj.name) # (2)first last:调用者是queryset对象,返回值是对象 # book=Book.objects.all().first() # book2=Book.objects.all().last() # print(book) # print(book2) # (3) filter 返回值是queryset对象(相当于where语句) # 可以加多个过滤条件 # book=Book.objects.filter(name='红楼梦').first() # print(book) # (4)get方法 有且只有一个查询结果才有意义 返回值是一个对象 # book=Book.objects.get(name='红楼梦') # print(book) # 直接报错 # book = Book.objects.get(name='红楼梦eee') # --------------最常用----------------- # (5)exclude 除了查询之外的 返回值也是queryset # ret=Book.objects.exclude(name='红楼梦') # print(ret) # (6)order_by(默认升序,加个- 就是降序),可以多个过滤条件调用者是queryset返回值也是queryset # book_list=Book.objects.all().order_by('id') # book_list=Book.objects.all().order_by('-id','price') # print(book_list) # (7)count() 调用者是queryset,返回值是int # ret=Book.objects.all().count() # print(ret) # (8)exist()判断是是否有值,不能传参数, # ret=Book.objects.all().exists() # print(ret) # (9)values方法 # 查询所有书籍的名称(里面传的值,前提是表有这个字段)也是queryset但是里面放的是字典 ''' values原理 temp=[] for obj in Book.objects.all(): temp.append({'name':obj.name}) ''' # ret=Book.objects.all().values('name') # print(ret) # 不加.all()也可以,调用是queryset返回值也是queryset # ret=Book.objects.values('price') # print(ret) # (10)value_list # ret=Book.objects.all().values_list('price','name') # print(ret) # (11) distinct seletc * 的时候没有意义 # SELECT DISTINCT name from app01_book; # 没有任何意义,不要这样么用 # Book.objects.all().distinct() # ret=Book.objects.all().values('name').distinct() # print(ret) # 双下划线模糊查询----------------------- # 查询价格大于100的书 # ret=Book.objects.filter(price__gt=100) # print(ret) # 查询大于50小于100的书 # ret=Book.objects.filter(price__gt=50,price__lt=100) # print(ret) # 查询已红楼开头的书 # ret=Book.objects.filter(name__startswith='红楼') # print(ret) # 查询包含‘红’的书 # ret= Book.objects.filter(name__contains='红') # print(ret) # icontains 不区分大小写 # 价格在50,88,100 中的 # ret=Book.objects.filter(price__in=[50,88,100]) # print(ret) # 出版日期在2018年的 # ret=Book.objects.filter(pub_data__year=2015,pub_data__month=2) # print(ret) # 删除,修改------------------------ # delete:调用者可以是queryset也可以是model对象 # 删除价格为188的书有返回值 (1, {'app01.Book': 1}) 删除的个数,那张表,记录数 # ret=Book.objects.filter(price=188).delete() # print(ret) # ret=Book.objects.filter(price=100).first().delete() # print(ret) # 修改 update只能queryset来调用 返回值为int # ret=Book.objects.filter(name='红楼梦1').update(name='红楼梦') # print(ret) # 报错 # Book.objects.filter(name='红楼梦').first().update(name='红楼梦1') # ret=Book.objects.filter(name='红楼梦1').first() # print(ret.delete()) # aa=Publish.objects.filter(name='人民出版社') # print(type(aa)) # aa.delete() return HttpResponse('ok')
基于双下划线的模糊查询 1 2 3 4 5 6 7 8 9 10 Book.objects.filter (price__in=[100 ,200 ,300 ]) Book.objects.filter (price__gt=100 ) Book.objects.filter (price__lt=100 ) Book.objects.filter (price__gte=100 ) Book.objects.filter (price__lte=100 ) Book.objects.filter (price__range=[100 ,200 ]) Book.objects.filter (title__contains="python" ) Book.objects.filter (title__icontains="python" ) Book.objects.filter (title__startswith="py" ) Book.objects.filter (pub_date__year=2012 )
2.5 删除表纪录 删除方法就是 delete()。它运行时立即删除对象而不返回任何值。例如:
你也可以一次性删除多个对象。每个 QuerySet 都有一个 delete() 方法,它一次性删除 QuerySet 中所有的对象。
例如,下面的代码将删除 pub_date 是2005年的 Entry 对象:
1 Entry.objects .filter (pub_date__year=2005 ).delete ()
在 Django 删除对象时,会模仿 SQL 约束 ON DELETE CASCADE 的行为,换句话说,删除一个对象时也会删除与它相关联的外键对象。例如:
1 2 3 b = Blog.objects.get (pk =1) b.delete()
要注意的是: delete() 方法是 QuerySet 上的方法,但并不适用于 Manager 本身。这是一种保护机制,是为了避免意外地调用 Entry.objects.delete() 方法导致 所有的 记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用:
1 Entry.objects .all ().delete ()
如果不想级联删除,可以设置为:
1 pubHouse = models.ForeignKey(to ='Publisher' , on_delete =models.SET_NULL, blank =True , null =True )
2.6 修改表纪录 1 Book.objects .filter (title__startswith="py" ).update (price=120
此外,update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录update()方法会返回一个整型数值,表示受影响的记录条数。
三 在Python脚本中调用Django环境 1 2 3 4 5 6 7 8 9 10 import os if __name__ == '__main__' : os .environ.setdefault("DJANGO_SETTINGS_MODULE" , "untitled15.settings" ) import django django.setup() from app01 import models books = models.Book.objects.all() print (books)
四 Django终端打印SQL语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 图书管理系统 实现功能:book单表的增删改查
2 查询操作练习 1 2 3 4 5 1 查询老男孩出版社出版过的价格大于200 的书籍2 查询2017 年8 月出版的所有以py开头的书籍名称 3 查询价格为50 ,100 或者150 的所有书籍名称及其出版社名称4 查询价格在100 到200 之间的所有书籍名称及其价格5 查询所有人民出版社出版的书籍的价格(从高到低排序,去重)
9-模型层-多表操作 一 创建模型 实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 Book id title price publish 1 php 100 人民出版社 2 python 200 老男孩出版社 3 go 100 人民出版社 4 java 300 人民出版社 为了存储出版社的邮箱,地址,在第一个表后面加字段 Book id title price publish email addr 1 php 100 人民出版社 111 北京 2 python 200 老男孩出版社 222 上海 3 go 100 人民出版社 111 北京 4 java 300 人民出版社 111 北京 这样会有大量重复的数据,浪费空间 #################################################################################### 一对多:一个出版社对应多本书(关联信息建在多的一方,也就是book表中) Book id title price publish_id 1 php 100 1 2 python 200 1 3 go 100 2 4 java 300 1 Publish id name email addr 1 人民出版社 111 北京 2 沙河出版社 222 沙河 总结:一旦确定表关系是一对多:在多对应的表中创建关联字段(在多的表里创建关联字段) ,publish_id 查询python这本书的出版社的邮箱(子查询) select publish_id from Book where title=“python” select email from Publish where id=1 #################################################################################### 多对多:一本书有多个作者,一个作者出多本书 Book id title price publish_id 1 php 100 1 2 python 200 1 3 go 100 2 4 java 300 1 Author id name age addr 1 alex 34 beijing 2 egon 55 nanjing Book2Author id book_id author_id 1 2 1 2 2 2 3 3 2 总结:一旦确定表关系是多对多:创建第三张关系表(创建中间表,中间表就三个字段,自己的id,书籍id和作者id) : id book_id author_id # lqz出版过的书籍名称(子查询) select id from Author where name='lqz' select book_id from Book2Author where author_id=1 select title from Book where id =book_id #################################################################################### 一对一:对作者详细信息的扩展(作者表和作者详情表) Author id name age ad_id(UNIQUE) 1 lqz 34 1 2 egon 55 2 AuthorDetail id addr gender tel gf_name author_id(UNIQUE) 1 beijing male 110 小花 1 2 nanjing male 911 杠娘 2 总结: 一旦确定是一对一的关系:在两张表中的任意一张表中建立关联字段+Unique ==================================== Publish Book Author AuthorDetail Book2Author CREATE TABLE publish( id INT PRIMARY KEY auto_increment , name VARCHAR (20 ) ); CREATE TABLE book( id INT PRIMARY KEY auto_increment , title VARCHAR (20 ), price DECIMAL (8 ,2 ), pub_date DATE , publish_id INT , FOREIGN KEY (publish_id) REFERENCES publish(id) ); CREATE TABLE authordetail( id INT PRIMARY KEY auto_increment , tel VARCHAR (20 ) ); CREATE TABLE author( id INT PRIMARY KEY auto_increment , name VARCHAR (20 ), age INT, authordetail_id INT UNIQUE , FOREIGN KEY (authordetail_id) REFERENCES authordetail(id) ); CREATE TABLE book2author( id INT PRIMARY KEY auto_increment , book_id INT , author_id INT , FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (author_id) REFERENCES author(id) )
注意:关联字段与外键约束没有必然的联系(建管理字段是为了进行查询,建约束是为了不出现脏数据)
在Models创建如下模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Book (models.Model): nid = models.AutoField(primary_key=True ) name = models.CharField(max_length=32 ) price = models.DecimalField(max_digits=5 , decimal_places=2 ) publish_date = models.DateField() publish = models.ForeignKey(to='Publish' ,to_field='nid' ,on_delete=models.CASCADE) authors=models.ManyToManyField(to='Author' ) def __str__ (self ): return self.name class Author (models.Model): nid = models.AutoField(primary_key=True ) name = models.CharField(max_length=32 ) age = models.IntegerField() author_detail = models.OneToOneField(to='AuthorDatail' ,to_field='nid' ,unique=True ,on_delete=models.CASCADE) class AuthorDatail (models.Model): nid = models.AutoField(primary_key=True ) telephone = models.BigIntegerField() birthday = models.DateField() addr = models.CharField(max_length=64 ) class Publish (models.Model): nid = models.AutoField(primary_key=True ) name = models.CharField(max_length=32 ) city = models.CharField(max_length=32 ) email = models.EmailField()
生成的表如下:
注意事项:
表的名称myapp_modelName
,是根据 模型中的元数据自动生成的,也可以覆写为别的名称
id
字段是自动添加的
对于外键字段,Django 会在字段名上添加"_id"
来创建数据库中的列名
这个例子中的CREATE TABLE
SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。
定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py
所在应用的名称。
外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
二 添加表记录 一对多的 1 2 3 4 5 6 方式1 : publish_obj=Publish.objects.get(nid=1 ) book_obj=Book.objects.create(title="" ,publishDate="2012-12-12" ,price=100 ,publish=publish_obj) 方式2 : book_obj=Book.objects.create(title="" ,publishDate="2012-12-12" ,price=100 ,publish_id=1 )
核心:book_obj.publish与book_obj.publish_id是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 关键点: 一 book_obj.publish=Publish.objects.filter (id =book_obj.publish_id).first() 二 book_obj.authors.all () 关键点:book.authors.all () 1 book.id =3 2 book_authors id book_id author_ID 3 3 1 4 3 2 3 author id name 1 lqz 2 egon book_obj.authors.all () -------> [lqz,egon] pub=Publish.objects.create(name='egon出版社' ,email='445676@qq.com' ,city='山东' ) print (pub)import datetime,timenow=datetime.datetime.now().__str__() now = datetime.datetime.now().strftime('%Y-%m-%d' ) print (type (now))print (now)Book.objects.create(name='海燕3' ,price=333.123 ,publish_date=now,publish_id=2 ) Book.objects.create(name='海3燕3' ,price=35.123 ,publish_date='2018/02/28' ,publish=pub) pub=Publish.objects.filter (nid=1 ).first() book=Book.objects.create(name='测试书籍' ,price=33 ,publish_date='2018-7-28' ,publish=pub) print (book.publish.name)book=Book.objects.filter (name='红楼梦' ).first() print (book.publish.email)
多对多 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 book_obj=Book.objects.create(title="追风筝的人" ,price=200 ,publishDate="2012-11-12" ,publish_id=1 ) yuan=Author.objects.filter (name="yuan" ).first() egon=Author.objects.filter (name="alex" ).first() book_obj.authors.add(yuan,egon) book = Book.objects.filter (name='红楼梦' ).first() egon=Author.objects.filter (name='egon' ).first() lqz=Author.objects.filter (name='lqz' ).first() book.authors.add(lqz,egon) book.authors.add(1 ,3 ) book.authors.add(*[1 ,2 ]) book = Book.objects.filter (name='红楼梦' ).first() book.authors.remove(1 ) egon = Author.objects.filter (name='egon' ).first() book.authors.remove(egon) book.authors.remove(*[1 ,2 ]) book.authors.clear() ret=book.authors.all () ret=book.authors.all ().values('name' ) print (ret)
核心:book_obj.authors.all()是什么?
多对多关系其它常用API:
1 2 3 book_obj.authors.remove() book_obj.authors.clear() book_obj.authors.set()
三 基于对象的跨表查询 一对多查询(publish与book) 正向查询(按字段:publish)
1 2 3 4 book_obj =Book.objects.filter(pk=1).first()print (book_obj.publish.city)
反向查询(按表名:book_set)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 publish=Publish.objects.get(name="苹果出版社" ) book_list=publish.book_set.all () for book_obj in book_list: print (book_obj.title) book=Book.objects.filter (name='红楼梦' ).first() print (book.publish) print (book.publish.name) pub=Publish.objects.filter (name='人民出版社' ).first() ret=pub.book_set.all () print (ret)
一对一查询(Author 与 AuthorDetail) 正向查询(按字段:authorDetail):
1 2 egon=Author.objects.filter (name="egon" ).first() print (egon.authorDetail.telephone)
反向查询(按表名:author):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 authorDetail_list=AuthorDetail.objects.filter (addr="beijing" ) for obj in authorDetail_list: print (obj.author.name) lqz=Author.objects.filter (name='lqz' ).first() tel=lqz.author_detail.telephone print (tel)author_detail=AuthorDatail.objects.filter (addr='北京' ).first() name=author_detail.author.name print (name)
多对多查询 (Author 与 Book) 正向查询(按字段:authors):
1 2 3 4 5 6 book_obj=Book.objects.filter (title="眉" ).first() authors=book_obj.authors.all () for author_obj in authors: print (author_obj.name,author_obj.authorDetail.telephone)
反向查询(按表名:book_set):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 author_obj=Author.objects.get(name="egon" ) book_list=author_obj.book_set.all () for book_obj in book_list: print (book_obj.title) book=Book.objects.filter (name='红楼梦' ).first() ret=book.authors.all () print (ret)for auth in ret:print (auth.name)author=Author.objects.filter (name='lqz' ).first() ret=author.book_set.all () print (ret)
注意:
你可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改:
1 publish = ForeignKey(Book, related_name='bookList' )
那么接下来就会如我们看到这般:
1 2 3 4 publish =Publish.objects.get(name="人民出版社" )book_list =publish.bookList.all()
四 基于双下划线的跨表查询 Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model 为止。
1 2 3 ''' 正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表 '''
一对多查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 queryResult=Book.objects.filter (publish__name="苹果出版社" ).values_list("title" ,"price" ) queryResult=Publish.objects.filter (name="苹果出版社" ).values_list("book__title" ,"book__price" ) 查询的本质一样,就是select from 的表不一样 ret=Book.objects.filter (name='红楼梦' ).values('publish__name' ) print (ret)ret=Publish.objects.filter (book__name='红楼梦' ).values('name' ) print (ret)
多对多查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 queryResult=Book.objects.filter (authors__name="lqz" ).values_list("title" ) queryResult=Author.objects.filter (name="lqz" ).values_list("book__title" ,"book__price" ) ret=Book.objects.filter (name='红楼梦' ).values('publish__name' ) print (ret)ret=Publish.objects.filter (book__name='红楼梦' ).values('name' ) print (ret)ret=Book.objects.filter (name='红楼梦' ).values('authors__name' ) print (ret)ret=Author.objects.filter (book__name='红楼梦' ).values('name' ) ret=Author.objects.filter (book__name='红楼梦' ).values('name' ,'author_detail__addr' ) print (ret)
一对一查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ret=Author.objects.filter (name="lqz" ).values("authordetail__telephone" ) ret=AuthorDetail.objects.filter (author__name="lqz" ).values("telephone" ) ret=Author.objects.filter (name='lqz' ).values('author_detail__telephone' ) print (ret)ret= AuthorDatail.objects.filter (author__name='lqz' ).values('telephone' ) print (ret)
进阶练习(连续跨表) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 queryResult=Book.objects.filter (publish__name="人民出版社" ).values_list("title" ,"authors__name" ) queryResult=Publish.objects.filter (name="人民出版社" ).values_list("book__title" ,"book__authors__age" ,"book__authors__name" ) queryResult=Book.objects.filter (authors__authorDetail__telephone__regex="151" ).values_list("title" ,"publish__name" ) ret=Author.objects.filter (authordetail__telephone__startswith="151" ).values("book__title" ,"book__publish__name" ) ret=AuthorDatail.objects.filter (telephone__startswith='33' ).values('author__book__name' ,'author__book__publish__name' ) print (ret) ret=Author.objects.filter (author_detail__telephone__startswith=33 ).values('book__name' ,'book__publish__name' ) print (ret)ret=Book.objects.filter (authors__author_detail__telephone__startswith='33' ).values('name' ,'publish__name' ) print (ret)ret=Publish.objects.filter (book__authors__author_detail__telephone__startswith='33' ).values('book__name' ,'name' ) print (ret)
1 2 3 4 publish = ForeignKey(Blog, related_name='bookList' ) queryResult=Publish.objects.filter (name="人民出版社" ).values_list("bookList__title" ,"bookList__price" )
反向查询时,如果定义了related_name ,则用related_name替换表名
五 聚合查询与分组查询 聚合 aggregate
(*args, **kwargs)
1 2 3 4 from django.db.models import AvgBook.objects.all ().aggregate(Avg('price' ))
aggregate()
是QuerySet
的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
1 2 Book.objects.aggregate(average_price=Avg('price' ))
如果你希望生成不止一个聚合,你可以向aggregate()
子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from django.db.models import Avg, Max, MinBook.objects.aggregate(Avg('price' ), Max('price' ), Min('price' )) from django.db.models import Avg,Count,Max,Minret=Book.objects.all ().aggregate(Avg('price' )) ret=Book.objects.all ().aggregate(avg_price=Avg('price' )) ret=Book.objects.all ().aggregate(avg_price=Avg('price' ),max_price=Max('price' )) ret = Book.objects.all ().aggregate(avg_price=Avg('price' ), min_price=Min('price' )) ret = Book.objects.all ().aggregate(avg_price=Avg('price' ), max_price=Max('price' ),count=Count('price' )) ret = Book.objects.all ().aggregate(avg_price=Avg('price' ), max_price=Max('price' ),count=Count('nid' )) print (ret)
分组 annotate()为调用的QuerySet
中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。
查询练习 练习:统计每一本书作者个数
1 2 3 4 5 6 7 from django.db.models import Avg, Max, Sum, Min, Max, Countbook_list = models.Book.objects.all ().annotate(author_num=Count("authors" )) for book in book_list: print (book.name) print (book.author_num) book_list = models.Book.objects.all ().annotate(author_num=Count("authors" )).values('name' ,'author_num' ) print (book_list)
练习:统计每一个出版社的最便宜的书
1 2 3 publishList=Publish.objects.annotate(MinPrice=Min("book__price" )) for publish_obj in publishList: print (publish_obj.name,publish_obj.MinPrice)
annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
1 2 queryResult= Publish.objects.annotate(MinPrice=Min("book__price" )).values_list("name" ,"MinPrice" ) print (queryResult)
练习:统计每一本以py开头的书籍的作者个数:
1 queryResult=Book.objects .filter (title__startswith="Py" ).annotate (num_authors=Count ('authors' ))
练习:统计不止一个作者的图书:(作者数量大于一)
1 2 ret=models.Book .objects .annotate (author_num=Count ("authors" )).filter (author_num__gt=1 ).values ('name' ,'author_num' ) print (ret)
练习:根据一本图书作者数量的多少对查询集 QuerySet
进行排序:
1 Book.objects .annotate (num_authors=Count ('authors' )).order_by ('num_authors' )
练习:查询各个作者出的书的总价格:
1 2 ret=models.Author .objects .annotate (sum_price=Sum ("book__price" )).values ("name" , "sum_price" ) print (ret)
练习:查询每个出版社的名称和书籍个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 ret=models.Publish.objects.all ().annotate(c=Count('book__name' )).values ('name' ,'c' ) print(ret) ''' 查询每一个部门名称以及对应的员工数 book: id name price publish 1 金品 11.2 1 2 西游 14.2 2 3 东游 16.2 2 4 北邮 19.2 3 ''' # 示例一:查询每一个出版社id,以及出书平均价格 # select publish_id,avg(price) from app01_book group by publish_id; # annotate # annotate() 内写聚合函数 # values 在前表示group by 的字段 # values 在后表示取某几个字段 # filter 在前表示where # filter 在后表示having # from django.db.models import Avg, Count, Max, Min # ret=models.Book.objects.values ('publish_id' ).annotate(avg=Avg('price' )).values ('publish_id' ,'avg' ) # print(ret) # 查询出版社id大于1 的出版社id,以及出书平均价格 #select publish_id,avg(price) from app01_book where publish_id>1 group by publish_id; # ret=models.Book.objects.values ('publish_id' ).filter (publish_id__gt=1 ).annotate(avg=Avg('price' )).values ('publish_id' ,'avg' ) # print(ret) # 查询出版社id大于1 的出版社id,以及出书平均价格大于30 的 # select publish_id,avg(price)as aaa from app01_book where publish_id>1 group by publish_id HAVING aaa>30 ; # ret = models.Book.objects.values ('publish_id' ).filter (publish_id__gt=1 ).annotate(avg=Avg('price' )).filter (avg__gt=30 ).values ( # 'publish_id' , 'avg' ) # print(ret) ## 查询每一个出版社出版的书籍个数 # pk 代指主键 # ret=models.Book.objects.get (pk=1 ) # print(ret.name) # ret=models.Publish.objects.values ('pk' ).annotate(count=Count('book__id' )).values ('name' ,'count' ) # print(ret) # 如果没有指定group by 的字段,默认就用基表(Publish)主键字段作为group by 的字段 # ret=models.Publish.objects.annotate(count=Count('book__id' )).values ('name' ,'count' ) # print(ret) # 另一种方式实现 # ret=models.Book.objects.values ('publish' ).annotate(count=Count('id' )).values ('publish__name' ,'count' ) # print(ret) #查询每个作者的名字,以及出版过书籍的最高价格(建议使用分组的表作为基表) # 如果不用分组的表作为基表,数据不完整可能会出现问题 # ret=models.Author.objects.values ('pk' ).annotate(max=Max('book__price' )).values ('name' ,'max' ) # ret = models.Author.objects.annotate(max=Max('book__price' )).values ('name' , 'max' ) # ret= models.Book.objects.values ('authors__id' ).annotate(max=Max('price' )).values ('authors__name' ,'max' ) # print(ret) #查询每一个书籍的名称,以及对应的作者个数 # ret=models.Book.objects.values ('pk' ).annotate(count=Count('authors__id' )).values ('name' ,'count' ) # ret=models.Book.objects.annotate(count=Count('authors__id' )).values ('name' ,'count' ) # ret=models.Author.objects.values ('book__id' ).annotate(count=Count('id' )).values ('book__name' ,'count' ) # # print(ret) #统计不止一个作者的图书 # ret=models.Book.objects.values ('pk' ).annotate(count=Count('authors__id' )).filter (count__gt=1 ).values ('name' ,'count' ) # ret = models.Author.objects.values ('book__id' ).annotate(count=Count('id' )).filter (count__gt=1 ).values ('book__name' , 'count' ) # print(ret) # 统计价格数大于10 元,作者的图书 ret = models.Book.objects.values ('pk' ).filter (price__gt=10 ).annotate(count=Count('authors__id' )).values ('name' , 'count' ) print(ret) #统计价格数大于10 元,作者个数大于1 的图书 ret = models.Book.objects.values ('pk' ).filter (price__gt=10 ).annotate(count=Count('authors__id' )).filter (count__gt=1 ).values ('name' , 'count' ) print(ret)
六 F查询与Q查询 F查询 在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
1 2 3 from django.db.models import FBook.objects.filter (commnetNum__lt=F('keepNum' ))
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
1 2 Book.objects.filter (commnetNum__lt=F('keepNum' )*2 )
修改操作也可以使用F函数,比如将每一本书的价格提高30元:
1 Book.objects .all ().update (price=F ("price" )+30 )
Q查询 filter()
等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR
语句),你可以使用Q 对象
。
1 2 from django.db .models import Q Q (title__startswith='Py' )
Q
对象可以使用&
和|
操作符组合起来。当一个操作符在两个Q
对象上使用时,它产生一个新的Q
对象。
1 bookList=Book . objects.filter(Q(authors__name ="lqz" ) |Q(authors__name ="egon" ) )
等同于下面的SQL WHERE
子句:
1 WHERE name ="lqz" OR name ="egon"
你可以组合&
和|
操作符以及使用括号进行分组来编写任意复杂的Q
对象。同时,Q
对象可以使用~
操作符取反,这允许组合正常的查询和取反(NOT
) 查询:
查询函数可以混合使用Q 对象
和关键字参数。所有提供给查询函数的参数(关键字参数或Q
对象)都将”AND”在一起。但是,如果出现Q
对象,它必须位于所有关键字参数的前面。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 bookList =Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year =2017), title__icontains ="python" )from django.db.models import F,Qret =Book.objects.filter(commit_num__gt=F('reat_num'))print (ret)Book.objects.all().update(price =F('price')+10) ret =Book.objects.filter(Q(name='红楼梦')|Q(price__gt=100))print (ret)ret = Book.objects.filter(Q(name ='红楼梦' ) & Q(price__gt =100)) print (ret)ret2 =Book.objects.filter(name='红楼梦',price__gt=100)print (ret2)ret =Book.objects.filter((Q(name='红楼梦') & Q(price__gt =100))|Q(nid__gt=2))print (ret)ret =Book.objects.filter(~Q(name='红楼梦'))print (ret)ret =Book.objects.filter(~Q(name='红楼梦'),price__gt=100)print (ret)
七 查询练习 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 查找所有书名里包含红楼的书 查找出版日期是2017年的书 查找出版日期是2017年的书名 查找价格大于10元的书 查找价格大于10元的书名和价格 查找在北京的出版社 查找名字以沙河开头的出版社 查找作者名字里面带“小”字的作者 查找年龄大于30岁的作者 查找手机号是155开头的作者 查找手机号是155开头的作者的姓名和年龄 查找书名是“红楼梦”的书的出版社 查找书名是“红楼梦”的书的出版社所在的城市 查找书名是“红楼梦”的书的出版社的名称 查找书名是“红楼梦”的书的所有作者 查找书名是“红楼梦”的书的作者的年龄 查找书名是“红楼梦”的书的作者的手机号码 查找书名是“红楼梦”的书的作者的地址 查找书名是“红楼梦”的书的作者的邮箱
10-模型层常用非常用字段和参数 1 ORM字段 AutoField int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列。
IntegerField 一个整数类型,范围在 -2147483648 to 2147483647。
CharField 字符类型,必须提供max_length参数, max_length表示字符长度。
DateField 日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。
DateTimeField 日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例
常用和非常用字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 AutoField(Field) - int 自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint 自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import modelsclass UserInfo(models.Model):# 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32 ) class Group (models.Model):# 自定义自增列 nid = models.AutoField(primary_key=True ) name = models.CharField(max_length=32 )SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin 以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin 以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin 以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both' ,"ipv4","ipv6" unpack_ipv4, 如果指定为True ,则输入::ffff:192.0 .2 .1 时候,可解析为192.0 .2 .1 ,开启此功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin 以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin 以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin 以及ModelForm中提供对UUID 格式的验证 FilePathField(Field) - 字符串,Django Admin 以及ModelForm中提供读取文件夹下文件的功能 - 参数: path , 文件夹路径match=None , 正则匹配 recursive =False , 递归下面的文件夹allow_files=True , 允许文件 allow_folders=False , 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage .FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage .FileSystemStorage width_field=None , 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint 存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10 进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型 对应关系: 'AutoField' : 'integer AUTO_INCREMENT' , 'BigAutoField' : 'bigint AUTO_INCREMENT' , 'BinaryField' : 'longblob' , 'BooleanField' : 'bool' , 'CharField' : 'varchar(%(max_length)s)' , 'CommaSeparatedIntegerField' : 'varchar(%(max_length)s)' , 'DateField' : 'date' , 'DateTimeField' : 'datetime' , 'DecimalField' : 'numeric(%(max_digits)s, %(decimal_places)s)' , 'DurationField' : 'bigint' , 'FileField' : 'varchar(%(max_length)s)' , 'FilePathField' : 'varchar(%(max_length)s)' , 'FloatField' : 'double precision' , 'IntegerField' : 'integer' , 'BigIntegerField' : 'bigint' , 'IPAddressField' : 'char(15)' , 'GenericIPAddressField' : 'char(39)' , 'NullBooleanField' : 'bool' , 'OneToOneField' : 'integer' , 'PositiveIntegerField' : 'integer UNSIGNED' , 'PositiveSmallIntegerField' : 'smallint UNSIGNED' , 'SlugField' : 'varchar(%(max_length)s)' , 'SmallIntegerField' : 'smallint' , 'TextField' : 'longtext' , 'TimeField' : 'time' , 'UUIDField' : 'char(32)' ,
2 ORM字段参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 用于表示某个字段可以为空。 如果设置为unique =True 则该字段在此表中必须是唯一的 。 如果db_index =True 则代表着为此字段设置索引。 为该字段设置默认值。 配置auto_now_add =True ,创建数据记录的时候会把当前时间添加到数据库。 配置上auto_now =True ,每次更新数据记录的时候会更新该字段。 null 数据库中字段是否可以为空db_column 数据库中字段的列名 db_tablespace default 数据库中字段的默认值primary_key 数据库中字段是否为主键 db_index 数据库中字段是否可以建立索引 unique 数据库中字段是否可以建立唯一索引 unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引 unique_for_month 数据库中字段【月】部分是否可以建立唯一索引 unique_for_year 数据库中字段【年】部分是否可以建立唯一索引 verbose_name Admin中显示的字段名称 blank Admin中是否允许用户输入为空 editable Admin中是否可以编辑 help_text Admin中该字段的提示信息 choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作 如:gf = models.IntegerField(choices=[(0, '何穗' ),(1, '大表姐' ),],default =1) error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息; 字典健:null , blank, invalid, invalid_choice, unique, and unique_for_date 如:{'null' : "不能为空." , 'invalid' : '格式错误' } validators 自定义错误验证(列表类型),从而定制想要的验证规则 from django.core.validators import RegexValidatorfrom django.core.validators import EmailValidator,URLValidator,DecimalValidator,\MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 如: test = models.CharField( max_length =32,error_messages={ 'c1' : '优先错信息1' ,'c2' : '优先错信息2' ,'c3' : '优先错信息3' ,}, validators=[ RegexValidator(regex ='root_\d+' , message ='错误了' , code ='c1' ), RegexValidator(regex ='root_112233\d+' , message ='又错误了' , code ='c2' ), EmailValidator(message ='又错误了' , code ='c3' ), ] )
3 关系字段 ForeignKey 外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 ‘一对多’中’多’的一方。
ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。
to 设置要关联的表
to_field 设置要关联的表的字段
反向操作时,使用的字段名,用于代替原反向查询时的’表名_set’。
例如:
1 2 3 4 5 6 class Classes(models .Model) : name = models.CharField(max_length =32) class Student(models .Model) : name = models.CharField(max_length =32) theclass = models.ForeignKey(to ="Classes" )
当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:
1 models.Classes .objects .first ().student_set .all ()
当我们在ForeignKey字段中添加了参数 related_name 后,
1 2 3 class Student(models .Model) : name = models.CharField(max_length =32) theclass = models.ForeignKey(to ="Classes" , related_name ="students" )
当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:
1 models.Classes .objects .first ().students .all ()
反向查询操作时,使用的连接前缀,用于替换表名。
on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。
models.CASCADE 删除关联数据,与之关联也删除
models.DO_NOTHING 删除关联数据,引发错误IntegrityError
models.PROTECT 删除关联数据,引发错误ProtectedError
models.SET_NULL 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
models.SET_DEFAULT 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
models.SET
删除关联数据, a. 与之关联的值设置为指定值,设置:models.SET(值) b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
1 2 3 4 5 6 7 8 9 def func(): return 10 class MyModel(models.Model): user = models.ForeignKey( to ="User" , to_field ="id" , on_delete =models.SET(func) )
db_constraint 是否在数据库中创建外键约束,默认为True。
OneToOneField 一对一字段。
通常一对一字段用来扩展已有字段。
一对一的关联关系多用在当一张表的不同字段查询频次差距过大的情况下,将本可以存储在一张表的字段拆开放置在两张表中,然后将两张表建立一对一的关联关系。
1 2 3 4 5 6 7 8 class Author(models .Model) : name = models.CharField(max_length =32) info = models.OneToOneField(to ='AuthorInfo') class AuthorInfo(models .Model) : phone = models.CharField(max_length =11) email = models.EmailField()
to 设置要关联的表。
to_field 设置要关联的字段。
on_delete 同ForeignKey字段。
ManyToManyField 用于表示多对多的关联关系。在数据库中通过第三张表来建立关联关系
to 设置要关联的表
同ForeignKey字段。
同ForeignKey字段。
symmetrical 仅用于多对多自关联时,指定内部是否创建反向操作的字段。默认为True。
举个例子:
1 2 3 class Person(models .Model) : name = models.CharField(max_length =16) friends = models.ManyToManyField("self" )
此时,person对象就没有person_set属性。
1 2 3 class Person(models .Model) : name = models.CharField(max_length =16) friends = models.ManyToManyField("self" , symmetrical =False)
此时,person对象现在就可以使用person_set属性进行反向查询。
through 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。
through_fields 设置关联的字段。
db_table 默认创建第三张表时,数据库中表的名称。
4 多对多关联关系的三种方式 方式一:自行创建第三张表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Book(models .Model) : title = models.CharField(max_length =32, verbose_name ="书名" ) class Author(models .Model) : name = models.CharField(max_length =32, verbose_name ="作者姓名" ) # 自己创建第三张表,分别通过外键关联书和作者 class Author2Book(models .Model) : author = models.ForeignKey(to ="Author" ) book = models.ForeignKey(to ="Book" ) class Meta: unique_together = ("author" , "book" )
方式二:通过ManyToManyField自动创建第三张表 1 2 3 4 5 6 7 8 class Book(models .Model) : title = models.CharField(max_length =32, verbose_name ="书名" ) # 通过ORM自带的ManyToManyField自动创建第三张表 class Author(models .Model) : name = models.CharField(max_length =32, verbose_name ="作者姓名" ) books = models.ManyToManyField(to ="Book" , related_name ="authors" )
方式三:设置ManyTomanyField并指定自行创建的第三张表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Book(models .Model) : title = models.CharField(max_length =32, verbose_name ="书名" ) # 自己创建第三张表,并通过ManyToManyField指定关联 class Author(models .Model) : name = models.CharField(max_length =32, verbose_name ="作者姓名" ) books = models.ManyToManyField(to ="Book" , through ="Author2Book" , through_fields =("author" , "book" ) ) # through_fields接受一个2 元组('field1',' field2'): # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。 class Author2Book(models .Model) : author = models.ForeignKey(to ="Author" ) book = models.ForeignKey(to ="Book" ) class Meta: unique_together = ("author" , "book" )
注意:
当我们需要在第三张关系表中存储额外的字段时,就要使用第三种方式。
但是当我们使用第三种方式创建多对多关联关系时,就无法使用set、add、remove、clear方法来管理多对多的关系了,需要通过第三张表的model来管理多对多关系。
5 元信息 ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
db_table ORM在数据库中的表名默认是 app_类名,可以通过 db_table 可以重写表名。
index_together 联合索引。
unique_together 联合唯一索引。
ordering 指定默认按什么字段排序。
只有设置了该属性,我们查询到的结果才可以被reverse()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class UserInfo (models.Model): nid = models.AutoField(primary_key=True) username = models.CharField(max_length=32 ) class Meta : # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # 联合索引 index_together = [ ("pub_date" , "deadline" ), ] # 联合唯一索引 unique_together = (("driver" , "restaurant" ),) ordering = ('name' ,) # admin中显示的表名称 verbose_name='哈哈' # verbose_name加s verbose_name_plural=verbose_name
6 自定义字段(了解) 自定义char类型字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class FixedCharField (models.Field): """ 自定义的char类型的字段类 """ def __init__ (self, max_length, *args, **kwargs ): self.max_length = max_length super (FixedCharField, self).__init__(max_length=max_length, *args, **kwargs) def db_type (self, connection ): """ 限定生成数据库表的字段类型为char,长度为max_length指定的值 """ return 'char(%s)' % self.max_length class Class (models.Model): id = models.AutoField(primary_key=True ) title = models.CharField(max_length=25 ) cname = FixedCharField(max_length=25 )
11-模型层-模型层进阶 一 QuerySet对象 1.1可切片 使用Python 的切片语法来限制查询集
记录的数目 。它等同于SQL 的LIMIT
和OFFSET
子句。
1 2 Entry.objects.all ()[:5 ] Entry.objects.all ()[5 :10 ]
不支持负的索引(例如Entry.objects.all()[-1]
)。通常,查询集
的切片返回一个新的查询集
—— 它不会执行查询。
1.2可迭代 1 2 3 4 articleList=models.Article.objects.all () for article in articleList: print (article.title)
1.3惰性查询 查询集
是惰性执行的 —— 创建查询集
不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集
需要求值时,Django 才会真正运行这个查询。
1 2 3 4 5 6 queryResult=models.Article.objects.all () print (queryResult) for article in queryResult: print (article.title)
一般来说,只有在“请求”查询集
的结果时才会到数据库中去获取它们。当你确实需要结果时,查询集
通过访问数据库来求值
1.4缓存机制 每个查询集
都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。
在一个新创建的查询集
中,缓存为空。首次对查询集
进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集
的缓存中并返回明确请求的结果(例如,如果正在迭代查询集
,则返回下一个结果)。接下来对该查询集
的求值将重用缓存的结果。
请牢记这个缓存行为,因为对查询集
使用不当的话,它会坑你的。例如,下面的语句创建两个查询集
,对它们求值,然后扔掉它们:
1 2 print ([a.title for a in models.Article.objects.all() ])print ([a.create_time for a in models.Article.objects.all() ])
这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存查询集
并重新使用它:
1 2 3 queryResult=models.Article .objects .all () print ([a.title for a in queryResult]) print ([a.create_time for a in queryResult])
何时查询集不会被缓存? 查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存, 如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。所以,这意味着使用切片或索引来限制查询集将不会填充缓存。
例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:
1 2 3 queryset = Entry.objects.all () print queryset[5 ] print queryset[5 ]
然而,如果已经对全部查询集求值过,则将检查缓存:
1 2 3 4 queryset = Entry .objects.all () [entry for entry in queryset] # Queries the database print queryset[5 ] # Uses cacheprint queryset[5 ] # Uses cache
下面是一些其它例子,它们会使得全部的查询集被求值并填充到缓存中:
1 2 3 4 [entry for entry in queryset] bool (queryset) entry in queryset list (queryset)
注:简单地打印查询集不会填充缓存。
1 2 3 queryResult =models.Article.objects.all()print (queryResult) # hits databaseprint (queryResult) # hits database
1.5 exists()与iterator()方法 exists: 简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据:
1 2 3 if queryResult.exists (): #SELECT (1 ) AS "a" FROM "blog_article" LIMIT 1 ; args=() print("exists...")
iterator: 当queryset非常巨大时,cache会成为问题。
处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。
1 2 3 4 5 6 7 objs = Book.objects.all().iterator() for obj in objs: print (obj.title) for obj in objs: print (obj.title)
当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。
总结:
queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。
二 中介模型 处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的ManyToManyField
就可以了。但是,有时你可能需要关联数据到两个模型之间的关系上。
例如,有这样一个应用,它记录音乐家所属的音乐小组。我们可以用一个ManyToManyField
表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。
对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的ManyToManyField
字段将使用through
参数指向中介模型。对于上面的音乐小组的例子,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from django.db import models class Person (models .Model ): name = models.CharField (max_length =128) def __str__(self ): # __unicode__ on Python 2 return self.name class Group (models .Model ): name = models.CharField (max_length =128) members = models.ManyToManyField (Person , through ='Membership ') def __str__(self ): # __unicode__ on Python 2 return self.name class Membership (models .Model ): person = models.ForeignKey (Person ) group = models.ForeignKey (Group ) date_joined = models.DateField () invite_reason = models.CharField (max_length =64)
既然你已经设置好ManyToManyField
来使用中介模型(在这个例子中就是Membership
),接下来你要开始创建多对多关系。你要做的就是创建中介模型的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 >>> ringo = Person.objects.create(name="Ringo Starr" ) >>> paul = Person.objects.create(name="Paul McCartney" ) >>> beatles = Group.objects.create(name="The Beatles" ) >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962 , 8 , 16 ), ... invite_reason="Needed a new drummer." ) >>> m1.save() >>> beatles.members.all () [<Person: Ringo Starr>] >>> ringo.group_set.all () [<Group: The Beatles>] >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960 , 8 , 1 ), ... invite_reason="Wanted to form a band." ) >>> beatles.members.all () [<Person: Ringo Starr>, <Person: Paul McCartney>]
与普通的多对多字段不同,你不能使用add
、 create
和赋值语句(比如,beatles.members = [...]
)来创建关系:
1 2 3 4 5 6 >>> beatles.members.add (john) >>> beatles.members.create(name ="George Harrison" ) >>> beatles.members = [john, paul, ringo, george]
为什么不能这样做? 这是因为你不能只创建 Person
和 Group
之间的关联关系,你还要指定 Membership
模型中所需要的所有信息;而简单的add
、create
和赋值语句是做不到这一点的。所以它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。
remove()
方法被禁用也是出于同样的原因。但是clear()
方法却是可用的。它可以清空某个实例所有的多对多关系:
1 2 3 4 5 >>> >>> beatles.members.clear() >>> >>> Membership.objects.all () []
三 查询优化 3.1表数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 class UserInfo(AbstractUser): "" " 用户信息 " "" nid = models.BigAutoField(primary_key =True ) nickname = models.CharField(verbose_name ='昵称' , max_length =32) telephone = models.CharField(max_length =11, blank =True , null =True , unique =True , verbose_name ='手机号码' ) avatar = models.FileField(verbose_name ='头像' ,upload_to = 'avatar/' ,default ="/avatar/default.png" ) create_time = models.DateTimeField(verbose_name ='创建时间' , auto_now_add =True ) fans = models.ManyToManyField(verbose_name ='粉丝们' , to ='UserInfo' , through ='UserFans' , related_name ='f' , through_fields=('user' , 'follower' )) def __str__(self): return self.username class UserFans(models.Model): "" " 互粉关系表 " "" nid = models.AutoField(primary_key =True ) user = models.ForeignKey(verbose_name ='博主' , to ='UserInfo' , to_field ='nid' , related_name ='users' ) follower = models.ForeignKey(verbose_name ='粉丝' , to ='UserInfo' , to_field ='nid' , related_name ='followers' ) class Blog(models.Model): "" " 博客信息 " "" nid = models.BigAutoField(primary_key =True ) title = models.CharField(verbose_name ='个人博客标题' , max_length =64) site = models.CharField(verbose_name ='个人博客后缀' , max_length =32, unique =True ) theme = models.CharField(verbose_name ='博客主题' , max_length =32) user = models.OneToOneField(to ='UserInfo' , to_field ='nid' ) def __str__(self): return self.title class Category(models.Model): "" " 博主个人文章分类表 " "" nid = models.AutoField(primary_key =True ) title = models.CharField(verbose_name ='分类标题' , max_length =32) blog = models.ForeignKey(verbose_name ='所属博客' , to ='Blog' , to_field ='nid' ) class Article(models.Model): nid = models.BigAutoField(primary_key =True ) title = models.CharField(max_length =50, verbose_name ='文章标题' ) desc = models.CharField(max_length =255, verbose_name ='文章描述' ) read_count = models.IntegerField(default =0) comment_count= models.IntegerField(default =0) up_count = models.IntegerField(default =0) down_count = models.IntegerField(default =0) category = models.ForeignKey(verbose_name ='文章类型' , to ='Category' , to_field ='nid' , null =True ) create_time = models.DateField(verbose_name ='创建时间' ) blog = models.ForeignKey(verbose_name ='所属博客' , to ='Blog' , to_field ='nid' ) tags = models.ManyToManyField( to ="Tag" , through ='Article2Tag' , through_fields=('article' , 'tag' ), ) class ArticleDetail(models.Model): "" " 文章详细表 " "" nid = models.AutoField(primary_key =True ) content = models.TextField(verbose_name ='文章内容' , ) article = models.OneToOneField(verbose_name ='所属文章' , to ='Article' , to_field ='nid' ) class Comment(models.Model): "" " 评论表 " "" nid = models.BigAutoField(primary_key =True ) article = models.ForeignKey(verbose_name ='评论文章' , to ='Article' , to_field ='nid' ) content = models.CharField(verbose_name ='评论内容' , max_length =255) create_time = models.DateTimeField(verbose_name ='创建时间' , auto_now_add =True ) parent_comment = models.ForeignKey('self' , blank =True , null =True , verbose_name ='父级评论' ) user = models.ForeignKey(verbose_name ='评论者' , to ='UserInfo' , to_field ='nid' ) up_count = models.IntegerField(default =0) def __str__(self): return self.content class ArticleUpDown(models.Model): "" " 点赞表 " "" nid = models.AutoField(primary_key =True ) user = models.ForeignKey('UserInfo' , null =True ) article = models.ForeignKey("Article" , null =True ) models.BooleanField(verbose_name ='是否赞' ) class CommentUp(models.Model): "" " 点赞表 " "" nid = models.AutoField(primary_key =True ) user = models.ForeignKey('UserInfo' , null =True ) comment = models.ForeignKey("Comment" , null =True ) class Tag(models.Model): nid = models.AutoField(primary_key =True ) title = models.CharField(verbose_name ='标签名称' , max_length =32) blog = models.ForeignKey(verbose_name ='所属博客' , to ='Blog' , to_field ='nid' ) class Article2Tag(models.Model): nid = models.AutoField(primary_key =True ) article = models.ForeignKey(verbose_name ='文章' , to ="Article" , to_field ='nid' ) tag = models.ForeignKey(verbose_name ='标签' , to ="Tag" , to_field ='nid' )
3.2.1简单使用 对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。
select_related 返回一个QuerySet
,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。
简单说,在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。
下面的例子解释了普通查询和select_related()
查询的区别。
查询id=2的文章的分类名称,下面是一个标准的查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # Hits the database. article=models.Article.objects.get (nid=2 ) # Hits the database again to get the related Blog object . print(article.category.title) SELECT "blog_article" ."nid" , "blog_article" ."title" , "blog_article" ."desc" , "blog_article" ."read_count" , "blog_article" ."comment_count" , "blog_article" ."up_count" , "blog_article" ."down_count" , "blog_article" ."category_id" , "blog_article" ."create_time" , "blog_article" ."blog_id" , "blog_article" ."article_type_id" FROM "blog_article" WHERE "blog_article" ."nid" = 2 ; args=(2 ,) SELECT "blog_category" ."nid" , "blog_category" ."title" , "blog_category" ."blog_id" FROM "blog_category" WHERE "blog_category" ."nid" = 4 ; args=(4 ,)
如果我们使用select_related()函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 articleList=models.Article.objects.select_related("category" ).all () for article_obj in articleList: print (article_obj.category.title) SELECT "blog_article" ."nid" , "blog_article" ."title" , "blog_article" ."desc" , "blog_article" ."read_count" , "blog_article" ."comment_count" , "blog_article" ."up_count" , "blog_article" ."down_count" , "blog_article" ."category_id" , "blog_article" ."create_time" , "blog_article" ."blog_id" , "blog_article" ."article_type_id" , "blog_category" ."nid" , "blog_category" ."title" , "blog_category" ."blog_id" FROM "blog_article" LEFT OUTER JOIN "blog_category" ON ("blog_article" ."category_id" = "blog_category" ."nid" )
**3.2.2 多外键查询 ** 这是针对category的外键查询,如果是另外一个外键呢?让我们一起看下:
1 2 article=models.Article .objects .select_related ("category" ).get (nid=1 ) print (article.articledetail)
观察logging结果,发现依然需要查询两次,所以需要改为:
1 2 article=models.Article .objects .select_related ("category" ,"articledetail" ).get (nid=1 ) print (article.articledetail)
或者:1.7以后支持链式操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 article= models.Article.objects .select_related("category") .select_related("articledetail") .get (nid= 1 ) # django 1.7 支持链式操作 print(article.articledetail) SELECT "blog_article"."nid", "blog_article"."title", ...... "blog_category"."nid", "blog_category"."title", "blog_category"."blog_id", "blog_articledetail"."nid", "blog_articledetail"."content", "blog_articledetail"."article_id" FROM "blog_article" LEFT OUTER JOIN "blog_category" ON ("blog_article"."category_id" = "blog_category"."nid") LEFT OUTER JOIN "blog_articledetail" ON ("blog_article"."nid" = "blog_articledetail"."article_id") WHERE "blog_article"."nid" = 1 ; args= (1 ,)
3.2.3 深层查询 1 2 3 4 article =models.Article.objects.select_related("blog").get(nid=1) print (article.blog.user.username)
依然需要查询两次:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 SELECT "blog_article" ."nid" , "blog_article" ."title" , ... ... "blog_blog" ."nid" , "blog_blog" ."title" , FROM "blog_article" INNER JOIN "blog_blog" ON ("blog_article" ."blog_id" = "blog_blog" ."nid" ) WHERE "blog_article" ."nid" = 1 ; SELECT "blog_userinfo" ."password" , "blog_userinfo" ."last_login" , ... ... FROM "blog_userinfo" WHERE "blog_userinfo" ."nid" = 1 ;
这是因为第一次查询没有query到userInfo表,所以,修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 article=models.Article.objects.select_related("blog__user" ).get(nid=1 ) print(article.blog.user.username) SELECT "blog_article" ."nid" , "blog_article" ."title" ,... ... "blog_blog" ."nid" , "blog_blog" ."title" , ... ... "blog_userinfo" ."password" , "blog_userinfo" ."last_login" , ... ... FROM "blog_article" INNER JOIN "blog_blog" ON ("blog_article" ."blog_id" = "blog_blog" ."nid" ) INNER JOIN "blog_userinfo" ON ("blog_blog" ."user_id" = "blog_userinfo" ."nid" ) WHERE "blog_article" ."nid" = 1 ;
3.2.4 总结
select_related主要针一对一和多对一关系进行优化。
select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。
没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询。
也可以通过depth参数指定递归的深度,Django会自动缓存指定深度内所有的字段。如果要访问指定深度外的字段,Django会再次进行SQL查询。
也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费。
Django >= 1.7,链式调用的select_related相当于使用可变长参数。Django < 1.7,链式调用会导致前边的select_related失效,只保留最后一个。
对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。
prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。
1 2 3 4 5 article_obj =models.Article.objects.all () for i in article_obj: print (i.tags.all ()) #4 篇文章: hits database 5
改为prefetch_related:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 article_obj =models.Article.objects.prefetch_related("tags").all() for i in article_obj: print (i.tags.all()) #4篇文章: hits database 2 SELECT "blog_article" ."nid" , "blog_article" ."title" , .. .. .. FROM "blog_article" ; SELECT ("blog_article2tag" ."article_id" ) AS "_prefetch_related_val_article_id" , "blog_tag" ."nid" , "blog_tag" ."title" , "blog_tag" ."blog_id" FROM "blog_tag" INNER JOIN "blog_article2tag" ON ("blog_tag" ."nid" = "blog_article2tag" ."tag_id" ) WHERE "blog_article2tag" ."article_id" IN (1, 2, 3, 4); def select_related(self, *f ields) 性能相关:表之间进行join连表操作,一次性获取关联的数据。 model.tb.objects.all().select_related() model.tb.objects.all().select_related('外键字段' ) model.tb.objects.all().select_related('外键字段__外键字段' ) def prefetch_related(self, *lookups) 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 # 获取所有用户表 # 获取用户类型表where id in (用户表中的查到的所有用户ID) models.UserInfo.objects.prefetch_related('外键字段' ) from django.db.models import Count, Case, When, IntegerField Article.objects.annotate( numviews =Count(Case( When(readership__what_time__lt =treshold, then =1), output_field =CharField(), )) ) students = Student.objects.all().annotate(num_excused_absences =models.Sum( models.Case( models.When(absence__type ='Excused' , then =1), default =0, output_field =models.IntegerField() ))) # 加select_related 主动做链表,相当于直接链表把数据全取出来了, # 不加:for 循环几次,就再次查几次数据库 # select_related('author_detail' )参数是fk的字段,可能有多个外键,所以可以写多个 ret =models.Author.objects.all().select_related('author_detail') for i in ret: print (i.author_detail.addr) ret = models.Author.objects.all() for i in ret: print (i.author_detail.addr) ret =models.Author.objects.all().prefetch_related('author_detail') for i in ret: print (i.author_detail.addr)
1 2 extra(select =None, where =None, params =None, tables =None, order_by =None, select_params =None)
有些情况下,Django的查询语法难以简单的表达复杂的 WHERE
子句,对于这种情况, Django 提供了 extra()
QuerySet
修改机制 — 它能在 QuerySet
生成的SQL从句中注入新子句
extra可以指定一个或多个 参数
,例如 select
, where
or tables
. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做
4.1参数之select The select
参数可以让你在 SELECT
从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。
1 2 queryResult=models.Article .objects .extra (select={'is_recent' : "create_time > '2017-09-05'" })
结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05.
练习:
1 2 3 4 5 6 7 article_obj =models.Article.objects .filter(nid =1) .extra(select={"standard_time" :"strftime('%%Y-%%m-%%d',create_time)" }) .values("standard_time" ,"nid" ,"title" ) print (article_obj) # <QuerySet [{'title' : 'MongoDb 入门教程' , 'standard_time' : '2017-09-03' , 'nid' : 1}]>
4.2参数之where
/ tables
您可以使用where
定义显式SQL WHERE
子句 - 也许执行非显式连接。您可以使用tables
手动将表添加到SQL FROM
子句。
where
和tables
都接受字符串列表。所有where
参数均为“与”任何其他搜索条件。
举例来讲:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 queryResult=models.Article .objects.extra(where =['nid in (1,3) OR title like "py%" ' ,'nid>2' ]) extra, 额外查询条件以及相关表,排序 models.UserInfo.objects.filter (id__gt=1 ) models.UserInfo.objects.all () # id name age ut_id models.UserInfo.objects.extra(self, select =None , where =None , params=None , tables =None , order_by=None , select_params=None ) # a. 映射 # select # select_params=None # select 此处 from 表 # b. 条件 # where =None # params=None , # select * from 表 where 此处 # c. 表 # tables # select * from 表,此处 # c. 排序 # order_by=None # select * from 表 order by 此处 models.UserInfo.objects.extra( select ={'newid' :'select count(1) from app01_usertype where id>%s' }, select_params=[1 ,], where = ['age>%s' ], params=[18 ,], order_by=['-age' ], tables =['app01_usertype' ] ) """ select app01_userinfo.id, (select count(1) from app01_usertype where id>1) as newid from app01_userinfo,app01_usertype where app01_userinfo.age > 18 order by app01_userinfo.age desc """ result = models.UserInfo.objects.filter (id__gt=1 ).extra( where =['app01_userinfo.id < %s' ], params=[100 ,], tables =['app01_usertype' ], order_by=['-app01_userinfo.id' ], select ={'uid' :1 ,'sw' :"select count(1) from app01_userinfo"} ) print(result.query) # SELECT (1 ) AS "uid", (select count(1 ) from app01_userinfo) AS "sw", "app01_userinfo"."id", "app01_userinfo"."name", "app01_userinfo"."age", "app01_userinfo"."ut_id" FROM "app01_userinfo" , "app01_usertype" WHERE ("app01_userinfo"."id" > 1 AND (app01_userinfo.id < 100 )) ORDER BY ("app01_userinfo".id) DESC # 在对象中加入字段 ret=models.Author.objects.all ().filter (nid__gt=1 ).extra(select ={'n' :'select count(*) from app01_book where nid>%s' },select_params=[1 ]) print(ret[0 ].n) print(ret.query) # 给字段重命名 ret=models.Author.objects.all ().filter (author_detail__telephone=132234556 ).extra(select ={'bb' :"app01_authordatail.telephone"}).values ('bb' ) print(ret) print(ret.query)
五 原生sql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from django.db import connection, connections cursor = connection.cursor () # connection=default数据cursor = connections['db2' ].cursor ()cursor .execute ("" "SELECT * from auth_user where id = %s" "" , [1 ])row = cursor .fetchone() row = cursor .fetchall() ret = models.Author.objects.raw('select * from app01_author where nid>1' )print (ret )for i in ret : print (i) print (ret .query)# 会把book的字段放到author对象中 ret = models.Author.objects.raw('select * from app01_book where nid>1' )print (ret )for i in ret : print (i.price) print (type (i))
六 整体插入 创建对象时,尽可能使用bulk_create()来减少SQL查询的数量。例如:
1 2 3 4 Entry.objects.bulk_create([ Entry(headline ="Python 3.0 Released" ), Entry(headline ="Python 3.1 Planned" ) ])
…更优于:
1 2 Entry.objects.create(headline ="Python 3.0 Released" ) Entry.objects.create(headline ="Python 3.1 Planned" )
注意该方法有很多注意事项,所以确保它适用于你的情况。
这也可以用在ManyToManyFields中,所以:
1 my_band.members.add (me, my_friend)
…更优于:
1 2 my_band.members.add (me) my_band.members.add (my_friend)
…其中Bands和Artists具有多对多关联。
七 事务操作 1 2 3 from django.db import transaction with transaction .atomic():
八 defer和only defer(‘id’,’name’):取出对象,字段除了id和name都有 only(‘id’,’name’):取的对象,只有id和name 如果点,依然能点出其它列,但是不要点了,因为取没有的列,会再次查询数据库
1 2 3 4 ret =models.Author.objects.only ('nid' )for i in ret : # 查询不在的字段,会再次查询数据库,造成数据库压力大 print (i.name)