Django之ORM
阅读目录
对应关系
映射关系:
python的类名对应的SQL语句的表名
python的类属性对应的SQL语句的表名下的字段
python的类属性的约束对应的SQL语句的表名下的字段类型
类的实例对象---------------表中的一条记录对象
创建表
class Student(models.Model): nid=models.AutoField(primary_key=True) # 主键约束 name=models.CharField(max_length=32) # 字符串字段 birth=models.DateField() # 日期类型 class_id=models.IntegerField(default=0) # 整数类型
外键创建
#多对一,放在多的一边 publish=models.ForeignKey(to="Publish",to_field="nid") #多对多,自动生成第三张表,放在任意一边 authors=models.ManyToManyField(to='Author') #一对一,放在常用的一边 authorDetail=models.OneToOneField(to="AuthorDetail")
字段类型详细
使用时需要引入django.db.models包,字段类型如下: AutoField:自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性。 BooleanField:布尔字段,值为True或False。 NullBooleanField:支持Null、True、False三种值。 CharField(max_length=字符长度):字符串。TextField:大文本字段,一般超过4000个字符时使用。 参数max_length表示最大字符个数。 IntegerField:整数。 DecimalField(max_digits=None, decimal_places=None):十进制浮点数。FloatField:浮点数。 参数max_digits表示总位数。 参数decimal_places表示小数位数。 DateField[auto_now=False, auto_now_add=False]):日期。 TimeField:时间,参数同DateField。 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false。 参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false。 参数auto_now_add和auto_now是相互排斥的,组合将会发生错误。 DateTimeField:日期时间,参数同DateField。 FileField:上传文件字段。 ImageField:继承于FileField,对上传的内容进行校验,确保是有效的图片。
字段选项
每个字段有一些特有的参数,例如,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'
字段补充
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class 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 自定义无符号整数字段 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) - 二进制类型
补充:
ForeignKey.on_delete
ForeignKey.on_delete¶ 当一个ForeignKey 引用的对象被删除时,Django 默认模拟SQL 的ON DELETE CASCADE 的约束行为,并且删除包含该ForeignKey的对象。这种行为可以通过设置on_delete 参数来改变。例如,如果你有一个可以为空的ForeignKey,在其引用的对象被删除的时你想把这个ForeignKey 设置为空: user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL) on_delete 在django.db.models中可以找到的值有: CASCADE¶ 级联删除;默认值。 PROTECT¶ 抛出ProtectedError 以阻止被引用对象的删除,它是django.db.IntegrityError 的一个子类。 SET_NULL¶ 把ForeignKey 设置为null; null 参数为True 时才可以这样做。 SET_DEFAULT¶ ForeignKey 值设置成它的默认值;此时必须设置ForeignKey 的default 参数。 SET()¶ 设置ForeignKey 为传递给SET() 的值,如果传递的是一个可调用对象,则为调用后的结果。在大部分情形下,传递一个可调用对象用于避免models.py 在导入时执行查询: from django.conf import settings from django.contrib.auth import get_user_model from django.db import models def get_sentinel_user(): return get_user_model().objects.get_or_create(username='deleted')[0] class MyModel(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET(get_sentinel_user)) DO_NOTHING¶ Take no action. 如果你的数据库后端强制引用完整性,它将引发一个IntegrityError ,除非你手动添加一个ON DELETE 约束给数据库自动(可能要用到初始化的SQL)。
related_name #反向查询的别名,就可以代替 表名_set
limit_choices_to={} #用于选择时option只显示按条件过滤后的选项 #示例 limit_choices_to={'depart_id__in':[1008,1009]} limit_choices_to={'depart_id':1001,}
auto_now 与 auto_now_add
DateTimeField.auto_now 这个参数的默认值为false,设置为true时,能够在保存该字段时,将其值设置为当前时间,并且每次修改model,都会自动更新。因此这个参数在需要存储“最后修改时间”的场景下,十分方便。需要注意的是,设置该参数为true时,并不简单地意味着字段的默认值为当前时间,而是指字段会被“强制”更新到当前时间,你无法程序中手动为字段赋值;如果使用django再带的admin管理器,那么该字段在admin中是只读的。 DateTimeField.auto_now_add 这个参数的默认值也为False,设置为True时,会在model对象第一次被创建时,将字段的值设置为创建时的时间,以后修改对象时,字段的值不会再更新。该属性通常被用在存储“创建时间”的场景下。与auto_now类似,auto_now_add也具有强制性,一旦被设置为True,就无法在程序中手动为字段赋值,在admin中字段也会成为只读的。 auto_now_add = True用于创建时间 auto_now = True用于更新时间
db_index = True 表示设置索引 unique(唯一的意思) = True 设置唯一索引 联合唯一 class Meta: unique_together = ( ('email','ctime'), ) 联合索引(不做限制) index_together = ( ('email','ctime'), )
class Meta: verbose_name = "课程大类" #人类可读对象名 verbose_name_plural = "课程大类" #人类可读对象名复数
多级表关系以及参数
ForeignKey(ForeignObject) # ForeignObject(RelatedField) to, # 要进行关联的表名 to_field=None, # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 - 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(可执行对象) def func(): return 10 class MyModel(models.Model): user = models.ForeignKey( to="User", to_field="id" on_delete=models.SET(func),) related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') db_constraint=True # 是否在数据库中创建外键约束 parent_link=False # 在Admin中是否显示关联数据 OneToOneField(ForeignKey) to, # 要进行关联的表名 to_field=None # 要关联的表中的字段名称 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 ###### 对于一对一 ###### # 1. 一对一其实就是 一对多 + 唯一索引 # 2.当两个类之间有继承关系时,默认会创建一个一对一字段 # 如下会在A表中额外增加一个c_ptr_id列且唯一: class C(models.Model): nid = models.AutoField(primary_key=True) part = models.CharField(max_length=12) class A(C): id = models.AutoField(primary_key=True) code = models.CharField(max_length=1) ManyToManyField(RelatedField) to, # 要进行关联的表名 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: # 如: - limit_choices_to={'nid__gt': 5} - limit_choices_to=lambda : {'nid__gt': 5} from django.db.models import Q - limit_choices_to=Q(nid__gt=10) - limit_choices_to=Q(nid=8) | Q(nid__gt=10) - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段 # 做如下操作时,不同的symmetrical会有不同的可选字段 models.BB.objects.filter(...) # 可选字段有:code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=True) # 可选字段有: bb, code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=False) through=None, # 自定义第三张表时,使用字段用于指定关系表 through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表 from django.db import models class Person(models.Model): name = models.CharField(max_length=50) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField( Person, through='Membership', through_fields=('group', 'person'), ) class Membership(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.CASCADE) inviter = models.ForeignKey( Person, on_delete=models.CASCADE, related_name="membership_invites", ) invite_reason = models.CharField(max_length=64) db_constraint=True, # 是否在数据库中创建外键约束 db_table=None, # 默认创建第三张表时,数据库中表的名称
更多详见模型字段参考
一旦你建立好数据模型之后,django会自动生成一套数据库抽象的API,可以让你执行关于表记录的增删改查的操作。
创建命令
python manage.py makemigrations #生成文件
python manage.py migrate #创建表
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', }, } }
单表操作
查询记录API:
(1)Student.objects.all() #返回的QuerySet类型 查询所有记录 [obj1,obj2....]
(2)Student.objects.filter() #返回的QuerySet类型 查询所有符合条件的记录
(3)Student.objects.exclude()#返回的QuerySet类型 查询所有不符合条件的记录
(4)Student.objects.get() #返回的models对象 查询结果必须有且只有一个,否则报错
(5)Student.objects.all().first() #返回的models对象 查询结果集合中的第一个
(6)Student.objects.filter().last() #返回的models对象 查询结果集合中的最后一个
(7)Student.objects.all().values("name","class_id") #返回的QuerySet类型 ,列表中存放的字典
(8)Student.objects.all().values_list("name","class_id") #返回的QuerySet类型 ,列表中存放的元组
(9)Student.objects.all().order_by("class_id") # 按指定字段排序,不指定,按主键排序
(9.1)Student.objects.all().order_by("-class_id") # 按指定字段 降序 排序
(10)Student.objects.all().count() # 返回的记录个数 数字类型
(11)Student.objects.all().values("name").distinct() #字段去重,用在values() 后
(12)Student.objects.all().exist() #判断是否为空,如果QuerySet包含数据,就返回True,否则返回False 原理是limit 1
单表查询之双下划线 __
Student.objects.filter()
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值
lt 小于 gt大于
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") #包含ven 区分大小写
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and 什么到什么之间(包含最后一个)
startswith, #什么开头
istartswith, #什么开头,大小写不敏感
endswith, #什么结尾
iendswith #什么结尾,大小写不敏感
添加记录:
方式1:
s=Student(name='',birth='',class_id='')
s.save()
方式2:
stu_obj=Student.objects.create(name='',birth='',class_id='') # stu_obj是添加的记录对象
删除记录:
Student.objects.filter(nid=1).delete() # QuerySet类型调用
修改记录:
Student.objects.filter(nid=1).update(name="yuan") # QuerySet类型调用
补充:
only 和 defer
user_list = models.User.objects.all() # 相当于SQL语句 select * from user # user_list = QuerySet() = [obj(*),obj(*),obj(*) ] user_list = models.User.objects.all().only('id','name') # 只查询id,name这两个字段,不取所有 # select id,name from user # user_list = QuerySet() = [obj(id,name),obj(id,name),obj(id,name) ] # 如果取id,name之外的字段就会再自动走一遍查询,会降低效率 # for item in user_list: # item.id # item.age 重新走数据库 user_list = models.User.objects.all().defer('id','name') # 和only相反,不取这几个字段 user_list = models.User.objects.all().vlaues('id','name') # select id,name from user # user_list = QuerySet() = [{'id':1,'name':'老男孩'}, ] # for item in user_list: # item['id'] # item['name']
添加表记录
普通字段
方式1 publish_obj=Publish(name="人民出版社",city="北京",email="renMin@163.com") publish_obj.save() # 将数据保存到数据库 方式2 publish_obj=Publish.objects.create(name="人民出版社",city="北京",email="renMin@163.com") 方式3 表.objects.create(**request.POST.dict())
外键字段
方式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)
多对多字段
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(*[]) book_obj.authors.add(1,2) #可以直接放作者对象的id book_obj.authors.create() #创建并保存一个新对象,然后将这个对象加被关联对象的集合中,然后返回这个新对象。
解除关系
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 ====== book_obj.authors.remove(*[]) book_obj.authors.clear() #清空被关联对象集合。
book_obj.authors.set() #先清空再设置
基于对象的跨表查询: 子查询
一对多查询
# 查询nid=1的书籍的出版社所在的城市 book_obj=Book.objects.get(nid=1) print(book_obj.publish.city) # book_obj.publish 是nid=1的书籍对象关联的出版社对象
反向查询:(按表名book_set)
# 查询 人民出版社出版过的所有书籍 publish=Publish.objects.get(name="人民出版社") book_list=publish.book_set.all() # 与人民出版社关联的所有书籍对象集合 for book_obj in book_list: print(book_obj.title)
一对一查询
正向查询(按字段:authorDetail):
# 查询egon作者的手机号 author_egon=Author.objects.get(name="egon") print(author_egon.authorDetail.telephone)
反向查询(按表名:author):
# 查询所有住址在北京的作者的姓名 authorDetail_list=AuthorDetail.objects.filter(addr="beijing") for obj in authorDetail_list: print(obj.author.name)
多对多查询
正向查询 (按字段)
# 金瓶眉所有作者的名字以及手机号 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)
# 查询egon出过的所有书籍的名字 author_obj=Author.objects.get(name="egon") book_list=author_obj.book_set.all() #与egon作者相关的所有书籍 for book_obj in book_list: print(book_obj.title)
基于双下划线的跨表查询: join查询
关键点:正向查询按字段,反向查询按表明。
一对多
查询活着的出版社名称 ret=Book.objects.filter(title="金瓶没").values("publish__name") # <QuerySet [{'publish__name': '沙河出版社'}]> print(ret) Publish.objects.filter(book__title="金瓶没").values("name") 查询沙河出版社出版过的书籍名称 ret=Publish.objects.filter(name="人民出版社").values("book__title") print(ret) Book.objects.filter(publish__name="人民出版社").values("title")
多对多
查询活着3所有作者的名字 ret=Book.objects.filter(title="金瓶没4").values_list("authors__name") print(ret) 查询alex出版过的所有书籍 ret=Author.objects.filter(name="alex").values("book__title") print(ret) Book.objects.filter(authors__name="alex").values("title")
一对一
查询地址在烟台并且email是789的作者的名字 ret=AuthorDetail.objects.filter(addr="烟台",email=789).values("author__name") print(ret) email以456开头的作者出版过的所有书籍名称以及出版社名称
多联
email以456开头的作者出版过的所有书籍名称以及出版社名称 ret=Book.objects.filter(authors__authordetail__email__startswith="456").values("title","publish__name") print(ret)
聚合查询与分组查询
导入
from django.db.models import Avg,Sum,Count,Min,Max
聚合:aggregate(*args, **kwargs)
# 计算所有图书的平均价格 >>> from django.db.models import Avg >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35}
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。
键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
>>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35}
如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
>>> 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')}
分组:annotate()
为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
查询每一个出版社出版过的书籍个数 ret=Publish.objects.all().annotate(c=Count("book__title")) for pub_obj in ret: print(pub_obj.name,pub_obj.c) 查询每一本书的作者个数 ret=Book.objects.all().annotate(counts=Count("authors__id")).values("title","counts") print(ret) 查询每一个作者出版过的书籍的平均价格 ret=Author.objects.all().annotate(avgprice=Avg("book__price")).values("name","avgprice") print(ret) 查询各个作者出的书的总价格: # 按author表的所有字段 group by queryResult=Author.objects .annotate(SumPrice=Sum("book__price")) .values_list("name","SumPrice") print(queryResult) # 按authors__name group by queryResult2=Book.objects.values("authors__name") .annotate(SumPrice=Sum("price")) .values_list("authors__name","SumPrice") print(queryResult2) 统计每一本以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) 根据一本图书作者数量的多少对查询集 QuerySet进行排序: Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
F 与 Q 查询
F查询 (可对字段的值进行操作,int类型)
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 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 Q condition = Q() condition.connector = "or" #默认是and关系 condition.children.append(("title","linux")) condition.children.append(("price",100)) #Book.objects.filter(condition) 相当于 Book.objects.filter(Q(title="linux")|Q(price=100))
ContentType
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey,GenericRelation # Create your models here. class Course(models.Model): """ 普通课程 """ title = models.CharField(max_length=32) # 仅用于反向查找,不会生成真实的字段 pricepolicy_list = GenericRelation(to="PricePolicy") class DegreeCourse(models.Model): """ 学位课程 """ title = models.CharField(max_length=32) # 仅用于反向查找,不会生成真实的字段 pricepolicy_list = GenericRelation(to="PricePolicy") class PricePolicy(models.Model): """ 价格策略 """ price = models.IntegerField() period = models.IntegerField() content_type = models.ForeignKey(ContentType,verbose_name="关联的表名称") object_id = models.IntegerField(verbose_name="关联的表中的数据的行ID") # 帮助你快速实现content_type操作,不会生成真实的字段 content_obj = GenericForeignKey('content_type',"object_id") # 会自动生成一张ContentType表,里面存着所有的表名称和对应的app名 # 插入数据的两种方法 # 方法一: #插入表id和数据id # cobj = ContentType.objects.first(model="course").first() #找到对应的表 # obj = Course.objects.filter(title="python全栈").first() # 找到表里的对象 # PricePolicy.objects.create(price=99,period=30,content_type_id=cobj.id,object_id=obj.id) # # 方法二: #直接让content_obj等于一个数据对象 # PricePolicy.objects.create(price=99,period=30,content_obj=obj) #查询 # 3. 根据课程ID获取课程, 并获取该课程的所有价格策略 course = models.Course.objects.filter(id=1).first() price_policys = course.pricepolicy_list.all().values("price","period","content_type__model","object_id").distinct() print(price_policys)
与性能相关
注意: 使用Foreignkey,原因: - 约束 - 节省硬盘 大型程序: FK,不用原因: - 约束--> 自己代码判断 - 节省硬盘--> 可以浪费,因为要加速
链表查询速度会慢
1. select_related 主动进行链表查询
问题:查看所有用户,并打印用户姓名、年龄、用户所在部门名称。 会有11次查询: users = 用户表.objects.all() for row in users: row.name row.age row.dp.title #每执行一次链表查询一次 print(users.query) # 查看查询语句 select id,name,age,e,wpd from xxx 解决方法一: 使用values users = 用户表.objects.all().values('name','age','dp__title') for row in users: row['name'] row['age'] row['dp__title'] 解决方法二:使用select_related 指定链表 users = 用户表.objects.all().select_related('dp') for row in users: row.name row.age row.dp.title print(users.query) # select id,name,age,e,wpd from 用户表 join 部门表
2. prefetch_related 两次单表查询
2次单表查询 select * from users; # 将所有部门ID获取到:[1,2,3,4,8] # 再去部门表查询相应的部门 select * from department where id in [1,2,3,4,8] users = 用户表.objects.all().prefetch_related('dp') for row in users: row.name row.age row.dp.title
执行原生sql语句
1. connection, connections
from django.db import connection, connections cursor = connections["default"].cursor() cursor.execute("""select * from api_userinfo WHERE username=%s""",["zhou"]) row = cursor.fetchall() print(row) #[(1, '周军豪', 'zhou', '123', None)] return HttpResponse("ok")
################### 原生SQL ################### from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone() # fetchall()/fetchmany(..)
2. raw
def raw(self, raw_query, params=None, translations=None, using=None): # 执行原生SQL models.UserInfo.objects.raw('select * from userinfo') # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名 models.UserInfo.objects.raw('select id as nid from 其他表') # 为原生SQL设置参数 models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,]) # 将获取的到列名转换为指定列名 name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'} Person.objects.raw('SELECT * FROM some_other_table', translations=name_map) # 指定数据库 models.UserInfo.objects.raw('select * from userinfo', using="default")
3. extra
# mysql 用date_format(date,format) sqllist用strftime(format,date) c = models.Comment.objects.extra(select={'comment_data':"strftime('%%Y-%%m-%%d %%H:%%M:%%S',date)"}).values("comment_data") x = UserInfo.objects.extra(select={'new_id': "select username from api_userinfo where api_userinfo.id > %s"}, select_params=(1,)).values() y = UserInfo.objects.extra(where=['id=%s'], params=[1,]).values() z = UserInfo.objects.extra(where=["username='zhou' OR username = 'yu'", "name = '周军豪'"]).values()