python-django_多表操作_聚合查询_分组查询_F和Q查询_原生sql_defer和only_事务

13 篇文章 0 订阅

django 多表操作

1. 聚合查询

# 聚合查询(聚合函数:最大,最小,和,平均,总个数)aggregate
from django.db.models import Avg,Max,Min,Count,Sum

1. 计算所有图书的平均价格
    # aggregate结束,已经不是queryset对象了
    book=models.Book.objects.all().aggregate(Avg('price'))
    # 起别名
    book=models.Book.objects.all().aggregate(avg=Avg('price'))
    
2. 计算总图书数
    book = models.Book.objects.all().aggregate(count=Count('id'))
    
3. 计算最低价格的图书
    book = models.Book.objects.all().aggregate(min=Min('price'))
    
4. 计算最大价格图书
    book = models.Book.objects.all().aggregate(max=Max('price'))
    print(book)

2. 分组查询

# annotate() 内写聚合函数
'''
       查询每一个部门名称以及对应的员工数
       book:
       id  name   price      publish
        1   金品   11.2        1
        2   西游   14.2        2
        3   东游   16.2        2
        4   北邮   19.2        3    
'''

'''
values在前表示group by的字段
values在后表示取某几个字段
filter在前表示where
filter在后表示having
''' 

from django.db.models import Avg, Count, Max, Min
# 1. 查询每一个出版社id,以及出书平均价格
# 原生sql: select publish_id,avg(price) from app01_book group by publish_id;

ret=models.Book.objects.values('publish_id').annotate(avg=Avg('price')).values('publish_id','avg')
print(ret)

# 2. 查询出版社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)

# 3. 查询出版社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)

# 4. 查询每一个出版社出版的书籍个数  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)

# 5. 查询每个作者的名字,以及出版过书籍的最高价格(建议使用分组的表作为基表)
# 如果不用分组的表作为基表,数据不完整可能会出现问题
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)

# 6. 查询每一个书籍的名称,以及对应的作者个数  
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)

# 7. 统计不止一个作者的图书
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)

# 8. 统计价格数大于10元,作者的图书
ret=models.Book.objects.values('pk').filter(price__gt=10).annotate(count=Count('authors__id')).values('name','count')
print(ret)

# 9. 统计价格数大于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)

3. pk 主键

# 主键 (pk) 查询快捷方式
	出于方便的目的,Django 提供了一种 pk 查询快捷方式, pk 表示主键 "primary key"

# 示例 
	Blog 模型中,主键是 id 字段,所以这 3 个语句是等效的:
        Blog.objects.get(id__exact=14) # Explicit form
        Blog.objects.get(id=14)        # __exact is implied
        Blog.objects.get(pk=14)        # pk implies id__exact
        
# pk 的使用并不仅限于 __exact 查询 任何的查询项都能接在 pk 后面,执行对模型主键的查询:
    # Get blogs entries with id 1, 4 and 7
    	Blog.objects.filter(pk__in=[1,4,7])

    # Get all blog entries with id > 14
    	Blog.objects.filter(pk__gt=14)
        
# pk 查找也支持跨连接。例如,以下 3 个语句是等效的:
    Entry.objects.filter(blog__id__exact=3) # Explicit form
    Entry.objects.filter(blog__id=3)        # __exact is implied
    Entry.objects.filter(blog__pk=3)        # __pk implies __id__exact

4. 在 LIKE 语句中转义百分号和下划线

# 简介
    在 LIKE 语句中转义百分号和下划线 
    等效于
    LIKE SQL 语句的字段查询子句
    (iexact, contains, icontains, startswith, istartswith, endswith 和 iendswith) 
    会将 LIKE 语句中有特殊用途的两个符号,即百分号和下划线自动转义
    在 LIKE 语句中,百分号匹配多个任意字符,而下划线匹配一个任意字符
	这意味着事情应该直观地工作,这样抽象就不会泄露
    
# 例如,要检索所有包含百分号的条目,就像对待其它字符一样使用百分号:
	Entry.objects.filter(headline__contains='%')

# Django 为你小心处理了引号;生成的 SQL 语句看起来像这样:
	SELECT ... WHERE headline LIKE '%\%%';

5. 检索对象

# 检索对象
要从数据库检索对象,要通过模型类的 Manager 构建一个 QuerySet

    一个 QuerySet 代表来自数据库中对象的一个集合
    它可以有 0 个,1 个或者多个 filters
    Filters,可以根据给定参数缩小查询结果量
    在 SQL 的层面上, QuerySet 对应 SELECT 语句,而filters对应类似 WHERE 或 LIMIT 的限制子句
	可以通过模型的 Manager 获取 QuerySet
    
# 每个模型至少有一个 Manager,默认名称是 objects。像这样直接通过模型类使用它:
    >>> Blog.objects
    <django.db.models.manager.Manager object at ...>
    >>> b = Blog(name='Foo', tagline='Bar')
    >>> b.objects
    Traceback:
        ...
    AttributeError: "Manager isn't accessible via Blog instances."
# 注意
	Managers 只能通过模型类访问,而不是通过模型实例,目的是强制分离 “表级” 操作和 “行级” 操作

# Manager 是模型的 QuerySets 主要来源
	例如 Blog.objects.all() 返回了一个 QuerySet,后者包含了数据库中所有的 Blog 对象

# 检索全部对象
	从数据库中检索对象最简单的方式就是检索全部。为此,在 Manager 上调用 all() 方法:
		>>> all_entries = Entry.objects.all()
        
	方法 all() 返回了一个包含数据库中所有对象的 QuerySet 对象

# 通过过滤器检索指定对象
all() 
	返回的 QuerySet 包含了数据表中所有的对象。虽然,大多数情况下,你只需要完整对象集合的一个子集。
	要创建一个这样的子集,你需要通过添加过滤条件精炼原始 QuerySet。

# 两种最常见的精炼 QuerySet 的方式是:
filter(**kwargs)
	返回一个新的 QuerySet,包含的对象满足给定查询参数。
    
exclude(**kwargs)
	返回一个新的 QuerySet,包含的对象不满足给定查询参数。
    
# 查询参数(**kwargs)应该符合下面的 Field lookups 的要求。
# 例如,要包含获取 2006 年的博客条目(entries blog)的 QuerySet,像这样使用 filter():
	Entry.objects.filter(pub_date__year=2006)
    
# 通过默认管理器类也一样:
	Entry.objects.all().filter(pub_date__year=2006)
    
# 链式过滤器
	精炼 QuerySet 的结果本身还是一个 QuerySet,所以能串联精炼过程。
    # 示例:
        >>> Entry.objects.filter(
        ...     headline__startswith='What'
        ... ).exclude(
        ...     pub_date__gte=datetime.date.today()
        ... ).filter(
        ...     pub_date__gte=datetime.date(2005, 1, 30)
        ... )
		这个先获取包含数据库所有条目(entry)的 QuerySet,然后排除一些,再进入另一个过滤器。
        最终的 QuerySet 包含标题以 "What" 开头的,发布日期介于2005130日与今天之间的所有条目。

# 每个 QuerySet 都是唯一的
	每次精炼一个 QuerySet,你就会获得一个全新的 QuerySet,后者与前者毫无关联。
    每次精炼都会创建一个单独的、不同的 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())
	这三个 QuerySets 是独立的。
    第一个是基础 QuerySet,包含了所有标题以 "What" 开头的条目。
    第二个是第一个的子集,带有额外条件,排除了 pub_date 是今天和今天之后的所有记录。
    第三个是第一个的子集,带有额外条件,只筛选 pub_date 是今天或未来的所有记录。
    最初的 QuerySet (q1) 不受筛选操作影响。

# QuerySet 是惰性的
	QuerySet 是惰性的 创建 QuerySet 并不会引发任何数据库活动。
    你可以将一整天的过滤器都堆积在一起,Django 只会在 QuerySet 被 计算 时执行查询操作。
    # 示例:
        >>> q = Entry.objects.filter(headline__startswith="What")
        >>> q = q.filter(pub_date__lte=datetime.date.today())
        >>> q = q.exclude(body_text__icontains="food")
        >>> print(q)
	虽然这看起来像是三次数据库操作,实际上只在最后一行 (print(q)) 做了一次。
    一般来说, QuerySet 的结果直到你 “要使用” 时才会从数据库中拿出。
    当你要用时,才通过数据库 计算 出 QuerySet。
    关于何时才真的执行计算的更多细节,参考 When QuerySets are evaluated。

# 用 get() 检索单个对象
	filter()总是返回一个QuerySet,即便只有一个对象满足查询条件,这种情况下,QuerySet只包含了一个元素
	若你知道只会有一个对象满足查询条件,你可以在 Manager 上使用 get() 方法,它会直接返回这个对象
		>>> one_entry = Entry.objects.get(pk=1)
    
# 可以对 get() 使用与 filter() 类似的所有查询表达式 —— 同样的,参考下面的 Field lookups

# 注意
	使用切片 [0] 时的 get()filter() 有点不同。
    如果没有满足查询条件的结果,get() 会抛出一个DoesNotExist异常,该异常是执行查询的模型类的一个属性
    所有,上述代码中,若没有哪个 Entry 对象的主键是 1,Django 会抛出 Entry.DoesNotExist。
	类似,Django 会在有不止一个记录满足 get() 查询条件时发出警告。
    这时,Django 会抛出 MultipleObjectsReturned,这同样也是模型类的一个属性。

# 其它 QuerySet 方法
	大多数情况下,你会在需要从数据库中检索对象时使用 all(), get()filter() 和 exclude()。
    然而,这样远远不够;完整的各种 QuerySet 方法请参阅 QuerySet API 参考。

# 限制 QuerySet 条目数
	利用 Python 的数组切片语法将 QuerySet 切成指定长度。这等价于 SQL 的 LIMIT 和 OFFSET 子句。

# 示例 
# 这将返回前 5 个对象 (LIMIT 5):
	>>> Entry.objects.all()[:5]
# 这会返回第 6 至第 10 个对象 (OFFSET 5 LIMIT 5):
	>>> Entry.objects.all()[5:10]
# 不支持负索引 (例如 Entry.objects.all()[-1])

# 一般情况下, QuerySet 的切片返回一个新的 QuerySet —— 其并未执行查询。
# 一个特殊情况是使用了的 Python 切片语法的 “步长”。
# 例如,这将会实际的执行查询命令,为了获取从前 10 个对象中,每隔一个抽取的对象组成的列表:
		>>> Entry.objects.all()[:10:2]
# 由于对 queryset 切片工作方式的模糊性,禁止对其进行进一步的排序或过滤。
    
# 要检索单个对象而不是一个列表时(例如 SELECT foo FROM bar LIMIT 1),请使用索引,而不是切片。
# 例如,这会返回按标题字母排序后的第一个 Entry:
		>>> Entry.objects.order_by('headline')[0]
# 这大致等价于:
		>>> Entry.objects.order_by('headline')[0:1].get()
# 注意
	若没有对象满足给定条件,前者会抛出 IndexError,而后者会抛出 DoesNotExist。

# 字段查询
	字段查询即你如何制定 SQL WHERE 子句。
    它们以关键字参数的形式传递给 QuerySet 方法 filter(), exclude() 和 get()# 基本的查询关键字参数遵照 field__lookuptype=value。(有个双下划线)。例如:
	>>> Entry.objects.filter(pub_date__lte='2006-01-01')
# 转换为 SQL 语句大致如下:
	SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
# 这是怎么做到的
	Python 能定义可接受任意数量 name-value 参数的函数,参数名和值均在运行时计算。更多信息,请参考官方 Python 教程中的 Keyword Arguments。

	查询子句中指定的字段必须是模型的一个字段名。
    不过也有个例外,在 ForeignKey 中,你可以指定以 _id 为后缀的字段名。
    这种情况下,value 参数需要包含 foreign 模型的主键的原始值。例子:
		>>> Entry.objects.filter(blog_id=4)
	若你传入了无效的关键字参数,查询函数会抛出 TypeError。

# 数据库API支持两套查询类型;完整参考文档位于 字段查询参考。为了让你了解能干啥,以下是一些常见的查询:
1. exact
	"exact" 匹配的例子:
		>>> Entry.objects.get(headline__exact="Cat bites dog")
	会生成这些 SQL:
		SELECT ... WHERE headline = 'Cat bites dog';
# 若你未提供查询类型 —— 也就说,若关键字参数未包含双下划线 —— 查询类型会被指定为 exact。

# 例如,以下两条语句是等价的:
    >>> Blog.objects.get(id__exact=14)  # Explicit form
    >>> Blog.objects.get(id=14)         # __exact is implied
# 这是为了方便,因为 exact 查询是最常见的。

2. iexact
	不分大小写的匹配,查询语句:
		>>> Blog.objects.get(name__iexact="beatles blog")
	# 会匹配标题为 "Beatles Blog", "beatles blog", 甚至 "BeAtlES blOG" 的 Blog。

3. contains
	大小写敏感的包含测试。例子:
		Entry.objects.get(headline__contains='Lennon')
	粗略地转为 SQL:
		SELECT ... WHERE headline LIKE '%Lennon%';
	# 注意这将匹配标题 'Today Lennon honored',而不是 'today lennon honored'。
4. icontains
	这也有个大小写不敏感的版本, icontains。
    
5. startswith, endswith
	以……开头和以……结尾的查找。
    当然也有大小写不敏感的版本,名为 istartswith 和 iendswith。
	同样,这只介绍了皮毛。完整的参考能在 field 查询参考 找到

6. 跨关系查询

​ Django 提供了一种强大而直观的方式来“追踪”查询中的关系,在幕后自动为你处理 SQL JOIN关系。为了跨越关系,跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段。

本例检索出所有的 Entry 对象,其 Blog 的 name'Beatles Blog'

>>> Entry.objects.filter(blog__name='Beatles Blog')

跨域的深度随你所想。

​ 反向操作也能行。尽管可以自定义,但默认情况下,你使用模型的小写名称在查找中引用“反向”关系。

本例检索的所有 Blog 对象均拥有一个 标题 含有 'Lennon' 的条目:

>>> Blog.objects.filter(entry__headline__contains='Lennon')

​ 如果你在跨多个关系进行筛选,而某个中间模型的没有满足筛选条件的值,Django 会将它当做一个空的(所有值都是 NULL)但是有效的对象。这样就意味着不会抛出错误。例如,在这个过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

​ (假设有个关联的 Author 模型),若某项条目没有任何关联的 author,它会被视作没有关联的 name,而不是因为缺失 author 而抛出错误。大多数情况下,这就是你期望的。唯一可能使你迷惑的场景是在使用 isnull 时。因此:

Blog.objects.filter(entry__authors__name__isnull=True)

​ 将会返回 Blog 对象,包含 authorname 为空的对象,以及那些 entryauthor 为空的对象。若你不想要后面的对象,你可以这样写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

7. 跨多值关联

​ 当基于 ManyToManyField 或反向 ForeignKey 筛选某对象时,你可能对两种不同类型的过滤器感兴趣。假设 Blog/Entry 关联关系(BlogEntry 是一种一对多关联关系)。我们可能对那些条目标题同时含有 “Lennon” 且发布于 2008 年的博客感兴趣。或者,我们可能想找到那些条目标题包含 “Lennon” 或发布于 2008 的博客。由于多个 Entry 能同时关联至一个 Blog,两种查询都是可行的,且在某些场景下非常有用。

​ 同样的场景也发生在 ManyToManyField。例如,若有个 Entry 拥有一个叫做 tags ManyToManyField,要从关联至 tags 中的条目中找到名为 “music”“bands” 的条目,或要找到某个标签名为 “music” 且状态为 “public” 的条目。

​ 要处理这两种情况,Django 有一套统一的方式处理 filter() 调用。配置给某次 filter()的所有条件会在调用时同时生效,筛选出满足条件的项目。连续的 filter() 调用进一步限制了对象结果集,但对于多值关联来说,限制条件作用于链接至主模型的对象,而不一定是那些被前置 filter() 调用筛选的对象。

这听起来可能有点迷糊,所以需要一个例子来解释一下。要筛选出所有关联条目同时满足标题含有 “Lennon” 且发布于 2008 (同一个条目,同时满足两个条件)年的博客,我们会这样写:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

要筛选所有条目标题包含 “Lennon” 或条目发布于 2008 年的博客,我们会这样写:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

假设只有一个博客,拥有的条目同时满足标题含有 “Lennon” 且发布于 2008 年,但是发布于 2008 年的条目的标题均不含有 “Lennon”。第一项查询不会返回任何博客,而第二项查询会返回此博客。

在第二个例子中,第一个过滤器限制结果集为那些关联了标题包含 “Lennon” 的条目的博客。第二个过滤器进一步要求结果集中的博客要发布于 2008 年。第二个过滤器筛选的条目与第一个过滤器筛选的可能不尽相同。我们是用过滤器语句筛选 Blog,而不是 Entry

filter() 的查询行为会跨越多值关联,就像前文说的那样,并不与 exclude() 相同。相反,一次 exclude() 调用的条件并不需要指向同一项目。

例如,以下查询会排除那些关联条目标题包含 “Lennon” 且发布于 2008 年的博客:

Blog.objects.exclude(
    entry__headline__contains='Lennon',
    entry__pub_date__year=2008,
)

但是,与 filter() 的行为不同,其并不会限制博客同时满足这两种条件。要这么做的话,也就是筛选出所有条目标题不带 “Lennon” 且发布年不是 2008 的博客,你需要做两次查询:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains='Lennon',
        pub_date__year=2008,
    ),
)

8. 缓存和 QuerySet

# 简介
	每个 QuerySet 都带有缓存,尽量减少数据库访问。理解它是如何工作的能让你编写更高效的代码
	新创建的 QuerySet 缓存是空的。一旦要计算 QuerySet 的值,就会执行数据查询,随后,Django 就会将查询结果保存在 QuerySet 的缓存中,并返回这些显式请求的缓存(例如,下一个元素,若 QuerySet 正在被迭代)。后续针对 QuerySet 的计算会复用缓存结果。
	牢记这种缓存行为,在你错误使用 QuerySet 时可能会被它咬一下。
    
    # 例如,以下会创建两个 QuerySet,计算它们,丢掉它们:
        >>> print([e.headline for e in Entry.objects.all()])
        >>> print([e.pub_date for e in Entry.objects.all()])
	这意味着同样的数据库查询会被执行两次,实际加倍了数据库负载。
    同时,有可能这两个列表不包含同样的记录,因为在两次请求间,可能有 Entry 被添加或删除了。
	
    # 要避免此问题,保存 QuerySet 并复用它:
        >>> queryset = Entry.objects.all()
        >>> print([p.headline for p in queryset]) # Evaluate the query set.
        >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

# 当 QuerySet 未被缓存时
	查询结果集并不总是缓存结果。
    当仅计算查询结果集的 部分 时,会校验缓存,若没有填充缓存,则后续查询返回的项目不会被缓存。
    特别地说,这意味着使用数组切片或索引的 限制查询结果集 不会填充缓存。

# 例如,重复的从某个查询结果集对象中取指定索引的对象会每次都查询数据库:
    >>> queryset = Entry.objects.all()
    >>> print(queryset[5]) # Queries the database
    >>> print(queryset[5]) # Queries the database again

# 不过,若全部查询结果集已被检出,就会去检查缓存:
    >>> queryset = Entry.objects.all()
    >>> [entry for entry in queryset] # Queries the database
    >>> print(queryset[5]) # Uses cache
    >>> print(queryset[5]) # Uses cache

# 以下展示一些例子,这些动作会触发计算全部的查询结果集,并填充缓存的过程:
    >>> [entry for entry in queryset]
    >>> bool(queryset)
    >>> entry in queryset
    >>> list(queryset)

# 注意
	只是打印查询结果集不会填充缓存。因为调用 __repr__() 仅返回了完整结果集的一个切片

9. 过滤器可以为模型指定字段

1. F查询

在之前的例子中,我们已经构建过的 filter 都是将模型字段值与常量做比较。但是,要怎么做才能将模型字段值与同一模型中的另一字段做比较呢?

Django 提供了 F 表达式 实现这种比较。 F() 的实例充当查询中的模型字段的引用。这些引用可在查询过滤器中用于在同一模型实例中比较两个不同的字段。

例如,要查出所有评论数大于 pingbacks 的博客条目,我们构建了一个 F() 对象,指代 pingback 的数量,然后在查询中使用该 F() 对象:

>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))

Django 支持对 F() 对象进行加、减、乘、除、求余和次方,另一操作数既可以是常量,也可以是其它 F() 对象。要找到那些评论数两倍于 pingbacks 的博客条目,我们这样修改查询条件:

>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)

要找出所有评分低于 pingback 和评论总数之和的条目,修改查询条件:

>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks'))

你也能用双下划线在 F() 对象中通过关联关系查询。带有双下划线的 F() 对象将引入访问关联对象所需的任何连接。例如,要检索出所有作者名与博客名相同的博客,这样修改查询条件:

>>> Entry.objects.filter(authors__name=F('blog__name'))

对于 date 和 date/time 字段,你可以加上或减去一个 timedelta 对象。以下会返回所有发布 3 天后被修改的条目:

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

F() 对象通过 .bitand().bitor().bitxor().bitrightshift().bitleftshift() 支持位操作。例子:

>>> F('somefield').bitand(16)

Oracle

​ Oracle 不支持按位 XOR 操作。

Changed in Django 3.1:

​ 已添加对 .bitxor() 的支持。

# F查询:取出数据库的某个字段的值

    # 把read_num都加1
    from django.db.models import F
    ret=models.Book.objects.all().update(read_num=F('read_num')+1)
    print(ret)

    # 查询评论数大于阅读数的书籍
    ret=models.Book.objects.all().filter(commit_num__gt=F('read_num'))
    for i in ret:
        print(i.name)

    # 查询评论数大于阅读数2倍的书籍
    ret=models.Book.objects.filter(commit_num__gt=F('read_num')*2)
    print(ret)

2. Q查询

在类似 filter() 中,查询使用的关键字参数是通过 “AND” 连接起来的。如果你要执行更复杂的查询(例如,由 OR 语句连接的查询),你可以使用 Q 对象

一个 Q 对象 (django.db.models.Q) 用于压缩关键字参数集合。这些关键字参数由前文 “Field lookups” 指定。

例如,该 Q 对象压缩了一个 LIKE 查询:

from django.db.models import Q
Q(question__startswith='What')

Q 对象能通过 &| 操作符连接起来。当操作符被用于两个 Q 对象之间时会生成一个新的 Q 对象。

例如,该语句生成一个 Q 对象,表示两个 "question_startswith" 查询语句之间的 “OR” 关系:

Q(question__startswith='Who') | Q(question__startswith='What')

这等价于以下 SQL WHERE 字句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

你能通过 &| 操作符和括号分组,组合任意复杂度的语句。当然, Q 对象也可通过 ~ 操作符反转,允许在组合查询中组合普通查询或反向 (NOT) 查询:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每个接受关键字参数的查询函数 (例如 filter()exclude()get()) 也同时接受一个或多个 Q 对象作为位置(未命名的)参数。若你为查询函数提供了多个 Q 对象参数,这些参数会通过 “AND” 连接。例子:

Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

粗略地转为 SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

查询函数能混合使用 Q 对象和关键字参数。所有提供给查询函数的参数(即关键字参数或 Q 对象)均通过 “AND” 连接。然而,若提供了 Q 对象,那么它必须位于所有关键字参数之前。例子:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who',
)

会是一个有效的查询,等效于前文的例子;但是:

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

却是无效的。

参见

​ Django 单元测试中的 OR 查询实例 展示了 Q 的用法。

# Q查询:制造  与或非的条件

    # 查询名字叫egon或者价格大于100的书
    from django.db.models import Q
    ret=models.Book.objects.filter(Q(name='egon') | Q(price__gt=100))
    
    # 查询名字叫egon并且价格大于100的书
    ret=models.Book.objects.filter(Q(name='egon') & Q(price__gt=100))
    ret=models.Book.objects.filter(name='egon',price__gt=100)

    # 查询名字不为egon的书
    ret = models.Book.objects.filter(~Q(name='egon'))
    print(ret)

    # Q可以嵌套
    ret = models.Book.objects.filter((Q(name='egon') & Q(price__lt=100)) | Q(id__lt=3))
    print(ret)

10. 原生sql

Django 允许你用两种方式执行原生 SQL 查询:

​ 你可以使用 Manager.raw()执行原生查询并返回模型实例,或者完全不用模型层 直接执行自定义 SQL

# 原生sql(有些sql用orm写不出来)
    # 两种方案
    # 第一种: 用的比较少
        from django.db import connection

        cursor = connection.cursor()

        cursor.execute("""SELECT * from app01_book where id = %s""", [1])

        # row = cursor.fetchone()
        row = cursor.fetchall()
        print(row)

    # 第二种: 用的多
        books=models.Book.objects.raw('select * from app01_book where id >3')
        print(books) # RawQuerySet对象
        for book in books:
            print(book.name)

        books=models.Book.objects.raw('select * from app01_publish')
        for book in books:
            print(book.__dict__)
            print(book.name)
            print(book.addr)
            print(book.email)
            print(book.price)

    	authors = models.Author.objects.raw('SELECT app01_author.id,app01_author. NAME,app01_authordetail.sex FROM app01_author JOIN app01_authordetail ON app01_author.author_detail_id = app01_authordetail.id WHERE app01_authordetail.sex = 1')

        for author in authors:
            print(author.name)
            print(author.__dict__)

1. 执行原生查询

若管理器方法 raw() 能用于执行原生 SQL 查询,就会返回模型实例:

  • Manager.``raw(raw_query, params=None, translations=None)

    该方法接受一个原生 SQL 查询语句,执行它,并返回一个 django.db.models.query.RawQuerySet 实例。这个 RawQuerySet 能像普通的 QuerySet 一样被迭代获取对象实例。

最好用例子来解释。假设你有以下模型:

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)

然后你可以像这样执行自定义 SQL:

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
John Smith
Jane Jones

这个例子并不令人激动——它与运行 Person.objects.all() 完全相同。然而, raw() 有很多额外选项,使得它非常强大。

模型表名

在本例中, Person 表的名称是从哪来的?

默认情况下,Django 通过拼接模型的 “app label” 和模型类名推算出数据表名 —— 即你在 manage.py startapp 中使用的名称,二者以一个下划线分割。在本例中,我们假定 Person 模型位于一个叫做 myapp 的应用中,这样,模型的表名就是 myapp_person

更多细节请查阅关于 db_table 选项的文档,它也允许你手动指定数据库的表名。

警告

不会对传给 .raw() 的 SQL 语句做任何检查。Django 期望该语句会从数据库中返回一个集合,但并不强制如此。若该查询没有返回一些记录,会导致一个(含糊)的错误。

警告

若你在 MySQL 上执行查询,至于其无声的强制类型可能会弄混类型时导致不可预料的后果。若你用一个整数值查询一个字符串列,MySQL 会执行比较前将表中所有数据强制转为整数。例如,若数据表包含的值有 'abc''def',而查询语句为 WHERE mycolumn=0,这两行都会匹配上。要避免这种情况,在将值传给查询语句前进行合适的类型转换。

2. 将查询字段映射为模型字段

raw() 字段将查询语句中的字段映射至模型中的字段。

查询语句中的字段排序并不重要。换而言之,以下两种查询是一致的:

>>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')
...
>>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')
...

匹配是根据名字来的。这意味着你可以使用 SQL 的 AS 子句将查询语句中的字段映射至模型中的字段。所以,若你还有一些数据表包含了 Person 数据,你可以很方便的将其映射至 Person 实例:

>>> Person.objects.raw('''SELECT first AS first_name,
...                              last AS last_name,
...                              bd AS birth_date,
...                              pk AS id,
...                       FROM some_other_table''')

只要名字对上了,模型实例就会被正确创建。

或者,你可以用 raw()translations 参数将查询语句中的字段映射至模型中的字段。这是一个字典,将查询语句中的字段名映射至模型中的字段名。例如,上面的查询也能这样写:

>>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
>>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

3. 索引查询

raw() 支持索引,所以,若你只需要第一个结果就这样写:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]

不过,索引和切片不是在数据库层面上实现的。若数据库中有非常多的 Person 对象,更搞笑的方式是在 SQL 层面使用 limit 子句:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]

4. 延迟模型字段

>>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')

该查询返回的 Person 对象即延迟模型实例(参考 defer())。这意味着查询语句中省略的字段按需加载。例子:

>>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
...     print(p.first_name, # This will be retrieved by the original query
...           p.last_name)  # This will be retrieved on demand
...
John Smith
Jane Jones

表面上,看起来该查询同时检出了 first name 和 last name。然而,这个例子实际上执行了三次查询。只有 first names 是由 raw() 查询检出的 —— last names 是在它们被打印时按需检出。

只有一个字段你不能省略 —— 主键字段。Django 用主键来区分模型实例,所以必须在原生查询语句中包含主键。若你忘了包含主键会抛出 FieldDoesNotExist 异常。

5. 添加注释

你可以执行带有模型中未定义字段的查询语句。例如,我们能用 PostgreSQL 的 age() 函数 获取用户列表,他们的年龄已由数据库计算:

>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
>>> for p in people:
...     print("%s is %s." % (p.first_name, p.age))
John is 37.
Jane is 42.
...

你总是可以用 Func() 表达式 避免使用原生 SQL 去计算注释。

6. 将参数传给 raw()

如果你需要执行参数化的查询,可以使用 raw()params 参数:

>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

params 是一个参数字典。你将用一个列表替换查询字符串中 %s 占位符,或用字典替换 %(key)s 占位符(key 被字典 key 替换),不论你使用哪个数据库引擎。这些占位符会被 params 参数的值替换。

注解

使用 SQLite 后端时不支持字典参数;使用此后端时,你必须以列表形式传入参数。

警告

不要对原生查询或 SQL 字符串中的引号占位符使用字符串格式化!

临时将上述查询写作:

>>> query = 'SELECT * FROM myapp_person WHERE last_name = %s' % lname
>>> Person.objects.raw(query)

你可能认为你需要将查询写成这样(用单引号包裹 %s):

>>> query = "SELECT * FROM myapp_person WHERE last_name = '%s'"

不要犯其它错误。

正如 SQL injection protection 介绍的,使用 params 参数和不用引号包裹占位符使你免受 SQL 注入攻击,这是一个攻击者常用的漏洞,将任意 SQL 注入你的数据库。若你使用了字符串插入或用引号包裹占位符,你正处于 SQL 注入的风险中。

7. 直接执行自定义 SQL

有时候,甚至 Manager.raw() 都无法满足需求:你可能要执行不明确映射至模型的查询语句,或者就是直接执行 UPDATEINSERTDELETE 语句。

这些情况下,你总是能直接访问数据库,完全绕过模型层。

对象 django.db.connection 代表默认数据库连接。要使用这个数据库连接,调用 connection.cursor() 来获取一个指针对象。然后,调用 cursor.execute(sql,[params]) 来执行该 SQL 和 cursor.fetchone(),或 cursor.fetchall() 获取结果数据。

例如:

from django.db import connection

def my_custom_sql(self):
    with connection.cursor() as cursor:
        cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
        cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
        row = cursor.fetchone()

    return row

要避免 SQL 注入,你绝对不能在 SQL 字符串中用引号包裹 %s 占位符。

注意,若要在查询中包含文本的百分号,你需要在传入参数使用两个百分号:

cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])

若你同时使用 不止一个数据库,你可以使用 django.db.connections 获取指定数据库的连接(和指针)。 django.db.connections 是一个类字典对象,它允许你通过连接别名获取指定连接:

from django.db import connections
with connections['my_db_alias'].cursor() as cursor:
    # Your code here...

默认情况下,Python DB API 返回的结果不会包含字段名,这意味着你最终会收到一个 list,而不是一个 dict。要追求较少的运算和内存消耗,你可以以 dict 返回结果,通过使用如下的玩意:

def dictfetchall(cursor):
    "Return all rows from a cursor as a dict"
    columns = [col[0] for col in cursor.description]
    return [
        dict(zip(columns, row))
        for row in cursor.fetchall()
    ]

另一个选项是使用来自 Python 标准库的 collections.namedtuple()namedtuple 是一个类元组对象,可以通过属性查找来访问其包含的字段;也能通过索引和迭代。结果都是不可变的,但能通过字段名或索引访问,这很实用:

from collections import namedtuple

def namedtuplefetchall(cursor):
    "Return all rows from a cursor as a namedtuple"
    desc = cursor.description
    nt_result = namedtuple('Result', [col[0] for col in desc])
    return [nt_result(*row) for row in cursor.fetchall()]

这有个例子,介绍了三者之间的不同:

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> cursor.fetchall()
((54360982, None), (54360880, None))

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> dictfetchall(cursor)
[{'parent_id': None, 'id': 54360982}, {'parent_id': None, 'id': 54360880}]

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> results = namedtuplefetchall(cursor)
>>> results
[Result(id=54360982, parent_id=None), Result(id=54360880, parent_id=None)]
>>> results[0].id
54360982
>>> results[0][0]
54360982

8. 连接和指针

connectioncursor 实现了 PEP 249 中介绍的大部分标准 Python DB-API —— 除了 事务处理 之外。

若你并不熟悉 Python DB-API,要注意 cursor.execute() 中的 SQL 语句使用了占位符 "%s",而不是直接在 SQL 中添加参数。若你使用这个技巧,潜在的数据库库会自动在需要时转义参数。

也要注意,Django 期望 "%s" 占位符,而 不是 "?" 占位符,后者由 SQLite Python 绑定使用。这是为了一致性和正确性。

将指针作为上下文的管理器:

with connection.cursor() as c:
    c.execute(...)

相当于:

c = connection.cursor()
try:
    c.execute(...)
finally:
    c.close()

9. 调用存储流程

CursorWrapper.callproc(procname, params=None, kparams=None)

以给定名称调用数据库存储流程。要提供一个序列 (params) 或字典 (kparams) 作为输入参数。大多数数据库不支持 kparams。对于 Django 内置后端来说,只有 Oracle 支持。例如,在一个 Oracle 数据库中指定存储流程:

CREATE PROCEDURE "TEST_PROCEDURE"(v_i INTEGER, v_text NVARCHAR2(10)) AS
    p_i INTEGER;
    p_text NVARCHAR2(10);
BEGIN
    p_i := v_i;
    p_text := v_text;
    ...
END;

这将调用该存储流程:

with connection.cursor() as cursor:
    cursor.callproc('test_procedure', [1, 'test'])

11. 数据库事务

​ Django 提供多种方式控制数据库事务。

管理数据库事务

Django 默认的事务行为

Django 默认的事务行为是自动提交。除非事务正在执行,每个查询将会马上自动提交到数据库, 详见下文

Django 自动使用事务或还原点,以确保需多次查询的 ORM 操作的一致性,特别是 delete()update() 操作。

由于性能原因,Django 的 TestCase 类同样将每个测试用事务封装起来。

事务

事务是指具有原子性的一系列数据库操作。即使你的程序崩溃,数据库也会确保这些操作要么全部完成要么全部都未执行。

Django doesn’t provide an API to start a transaction. The expected way to start a transaction is to disable autocommit with set_autocommit().

进入事务后,你可以选择在 commit() 之前应用执行的更改,或者使用 rollback() 取消它们。这些函数在 django.db.transaction 中定义。

  • commit(using=None)

  • rollback(using=None)

这些函数使接受一个 using 参数表示所要操作的数据库。如果未提供,则 Django 使用 "default" 数据库。

当一个原子 atomic() 事务处于活动状态时, Django 将会拒绝进行事务提交或者事务回滚,因为这样会破坏原子性。

# 事务:ACID,事务的隔离级别(搜),锁, 行级锁,表级锁

# djanog orm中使用事务:原子性操作,要么都成功,要么都失败
# 示例
    # 新增一个作者详情,新增一个作者
    # 事物的三个粒度
    # 1 局部使用
from django.db import transaction
with transaction.atomic(): # 都在事物中,要么都成功,要么都失败
    author_detail=models.AuthorDetail.objects.create(addr='xxx',phone='123',sex=1)
    # raise Exception('抛了异常')
	author=models.Author.objects.create(name='llqz',age=19,author_detail=author_detail)
    
    # 2 视图函数装饰器,这一个视图函数都在一个事物中
    @transaction.atomic
    def index(request):
        return HttpResponse('ok')

    # 3 整个http请求,在事物中,在setting.py中配置
    '''
    DATABASES = {
        'default': {
            ...
            'PORT': 3306,
            'ATOMIC_REQUEST': True,
       
        }
    }

    'ATOMIC_REQUEST': True,
	设置为True同一个http请求对应的所有sql都放在一个事务中执行(要么所有都成功,要么所有都失败)。
    '''

12. defer 和 only

# defer和only(查询优化相关)

    # only保持是book对象,但是只能使用only指定的字段
    books = models.Book.objects.all().only('name')
    print(books[0].name)
    print(books[0].price)  # 能出来,但是会去数据库再查询一次 耗费资源
    print(books[0].__dict__)
    
    # defer 延迟查询 除 name price 字段其他的字段都筛选出来
    books = models.Book.objects.all().defer('name','price')
    print(books[0].__dict__)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I believe I can fly~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值