Django ORM 学习笔记(一)

本文详细介绍了Django ORM中如何进行复杂查询,包括使用Q对象进行AND和OR操作,F()表达式的运用,以及聚合函数如Count、Avg、Max、Min、Sum的使用。同时,讲解了Django ORM的性能优势和如何进行数据库操作优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 一般在Django程序中查询数据库操作都是在QuerySet里进行的:

    q1 = Entry.objects.filter(headline__startswith="What")
    q2 = q1.exclude(pub_date__gte=datetime.date.today())
    q3 = q1.filter(pub_date__gte=datetime.date.today())
    
  2. 获取ORM对应的sql:

    代码:

     >>> queryset = Event.objects.all()
     >>> str(queryset.query)
     SELECT "events_event"."id", "events_event"."epic_id","events_event"."details", "events_event"."years_ago" FROM "events_event"
    
  3. multiple criteria【评价或做决定的标准,准则,原则】符合多条件【文盲笔记】

  4. 执行AND操作:

    1. filter(<condition_1>, <condition_2>)

    2. queryset_1 & queryset_2

    3. filter(Q(<condition_1>) & Q(<condition_2>))

      多个Q()对象之间的关系Django会自动理解成“且(and)”关系

      News.objects.get(
        Q(question__startswith='Who'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
      )
      # 对应的SQL语句可以理解为:
      SELECT * from news WHERE question LIKE 'Who%'  AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
      
  5. Django filter中使用or(Django中Q)

    filter(kwargs) //返回一个匹配查询参数的新的结果集.
    

filter(~Q(kwargs)) //返回一个不匹配查询参数的新的结果集.
exclude(kwargs) //返回一个不匹配查询参数的新的结果集.

将filter与or联系起来:
  from django.db.models import Q
  q=Q(question_startswith="What")
```
  1. 使用符号 & 或者 | 将多个Q()对象组合起来传递给filter(),exclude(),get()等函数。
    当多个Q()对象组合起来时,Django会自动生成一个新的Q():

    user.object.filter(Q(question__startswith='Who') | Q(question__startswith='What'))
    

【注意】查询表中question以‘who’或者‘what’开头的用户

```
Q(question__startswith='Who') | Q(question__startswith='What')
#这条对应的sql语句如下
WHERE question LIKE 'Who%' OR question LIKE 'What%'
```
  1. 可以在Q()对象前面使用字符“~”来表达“非”

    # 查询表中question以‘who’开头的用户或者pub_date不是2005年的数据
    Q(question__startswith='Who') | ~Q(pub_date__year=2005)
    
    # 对应SQL语句可以理解为:
    WHERE question like "Who%" OR year(pub_date) !=2005
    
  2. Q()对象可以结合关键字参数一起传递给查询函数,注意的是要将Q()对象放在关键字参数的前面

    #正确的做法                                                               
     News.objects.get(                           
       Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),    
       question__startswith='Who')                    
                                                                            
    #错误的做法,代码将关键字参数放在了Q()对象的前面。               
     News.objects.get(                           
       question__startswith='Who',                    
       Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
    
  3. Q()传入条件查询

    con = Q()
    
    q1 = Q()
    q1.connector = 'OR'
    q1.children.append(('name', "cox"))
    q1.children.append(('name', "Tom"))
    q1.children.append(('name', "Jeck"))
    
    
    q2 = Q()
    q2.connector = 'OR'
    q2.children.append(('age', 12))
    
    con.add(q1, 'AND')
    con.add(q2, 'AND')
    
    models.Author.objects.filter(con) # 在Author表中,name等于cox/Tom/Jeck的 并且 满足age等于12 的所有数据
    
  4. union组合来自相同或不同模型的两个查询集:
    union运算符用于组合两个或多个查询集的结果集。
    只有具有相同字段和数据类型的查询集才能执行union操作
    查询集可以来自相同或不同的模型。
    当它们的查询集来自不同的模型时,可截取部分字段,此时字段及其数据类型应该匹配。

  5. 使用F()表达式

    每次获取times当前的值,再+1,这样需要将times值取出,存到内存中

    obj = models.Test.objects.get(name="cox")
    obj.times = obj.times + 1
    obj.save()
    obj.save() 
    obj.save() 
    obj.save()
    # 如果times的值是1,那么经过n次save()之后,times的值是2
    
    1. 虽然 obj.times = F(“times”) + 1看起来像常规的Python为实例属性赋值,但实际上它是一个描述数据库上操作的SQL结构。

    2. 当Django遇到要给F()实例,它会覆盖标准的Python运算符来创建一个封装的SQL表达式;在这个例子中,指示数据库增加由 obj.times 表示的数据库字段。

    3. 无论 obj.times 的值是或曾是什么,Python永远不需要知道----完全由数据库来处理。Python通过Django的F()类做的所有事情仅是参考某个字段创建SQL语法来描述操作。

    obj = models.Test.objects.get(name="cox")
    obj.times = F("times") + 1
    obj.save()
    obj.save() 
    obj.save() 
    obj.save()
    # 如果times的值是1,那么经过n次save()之后,times的值是1+n,而不是2,就是因为F()操作在 obj.save() 后会持续存在
    

    【性能优势:

    - 直接在数据库中操作而不是Python
    - 减少一些操作所需的数据库查询次数】

  6. F()与filter的结合使用

    1. 获取表中收入(input_price)大于支出(output_price)的数据:

      models.Test.objects.filter(input_price__gt=F("output_price"))
      
    2. Django支持F()对象使用加、减、乘、除、取模和幂运算等算术操作,两个操作数可以是常数或F()对象

      models.Test.objects.filter(input_price__gt=F("output_price")*2)
      
      models.Test.objects.filter(input_price__gt=F("output_price")+F("output_price"))
      
    3. 在F()对象中使用双下划线标记来跨越关联关系。 带有双下划线的F()对象将引入任何需要的join 操作以访问关联的对象

      models.Test.objects.filter(authors__name=F('blog__name'))
      
    4. 对于date 和date/time 字段,你可以给它们加上或减去一个timedelta对象

      from datetime import timedelta
      models.Test.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
      

    参考文章

  7. 仅选择部分字段:

    1. queryset上的values和values_list方法
    >>> User.objects.filter(first_name__startswith='R').values('first_name', 'last_name')
    <QuerySet [{'first_name': 'Ricky', 'last_name': 'Dayal'}, {'first_name': 'Ritesh', 'last_name': 'Deshmukh'}, {'first_name': 'Radha', 'last_name': 'George'}, {'first_name': 'Raghu', 'last_name': 'Khan'}, {'first_name': 'Rishabh', 'last_name': 'Deol'}]
    
    >>> str(queryset.query)
    SELECT "auth_user"."first_name", "auth_user"."last_name"
    FROM "auth_user" WHERE "auth_user"."first_name"::text LIKE R%
    
    1. only方法【能够取到id】
    >> queryset = User.objects.filter(
        first_name__startswith='R'
    ).only("first_name", "last_name")
    
    >>> str(queryset.query)
    SELECT "auth_user"."id", "auth_user"."first_name", "auth_user"."last_name"
    FROM "auth_user" WHERE "auth_user"."first_name"::text LIKE R%
    
  8. Django的聚合函数和aggregate\annotate

    1. 聚合函数不能在Django中单独使用,要想在Django中使用这些聚合函数,就必须把这些聚合函数放到支持他们的方法内去执行。

    2. 支持聚合函数的方法有两种,分别是aggregateannotate,这两种方法执行的原生SQL以及结果都有很大的区别

聚合是aggreate(*args,**kwargs),通过QuerySet 进行计算做求值运算
分组是annotate(*args,**kwargs),括号里是分组条件遇到某条件的时候要分组
# 示例模型:
class Author(models.Model):
"""作者模型"""
name = models.CharField(max_length=100)
age = models.IntegerField()
email = models.EmailField() 
 
class Book(models.Model):
"""图书模型"""
name = models.CharField(max_length=100)
author = models.ForeignKey('Author',on_delete=models.CASCADE)
price = models.FloatField() 

class BookOrder(models.Model):
"""图书订单模型"""
book = models.ForeignKey('Book',on_delete=models.CASCADE)
sailprice = models.FloatField()
create_time = models.DateTimeField(auto_now_add=True)
  • aggregate:

    1. 是一个QuerySet对象的API,在执行聚合函数的时候,是对QuerySet整个对象的某个属性汇总,在汇总时不会使用该模型的主键进行group by进行分组,得到的是一个结果字典。

    2. 该方法支持聚合关联表(如使用ForeignKey)中的字段,在聚合连表中字段时,传递该字段的方式与查询连表时传递字段的方式相同,会使用到"__"。示例代码如下:

    from django.db.models import Avg
    from django.db import connection
    
    1. 对当前表中数据进行聚合:
    result = Author.objects.aggregate(avg_age=Avg('age'))
    print(connection.queries) # 打印执行时所有的查询语句
    
    1. 对连表中数据进行聚合:
    result = Book.objects.aggregate(sum=Sum('bookorder__price'))
    
  • annotate:

    1. 可以执行聚合函数,也可以传递F、Q对象为当前QuerySet生成一个新的属性

    2. 这个方法一般聚合的是连表中的字段,会为当前QuerySet中的每个对象生成一个独立的摘要,为查询的模型增加一个新的属性,这个属性的值就是使用聚合函数所得到的值。

    3. 在使用这个聚合函数的时候annotate会使用这个模型的主键进行group by进行分组(注意这里只有在使用聚合函数生成新字段的时候会进行group by,在使用F、Q表达式增添新字段时,并不会使用group by),然后在连表中根据分组的结果进行聚合,这一点正符合为QuerySet中每个对象增加一个独立摘要的事实。

    4. 使用这个annotate方法执行聚合函数,得到的结果是一个QuerySet对象,结果依然能够调用filter()、order_by()甚至annotate()进行再次聚合,现在我想提取每一本书的平均销售的价格(注意销售价格在BookOrder表中):

    from django.db.models import Avg
    from django.db import connection 
    
    books = Book.objects.annotate(avg=Avg('bookorder__sailprice'))
    for book in books:
       print('%s/%s'%(book.name,book.avg)) # 注意这里的avg属性就是annotate执行聚合函数得到的
       print(connection.queries)
    
  • 聚合函数:

    在Django中,聚合函数都是在django.db.models模块下的,具体的聚合函数有Avg、Count、Max、Min、Sum,现在我们一一介绍这些函数的作用:

    1. Avg:计算平均值,使用于与数值相关的字段,如果使用aggregate方法来执行这个函数,那么会得到一个字典,默认情况下,字典的键为field__avg,值为执行这个聚合函数所得到的值,示例代码如下:
    # 计算所有作者的平均年龄
    result = Author.objects.aggregate(Avg('age'))
    print(result) # 结果为:{"age__avg": 23.8}
    
    # 如果想要使用自定义的键,那么可以把aggregate中的未知参数变为关键字参数,该关键字就是得到的键,示例代码如下:
    result = Author.objects.aggregate(avgAge=Avg('age'))
    print(result) # 结果为:{"avgAge": 23.8} 
    
    # 如果使用annotate方法执行这个函数,那么得到的结果就是一个QuerySet对象,只不过这个对象中的每一个都会添加一个属性,这个属性的名称其实和上面的键一样,可以使用默认也可以自定义,使用方法与在aggregate中键名的定义一样,这里就不再赘述: 
    books = Book.objects.annotate(avg=Avg('bookorder__sailprice'))
    for book in books:
      print('%s/%s'%(book.name,book.avg)) # 注意这里的avg属性就是annotate执行聚合函数得到的
      print(connection.queries)
    

    2、Count:计算数量,基本用法与Avg相同,在使用这个聚合函数的时候可以传递一个distinct参数用来去重:

    # 计算总共有多少个订单
    result = BookOrder.objects.aggregate(total=Count('id',distanct=True))
    print(result) # 结果为:{"total": 18}
    
    # 计算每本书的订单量
    books = Book.objects.annotate(total=Count('bookorder__id'))
    for book in books:
      print('%s/%s'%(book.name,book.total))
    

    3、Max和Min:计算某个字段的最大值和最小值,用法与Avg一样

    4、Sum:计算总和,用法与Avg一样

    总结一下:

    • 使用aggregate时,是对QuerySet整个对象的某个属性汇总聚合,不会使用分组。
    • 而使用annotate方法时,是为QuerySet中的每个对象生成一个独立的摘要,一定会使用分组,然后再聚合。
  1. 复杂查询:

    字段含义
    exclude()与filter相反
    first()返回第一个
    last()返回最后一个
    exists()是否存在
    count()数量
    order_by()按字段排序
    __contains包含
    __icontains包含,不分大小写
    __lt,__gt,__lte,__gte小于,大于,小于等于,大于等于
    __range范围
    __in是否在范围内
    Q逻辑组合

    高级查询:

    1. in 通过 字段名_in=[1,2]查询
    res = models.Student.objects.filter(age__in=[12,14,42]).all()
    
    1. not in 通过exclude(字段名__in = [1,2]) exclude就是除了的意思

      res = models.Student.objects.exclude(age__in=[12,14,42]).all()
      
    2. like 通配查询

      # where name like "李%" 
      # 代表查询名字以李开头的所有  
      # name__istartswith 表示不区分大小写,以什么什么为开头  
      res = models.Student.objects.filter(name__startswith="李").all()
      
      # where name like "%白" 
      # 表示匹配以白结尾的所有单词  
      # name__iendswith  表示不区分大小写,以什么什么为结尾  
      res = models.Student.objects.filter(name__endswith="白").all()
      
      # where name like "%小%" 表示匹配中间有 小 字的所有单词  
      # name__icontains  表示不区分大小写,包含什么什么的单词  
      res = models.Student.objects.filter(name__contains="小").all() 
      
    3. between … and… 通过 列名__range = [开始位置,结束位置] 闭区间

      res = models.Student.objects.filter(id__range=[2,5]).all()
      
    4. limit 通过索引进行切片[10:20] 代表从10取到20 前闭后开

      res = models.Student.objects.all()[5:7]
      
    5. order by 通过order by 方法, 前面加"-"就是降序,默认升序

      res = models.Student.objects.order_by("id").all()
      # 可以多次排序 先按age字段降序,如果age相同,就按id字段升序  
      res = models.Student.objects.order_by("-age","id").all()
      
    6. group by

      \# 分组需要用到django中的方法  
      
      from django.db.models import Count, Max, Min, Sum  
      
      res = models.Student.objects.values("name").annotate(xxx=Count("id"))  
      
      \# 表示的是搜索name字段和对表的id字段计算count,并起了别名为xxx  通过name字段进行分组
      
      \# SELECT "app01_student"."name", COUNT("app01_student"."id") AS "xxx" FROM "app01_student" GROUP BY "app01_student"."name"
      
    7. only 只取某列的值

      res = models.Student.objects.only("name","age").all()  
      # 取出来是QuerySet 列表里面包含对象,对象中包含name字段的值以及id列的值  
      # 和values取值的区别就是only取出来的是列表套对象,values取的是列表套字典  
      # 并且,不管你only中是否包含id字段,他都会把你的id字段一并取出来
      
    8. defer 取出除了某列以外其他列的值

      res = models.Student.objects.defer("id","name").all()  
      # 取出来的也是列表套对象,并且,不管你里面有没有id字段,都会把你id列的值取出来
      
    9. using

      # 我们在配置数据库时,会把数据库添加到DATABASES 中 
      # DATABASES = {  
      #   'default': {  
      #     'ENGINE': 'django.db.backends.sqlite3',  
      #     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),  
      #   }  
      # }  
      # 如果里面连有多个数据库,就可以通过using来确定使用哪一个数据库  
      
      res =models.Student.objects.all().using("default")
      
    10. 查询表中一共有多少条数据

      res = models.Student.objects.count()
      
    11. 第一条数据

      res = models.Student.objects.first()
      
    12. 最后一条数据

      res = models.Student.objects.last()
      
    13. gt 大于 lt 小于 gte 大于等于 lte 小于等于

      res = models.Student.objects.filter(id__gt=8).all()
      res = models.Student.objects.filter(id__lte=8).all()
      
    14. and 操作 , 在filter中用逗号隔开就是and

      res = models.Student.objects.filter(id=2,age=23)  
      # 返回的还是QuerySet 类型
      
    15. or 操作

      # or 需要从django中导入Q方法  
      from django.db.models import Q  
      res = models.Student.objects.filter(Q(id=2) | Q(age=12))
      res = models.Student.objects.filter(Q(Q(id=2) | Q(age=12)) & Q(name="李铁柱"))  
      #  | 表示 或  & 表示 与
      
    16. 在原有的基础上更新,比如我想让某一列的值整体 + 1

      # 需要从django中导入F方法  
      from django.db.models import F  
      models.Student.objects.update(F("age") + 1)  # 这样age字段就会全部 + 1
      
    17. 原生sql语句

      # django也给我们提供了写原生sql的方法  
      from django.db import connection  # 导入连接  
      cursor = connection.cursor()  # 生成cursor对象  
      cursor.execute()   # 可以提交sql语句,也可以传参数  
      cursor.fetchall()  # 拿取所有的结果
      cursor.fetchone()  # 拿取一条结果  
      models.Student.objects.raw()  # 这个方法也可以提交sql语句,不建议使用
      
  2. 执行python manage.py migrate后数据库中默认生成的表的名称为 “应用名”+“_”+"模型类名"

    例如booktest_bookinfobooktest_heroinfo 分别代表booktest应用中的bookinfo类和heroinfo类,注意此时数据库表名中的所有字母为小写

  3. 修改数据:

    更新:

    c = Demo.objects.get(id=1) //单条更新
    c.op = 123 //更新
    c.save() //操纵完后,保存
     
    等价于:Demo.object.filter(id=1).update(name=‘Guangzhou ’,address=‘guangdong’)   //批量更新
    

    创建:

    # 一次增加一条数据
    models.Student.objects.create(name="xxx",age=12)
    
    # 一次增加多条数据
    obj = [
      models.Student(name="qqq",age=12),
    models.Student(name="aaa",age=32),
      models.Student(name="www",age=21)
    ]
    models.Student.objects.bulk_create(obj)
    

    删除:

    c = Demo.objects.get(id=1)            //单条删除
    

c.delete()
Demo.object.filter(id=1).delete() //批量删除
models.Student.objects.filter(name=“xxx”).delete()

# 如果是单表删除没有问题,但如果这张表的主键和别的表建立了外键关系,删除这条数据,另一张表对应的数据也会被删除掉
# 这也称为级联删除,如果我们不需要这样,可以在建外键是给on_delete赋值
# on_delete 几个参数的含义
# CASCADE 默认值  级联删除
# SET_NULL 取消级联删除,如果被关联外键的一条数据删除,关联的对应值用NULL代替,所以需要支持NULL
# SET_DEFAULT 取消级联删除,被删除就用默认值代替

class Student(models.Model): 
  name = models.CharField(max_length=32) 
  teac = models.ForeignKey('Teacher', null=True, on_delete=models.SET_NULL)

小技巧:
    python的函数接收参数test(a=1,b=2)可以写成test(**{‘a’:1,’b’:2})以字典的形式接收
    Demo.objects.create(**{‘id’:12,’op’:321})
    Demo.objects.get(id=1).update(**{‘id’:12,’op’:321})
```
  1. 外键查询:一对多

    from django.db import models
     
    class Owner(models.Model):
      name = models.CharField(u'名称',max_length=10)
     
    class Computer(models.Model):
      cname = models.CharField(u'电脑名',max_length=10)
      owner = models.ForeignKey(Owner)
    
    # 一找多
    owner_obj = Owner.objects.get(id=1)
    owner_obj.computer_set.all()
    
    # 多找一
    com_obj = Computer.objects.get(id=1)
    com_obj.owner.name
    
    # 复杂查询
    

Computer.objects.filter(owner__name=‘test’)

复杂查询

Owner.objects.filter(computer__cname='demo')

# 增
owner_obj = Owner.objects.get(id=1)

owner_obj.computer_set.create(name=‘test’)
Computer.objects.create(name=‘test’,owner_id=‘1’)
Computer.objects.create(name=‘test’,owner=owner_obj)

# 删除
owner_obj = Owner.objects.get(id=1)
owner_obj.computer_set.all().delete()
Computer.objects.filter(owner_id='1').delete()
Computer.objects.filter(owner=owner_obj).delete()
```
  1. 外键查询:多对多

    # 创建两张表
    from django.db import models
     
    class User(models.Model):
      username = models.CharField(u'名称',max_length=20)
      age = models.IntegeraField()
      sex = models.BooleanField()
      phone = models.CharField(u'电话号码',max_length=30)
     
    class Book(models.Model):
      name = models.CharField(u'电脑名',max_length=40)
      price = models.IntegeraField()
      owner = models.ManyToMany(User)  # 定义了多对多的关系
    

    生成三张表:

    home_application_book
    home_application_book_user
    home_application_user
    

    其中User表如下:(数据手动填)
    在这里插入图片描述
    其中Book表:(数据手动填)
    在这里插入图片描述
    其中Book_User表:(手动填)
    在这里插入图片描述

    多对多查询:

    # 查询
    owner_obj = Owner.objects.get(id=1)
    print owner_obj.computer_set.all().values()
    com_obj = Computer.objects.get(id=1)
    print com_obj.owner.all().values()
    
    # 添加关系
    owner_obj.computer_set.add(com_obj)
    com_obj.owner.add(owner_obj)
    
    # 删除关系
    owner_obj.computer_set.remove(com_obj)
    owner_obj.computer_set.clear()
    com_obj.owner.remove(owner_obj)
    com_obj.owner.clear()
    

    多对多查询实例:
    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值