数据库查询相关

  • 1、使用场景
  • 2、数据库表新增一行记录
  • 3、将修改保存到数据库
  • 4、查询数据库表内容
  • 检索全部对象
  • 过滤检索的对象
  • 查询单个对象
  • 其他查询API
  • 限制查询结果条数
  • 通过指定条件查询数据表内容
  • 跨关系查询
  • 跨多值查询
  • 过滤器可以为模型指定字段
  • 引用表达式进行转换
  • 主键的快捷查询方式
  • 5、自动转义百分号和下划线
  • 6、缓存
  • 7、通过Q对象完成复杂查询
  • 8、比较两个模型的实例
  • 9、删除对象
  • 10、复制实例
  • 11、一次修改多个对象
  • 12、关联对象
  • Foreignfield字段
  • manytomangfield 关联
  • 13、查询 JSONField
  • 其他


1、使用场景

django作为后端拿到前端传到的数据后,要对数据进行处理。这个时候会对数据库进行增删改查的操作,
一种是执行原生的sql语句
一种是调用django封装好的数据库API(简单、快速)
参考代码:

from django.contrib.auth.models import User
from django.db import models


class Project(models.Model):
    PROJECT_TYPE = (
        (1, "web"),
        (2, "App"),
        (3, "微服务"),
    )
    # 自增字段,主键
    id = models.AutoField(primary_key=True)  # 自增字段:主键
    # 项目名称
    name = models.CharField(max_length=200, verbose_name="测试项目名称")  # 项目名称
    # 版本
    version = models.CharField(max_length=20, verbose_name="版本")
    # 项目类型
    type = models.IntegerField(verbose_name="产品类型", choices=PROJECT_TYPE)  # 32bit
    # 描述
    description = models.CharField(max_length=200, verbose_name="项目描述", blank=True, null=True)
    # 状态
    status = models.BooleanField(default=True, verbose_name="状态")
    # 创建人
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="创建人", db_column="created_by")
    # 创建时间
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    # 最后更新的时间
    updated_at = models.DateTimeField(auto_now=True, verbose_name="最近更新时间")

    # 项目成员(暂缓)TODO
    members = models.ManyToManyField(User, related_name="project_members",
                                   through="ProjectMember",
                                   through_fields=('project', 'user'))
    # 默认显示
    def __str__(self):
      return self.name

    # 内部类 meta ,决定 project 这个model里面的内容显示
    class Meta:
        verbose_name = "测试项目"
        verbose_name_plural = verbose_name


class ProjectMember(models.Model):
    """
    增加项目成员
    """
    MEMBER_ROLE = (
        (1, '测试人'),
        (2, '测试组长'),
        (3, '测试经理'),
        (4, '开发'),
        (5, '运维'),
        (6, '项目经理'),
    )
    # 主键
    id = models.AutoField(primary_key=True)
    # 项目
    project = models.ForeignKey(Project,  on_delete=models.PROTECT, verbose_name="测试项目")
    # 用户
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="用户")
    # 加入日期
    join_date = models.DateTimeField(verbose_name="加入日期")
    # 角色
    role = models.IntegerField(choices=MEMBER_ROLE, verbose_name='角色')
    # 状态
    status = models.BooleanField(default=True, verbose_name="状态")
    # 退出日期
    quit_date = models.DateTimeField(null=True, blank=True, verbose_name="退出日期")
    # 备忘录
    memo = models.CharField(max_length=200, verbose_name="备忘录", blank=True, null=True)

    def __str__(self):
        if not self.user:
            return '-'
        else:
            # 张某某
            firstname = self.user.first_name if self.user.first_name else '-'
            username = self.user.username
            return  f"{firstname}({username})"

    class Meta:
        verbose_name = "项目成员"
        verbose_name_plural = verbose_name


class DeployEnv(models.Model):
    """
    部署环境
    """
    # 主键
    id = models.AutoField(primary_key=True)
    # 项目
    project = models.ForeignKey(Project, on_delete=models.PROTECT, verbose_name="测试项目")
    # 名称
    name = models.CharField(max_length=50, verbose_name="环境名称")
    # 主机名 IP
    hostname = models.CharField(max_length=50, verbose_name="主机名", help_text="主机名(IP)")
    # 端口
    port = models.IntegerField(verbose_name="端口")
    # 状态
    status = models.BooleanField(default=True, verbose_name="状态")
    # 备忘录
    memo = models.CharField(max_length=200, verbose_name="备忘录", blank=True, null=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "部署环境"
        verbose_name_plural = verbose_name

2、数据库表新增一行记录

>>> from test_plt.models import *
>>> p = Project(name="命令行添加的项目",type=1)
>>> p.save()
>>> Project.objects.all()
<QuerySet [<Project: test001>, <Project: test002>, <Project: test003>, <Project: test004>, <Project: test005>, <Project: 命令行修改名字>, <Project: 命令行添加的项目>]>

3、将修改保存到数据库

  1. 将修改的字段值保存到数据库
>>> from test_plt.models import *
>>> Project.objects.all()
<QuerySet [<Project: test001>, <Project: test002>, <Project: test003>, <Project: test004>, <Project: test005>, <Project: 命令行修改名字>, <Project: 命令行添加的项目>]>
>>> p.name = 'new name'
>>> p.save()
>>> Project.objects.all()
<QuerySet [<Project: test001>, <Project: test002>, <Project: test003>, <Project: test004>, <Project: test005>, <Project: 命令行修改名字>, <Project: new name>]>
  1. 更改数据库表 foreignkey 字段的值
    测试项目中,创建人是测试项目的外键,如下更改测试项目的创建人:
>>> from test_plt.models import *
>>> pro = Project.objects.get(pk=2)
>>> pro
<Project: test001>
>>> from django.contrib.auth.models import User
>>> pro.created_by = User.objects.get(username="x21202")
>>> pro.save()
>>> pro.created_by
<User: x21202>
  1. 更改数据库表 ManyToManyField 字段的值
    测试项目的属性 测试成员,字段为 多对多的关系,这里给测试项目添加 测试成员 x21201:
>>> from test_plt.models import *
>>> pro = Project.objects.get(pk=2)
>>> from django.contrib.auth.models import User
>>> member = User.objects.get(username='x21201')
>>> member
<User: x21201>
>>> pro.members.add(member)
django.db.utils.IntegrityError: (1048, "Column 'join_date' cannot be null")

4、查询数据库表内容

要从数据库检索对象,要通过模型类的 Manager 构建一个 QuerySet。每个模型至少有一个 Manager,默认名称是 objects。

检索全部对象

>>> from test_plt.models import *
>>> Project.objects.all()
<QuerySet [<Project: test001>, <Project: test002>, <Project: test003>, <Project: test004>, <Project: test005>, <Project: 命令行修改名字>, <Project: new name>]>

过滤检索的对象

  • all() 返回的 QuerySet 包含了数据表中所有的对象
  • filter(**kwargs) 返回一个新的 QuerySet,包含的对象满足给定查询参数。
  • exclude(**kwargs) 返回一个新的 QuerySet,包含的对象 不 满足给定查询参数

字段查询即你如何制定 SQL WHERE 子句。它们以关键字参数的形式传递给 QuerySet 方法 filter(), exclude() 和 get()。

>>> from test_plt.models import *
>>> Project.objects.filter(name='test001')
<QuerySet [<Project: test001>]>
>>> Project.objects.filter(created_by__id=1,status=1)
<QuerySet [<Project: test003>]>

(1)链式过滤器
查询 测试项目名非test001 且 测试项目名是 非test002 且 项目名是 test003 的项目

>>> from test_plt.models import *
>>> Project.objects.exclude(name='test001').exclude(name='test002').filter(name="test003")
<QuerySet [<Project: test003>]>

(2)每个查询集合都是唯一的、懒惰的

>>> 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())
>>> print(q)

查询单个对象

只会有一个对象满足查询条件,你可以在 Manager 上使用 get() 方法,它会直接返回这个对象,pk是主键的别名:

>>> one_entry = Entry.objects.get(pk=1)

其他查询API

https://docs.djangoproject.com/zh-hans/4.0/ref/models/querysets/#queryset-api

限制查询结果条数

  1. 查询前5个对象:Entry.objects.all()[:5]
  2. 查询第6到第10个对象:Entry.objects.all()[5:10]
  3. 从前 10 个对象中,每隔一个抽取的对象组成的列表:Entry.objects.all()[:10:2]
  4. 返回按标题字母排序后的第一个 Entry:Entry.objects.order_by('headline')[0]

通过指定条件查询数据表内容

此查询通俗被称为字段查询,就是制定 sql 的 where 子句,它们已关键字参数的形式传递给查询方法:filter、exclude、get
语法是:field__lookuptype=value,

  1. 查询项目要求:创建时间 小于 2022-05-11 14:19:00:
>>> from test_plt.models import *
>>> Project.objects.filter(created_at__lte="2022-05-11 14:19:00")
<QuerySet [<Project: test001>, <Project: test002>]>

查询子句中,指定的字段必须是该模型的一个字段,在foreignkey中,指定_id为后缀的字段名,value必须对应外键模型的主键的原始值,
查询项目要求:创建人id是2的:

>>> from test_plt.models import *
>>> Project.objects.get(created_by_id=2)
<QuerySet [<Project: test001>, <Project: test005>]>
  1. 查询项目要求:测试项目名 完全等于 test001
>>> from test_plt.models import *
>>> Project.objects.filter(name__exact="test001")
<Project: test001>
  1. 查询项目要求:测试项目名 等于 test001,不区分大小写
>>> from test_plt.models import *
>>> Project.objects.filter(name__iexact="test001")
<Project: test001>
  1. 查询项目要求:测试项目名 包含 test
>>> from test_plt.models import *
>>> Project.objects.filter(name__contains="test")
<Project: test001>
  1. 查询项目要求:创建时间在某个时间段内
>>> from test_plt.models import *
>>> Project.objects.filter(created_at__range=("2022-05-11 00:00:00", "2022-05-11 14:18:00"))
<QuerySet [<Project: test001>]>
  1. 其他查询条件:
    in 多值
    gt 大于
    gte 大于等于
    lt 小于
    lte 小于等于
    date 日期
    year 年
    month 月
    day
    week
    startswith 开头
    endswith 结尾
    istartswith 不区分大小写的开头
    iendswith 不区分大小写的结尾

跨关系查询

  1. 查询测试项目要求:要求创建人名字是 x21201
>>> from test_plt.models import *
>>> Project.objects.filter(created_by__username="x21201")
<QuerySet [<Project: test002>, <Project: test003>, <Project: test004>]>
  1. 查询测试项目要求:要求创建人名字包含x
>>> from test_plt.models import *
>>> Project.objects.filter(created_by__username__contains="x")
<QuerySet [<Project: test002>, <Project: test003>, <Project: test004>, <Project: test001>, <Project: test005>]>
  1. 查询测试项目要求:要求创建人名字是个空值
>>> from test_plt.models import *
>>> Project.objects.filter(created_by__username__isnull=True)
<QuerySet [<Project: test002>, <Project: test003>, <Project: test004>, <Project: test001>, <Project: test005>]>
  1. 查询测试项目要求:要求创建人名字包含1 且 是超级用户
>>> from test_plt.models import *
>>> Project.objects.filter(created_by__username__contains="x", created_by__is_superuser=1)
<QuerySet [<Project: test002>, <Project: test003>, <Project: test004>, <Project: test001>, <Project: test005>]>

跨多值查询

当跨越 manytomangyfield 或者反查foreignkey时,
查询用户,要求:项目名字包含 test 且 项目状态是 在线的(其中用户是项目表的外键)

>>> from test_plt.models import *
>>> User.objects.filter(project__name__contains="test",project__status=1)
<QuerySet [<User: x21202>, <User: x21201>, <User: x21202>]>

查询用户,要求:上个的条件的非

>>> from test_plt.models import *
>>> User.objects.exclude(project__name__contains="test",project__status=1)
<QuerySet [<User: x21202>, <User: x21201>, <User: x21202>]>

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

将模型字段值与同一模型中的另一字段做比较,Django 提供了 F 表达式 实现这种比较。 F() 的实例充当查询中的模型字段的引用,参考代码:

from datetime import date

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline

(1)查询博客条目:要求博客的评论数(number_of_comments) > 博客的点赞数(number_of_pingbacks)

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

(2)查询博客条目:要求博客的评论数 两倍于 博客的点赞数

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

(3)找出所有评分低于 pingback 和评论总数之和的条目

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

(4)要检索出所有作者名与博客名相同的博客,其中blog字段是表entry的外键,name是关联表的字段名

>>> from django.db.models import F
>>> Entry.objects.filter(authors__name=F('blog__name'))

(5)所有发布 3 天后被修改的条目

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

引用表达式进行转换

要查找Entry与上次修改同一年发布的所有对象

>>> from django.db.models import F
>>> Entry.objects.filter(pub_date__year=F('mod_date__year'))

要查找条目发布的最早年份

>>> from django.db.models import F
>>> Entry.objects.aggregate(first_published_year=Min('pub_date__year'))

查找最高评分条目的值以及每年所有条目的评论总数

>>> from django.db.models import F
>>> Entry.objects.values('pub_date__year').annotate(
...     top_rating=Subquery(
...         Entry.objects.filter(
...             pub_date__year=OuterRef('pub_date__year'),
...         ).order_by('-rating').values('rating')[:1]
...     ),
...     total_comments=Sum('number_of_comments'),
... )

主键的快捷查询方式

Django 提供了一种pk查询快捷方式,pk表示主键,也就是id

>>> from test_plt.models import *
>>> Project.objects.all()
<QuerySet [<Project: test001>, <Project: test002>, <Project: test003>, <Project: test004>, <Project: test005>, <Project: 命令行修改名字>, <Project: new name>]>
>>> Project.objects.get(pk=2)
<Project: test001>
>>> Project.objects.get(id__exact=3)
<Project: test002>
>>> Project.objects.get(id=2)
<Project: test001>
>>> Project.objects.filter(pk__in=[2,4])  # 查询id是2和4的所有项目
<QuerySet [<Project: test001>, <Project: test003>]>
>>> Project.objects.filter(pk__gt=3)  # 查询id大于3的所有项目
<QuerySet [<Project: test003>, <Project: test004>, <Project: test005>, <Project: 命令行修改名字>, <Project: new name>]>
>>> Project.objects.filter(created_by__pk=2)  # 查询创建人id是2的所有项目
<QuerySet [<Project: test002>, <Project: test005>]>
>>> Project.objects.filter(created_by__pk__gt=2)  # 查询创建人id大于2的所有项目
<QuerySet [<Project: test004>, <Project: test003>]>

5、自动转义百分号和下划线

>>> from test_plt.models import *
>>> Project.objects.filter(name__contains="%")
<QuerySet [<Project: test006%>]>
>>> Project.objects.filter(name__contains="_")
<QuerySet [<Project: test007_>]>

6、缓存

(1)print(queryset[3]) 若全部查询结果集已被检出,就会去检查缓存
(2)[name for name in queryset] 重复的从某个查询结果集对象中取指定索引的对象会每次都查询数据库:

>>> from test_plt.models import *
>>> queryset = Project.objects.all()
>>> print(p.name for p in queryset)
<generator object <genexpr> at 0x000001B13BDBC510>
>>> print(queryset[3])
test004
>>> [name for name in queryset]
[<Project: test001>, <Project: test002>, <Project: test003>, <Project: test004>, <Project: test005>, <Project: 命令行修改名字>, <Project: new name>, <Project: test006%>, <Project: test007_>]

7、通过Q对象完成复杂查询

>>> from test_plt.models import *
>>> from django.db.models import Q
>>> 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')

8、比较两个模型的实例

比较实例的时候还会比较主键值,以下第一个跟下面两个都是等效的,如果name是主键的话。

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
>>> some_obj.name == other_obj.name

9、删除对象

删除2005年的entry所有的实例,也就是数据行记录

>>> Entry.objects.filter(pub_date__year=2005).delete()

当 Django 删除某个对象时,默认会模仿 SQL 约束 ON DELETE CASCADE 的行为——换而言之,某个对象被删除时,关联对象也会被删除

10、复制实例

复制实例前,设置实例 pk 属性为None,_state.adding属性为True,然后save即可

>>> pro = Project(name='test008',type=1)
>>> pro.save()
>>> pro.pk
11
>>> pro.pk=None
>>> pro._state.adding=True
>>> pro.save()
>>> Project.objects.all()
 <Project: 命令行修改名字>, <Project: test006%>, <Project: test007_>, <Project: test008>, <Project: test008>]>

(1)如果你的models是继承了另一个业务model类,参考:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3
>>> django_blog.pk = None
>>> django_blog.id = None
>>> django_blog._state.adding = True
>>> django_blog.save() # django_blog.pk == 4

(2)如果你的业务类有 mangtomangfield 关联关系,则如下处理:

>>> entry = Entry.objects.all()[0] # some previous entry
>>> old_authors = entry.authors.all()
>>> entry.pk = None
>>> entry._state.adding = True
>>> entry.save()
>>> entry.authors.set(old_authors)

(3)onetoonefield关系,如下处理:

>>> detail = EntryDetail.objects.all()[0]
>>> detail.pk = None
>>> detail._state.adding = True
>>> detail.entry = entry
>>> detail.save()

11、一次修改多个对象

(1)统一设置QuerySet中对象的想法来实现目标update()

>>> Entry.objects.filter(pub_date__year=2007).update(headline='统一更新了2007年博客的headline属性')

(2)修改 foreignfield 字段的值

>>> b = Blog.objects.get(pk=1)
>>> Entry.objects.all().update(blog=b)
>>> Entry.objects.filter(blog=b).update(headline='blog属性等于b的所有博客的headline,blog是entry的外键')

(3)使用F()表达式 给博客的点赞数加1

# 正确的用法
>>> Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks') + 1)
# 错误的用法
>>> Entry.objects.update(headline=F('blog__name'))

12、关联对象

Foreignfield字段

(1)从 Project 访问 创建人(创建人是 Project表 的外键)

>>> from django.contrib.auth.models import User
>>> from test_plt.models import *
>>> pro = Project.objects.get(pk=2)
>>> pro.created_by
<User: x21201>
>>> pro.created_by = User.objects.get(pk=2)
>>> pro.created_by
<User: x21202>

(2)从 创建人 访问 Project,表示:这个user创建了哪些表?

>>> from django.contrib.auth.models import User
>>> user = User.objects.get(id=2)
>>> user
<User: x21202>
>>> user.project_set.all()
<QuerySet [<Project: test002>, <Project: test005>]>
>>> user.project_set.filter(name__contains="002")
<QuerySet [<Project: test002>]>
>>> user.project_set.count()
2
>>> User.objects.get(id=2).project_set.filter(name__contains="002")
<QuerySet [<Project: test002>]>

manytomangfield 关联

(1)从 项目表 中查询多对多的字段 members

>>> from test_plt.models import *
>>> pro = Project.objects.get(id=2)
>>> pro
<Project: test001>
>>> pro.members.all()
<QuerySet [<User: x21201>]>
>>> pro.members.count()
1
>>> pro.members.filter(email__contains="7292")
<QuerySet [<User: x21201>]>

(2)从 测试成员 推理他所在项目有哪些

>>> from django.contrib.auth.models import User
>>> user = User.objects.get(id=2)
>>> user.project_set.all()
<QuerySet [<Project: test002>, <Project: test005>]>

>>> a = Author.objects.get(id=5)
>>> a.entry_set.all() # Returns all Entry objects for this Author.

13、查询 JSONField

ing

其他

(1)查询参考链接:https://docs.djangoproject.com/zh-hans/4.0/topics/db/queries/#field-lookups