跳转至

django ORM

约 1089 个字 153 行代码 预计阅读时间 6 分钟

定义

./应用/models.py
from django.db import models

class User(models.Model):
    id = models.AutoField(primary_key=True)
    create_time = models.DateTimeField(auto_now_add=True)
    update_time = models.DateTimeField(auto_now=True)
    create_user = models.CharField(max_length=20)
    update_user = models.CharField(max_length=20)
    is_deleted = models.BooleanField(default=False)
    username = models.CharField(max_length=20)
    password = models.CharField(max_length=64)
    email = models.EmailField()
    phone_area_code = models.CharField(max_length=10)
    phone = models.CharField(max_length=20)
    is_validated = models.BooleanField(default=False)

常用字段类型

数字

  • IntegerField:整数,-2147483648-2147483647
  • BigIntegerField:大整数,-9223372036854775808-9223372036854775807
  • PositiveIntegerField:正整数,0-2147483647
  • SmallIntegerField:小整数,-32768-32767
  • PositiveSmallIntegerField:正小整数,0-32767
  • FloatField:浮点数
  • AutoField:自增列,使用整数(但最小值为 1)
  • BigAutoField:自增列,使用大整数(但最小值为 1)

    • 定义实体类时,默认会定义自增的 id 为主键,默认为该类:
    ./项目名/settings.py
    DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
    
  • BooleanField:接受布尔值,在数据库层面为 tinyint。若未指定默认值,则为 None

文本

  • CharField:对应 varchar。可设置 max_length 参数
  • TextField:大量文本。对应 longtext
  • EmailField:类似 CharField,最大长度 254。会验证格式
  • FileField:存储文件路径
  • ImageField:存储图片路径
  • UUIDField:存储 UUID 格式的字符串(32 字)。会验证格式
  • URLField:类似 CharField,存储 URL 格式的字符串(默认 max_length 为 200)。会验证格式

日期时间

  • DateTimeField:存储日期时间
  • DateField:存储日期
  • TimeField:存储时间

这些类可以设置以下属性:

  • auto_now:设为 True 时,新增、修改时设置为当前时间
  • auto_now_add:设为 True 时,新增时设置为当前时间

常用字段参数

  • null:数据库层面上是否可空,默认为 False。官方不建议在文本类型中设置为 True(防止同时有 NULL 和空字符串的情况)
  • blank:表单验证层面上是否可空,默认为 False
  • db_column:数据库里面的列名(如未定义则用参数名)
  • default:默认值。可用函数,但不能用 lambda 表达式;不能用列表、字典、集合等可变数据类型
  • primary_key:是否主键
  • unique:是否唯一

Meta

1
2
3
4
5
6
7
class User(models.Model):
    id = models.AutoField(primary_key=True)
    create_time = models.DateTimeField(auto_now_add=True)
    ...

    class Meta:
        db_table = 'test'

常用参数:

  • db_table: 自定义表名
  • ordering:自定义查询时的默认排序方式,如 ['username', '-create_time']

外键

一般外键(一对多关系)

1
2
3
4
5
6
7
class User(models.Model):
    username = models.CharField(max_length=20)
    email = models.EmailField()

class Article(models.Model):
    title = models.CharField(max_length=20)
    author = models.ForeignKey("User", on_delete = models.CASCADE)

on_delete 参数定义了:当外键指向的记录删除时,本记录如何处理(应用层面上的,不是数据库层面上的)。常用值:

  • models.CASCADE:关联的本条记录也一并删除
  • models.SET_NULL:本记录的对应列设为 NULL(需要设置该列的 nullblankTrue
  • models.PROTECT:只要该记录存在,就不能删除外键指向的记录
  • models.SET_DEFAULT:本记录的对应列设为默认值(需设置默认值)
  • models.SET(函数或方法对象):本记录的对应列设为函数 / 方法的返回值
  • models.DO_NOTHING:只看数据库层面约束

上例在数据库中,会添加 xxx_article.author_id 列,对应 xxx_user.id

被指向的实体可以通过 xxxx_set (如上例的 User.author_set)查询其下的项目:

# 假如指定了 User 实例 user
articles = user.article_set.all()

这个名字可以在定义外键时通过 related_name 参数自定义:

author = models.ForeignKey("User", on_delete = models.CASCADE, related_name="posted_articles")

如果被指向的实体类在其他 app 中:

# 如 app 名称为 user 中有 User 实体类
author = models.ForeignKey("user.User", on_delete = models.CASCADE)

如果要指向自己:

author = models.ForeignKey("self", on_delete = models.CASCADE)
# 也可写该实体类的名字

一对一关系

1
2
3
4
5
6
7
class User(models.Model):
    username = models.CharField(max_length=20)
    email = models.EmailField()

class UserExtension(models.Model):
    birthday = models.DateTimeField()
    user = models.OneToOneField("User", on_delete=models.CASCADE)

多对对关系

1
2
3
4
5
6
7
class User(models.Model):
    username = models.CharField(max_length=20)
    email = models.EmailField()
    tags = models.ManyToManyField("Tag", related_name="users")

class Tag(models.Model)
    name = models.CharField(max_length=20)

实质是建了一个中间表(上例为 xxx_user_tags,其中有 idarticle_idtag_id)。

应用到数据库

1
2
3
4
# 1. 生成迁移文件,但不会应用到数据库(但是会试图连接数据库,如有问题仍然会生成)
python manage.py makemigrations
# 2. 应用到数据库
python manage.py migrate

新增

user = User(create_time=..., ...)
user.save()

查询

查询所有

1
2
3
users = User.objects.all()
for user in users:
    print(user.id, user.username, ...)

这里的 usersQuerySet 实例,可通过 .query 属性查看具体执行的 SQL 语句。

带条件查询

1
2
3
4
5
6
# 返回多个实例
users = User.objects.filter(username="ABC")
# 返回单个实例,找不到则抛出 类.DoesNotExist 错误
user = User.objects.get(username="ABC")
# 返回多个实例,但排除掉符合条件的数据
users = User.objects.exclude(username="ABC")

可以通过 列名__操作符 的参数指定条件:

exact:等于

article = Article.objects.get(id__exact=14)     # id = 14
article = Article.objects.get(id__exact=None)   # id is null

iexact:类似于(LIKE

articles = Article.objects.filter(title__iexact='hello')     # title like 'hello'
articles = Article.objects.filter(title__iexact='hello%')     # title like 'hello%'

包含

  • contains:大小写敏感
  • icontains:大小写不敏感
articles = Article.objects.filter(title__contains='hello')  # title like binary '%hello%'
articles = Article.objects.filter(title__icontains='hello')  # title like '%hello%'

in:值是否在给定容器

可以传列表、元组等可迭代对象,包括 QuerySet

1
2
3
4
5
6
7
8
9
articles = Article.objects.filter(id__in=[1, 2, 3]) # id in (1, 2, 3)

inner_qs = Article.objects.filter(title__contains='hello')
categories = Category.objects.filter(article__in=inner_qs)
# select ... from category
# where article_id in (
#   select id from article
#   where title like binary '%hello%'
# )

比较

  • gt: >
  • gte: ≥
  • lt:<
  • lte:≤
articles = Article.objects.filter(id__gt=3) # id > 3

以给定值开始

  • startswith:大小写敏感
  • istartswith:大小写不敏感
articles = Article.objects.filter(title__startswith='hello')    # id like binary 'hello%'

以给定值结束

  • endswith:大小写敏感
  • iendswith:大小写不敏感
articles = Article.objects.filter(title__endswith='hello')    # id like binary '%hello'

range:值是否在给定区间

传入元组:

1
2
3
4
5
6
7
from django.utils.timezone import make_aware
from datetime import datetime

start_date = make_aware(datetime(year=2024, month=1, day=1))
end_date = make_aware(datetime(year=2025, month=1, day=1))
articles = Article.objects.filter(pub_date__range=(start_date, end_date))
# pub_date between '2024-01-01' and '2025-01-01'

日期时间

可用 dateyearmonthdayweek_day(周日为 1,周一 - 周六为 2-7)、time,并支持链式调用。

1
2
3
4
articles = Article.objects.filter(pub_date__year=2020)
articles = Article.objects.filter(pub_date__year__gt=2020)
articles = Article.objects.filter(pub_date__date=date(2020,1,1))
articles = Article.objects.filter(pub_date__time=datetime.time(22,1,1))

isnull:是否为空

articles = Article.objects.filter(pub_date__isnull=False)   # pub_date is not null

正则表达式

  • regex:大小写敏感
  • iregex:大小写不敏感
articles = Article.objects.filter(title__regex=r'^hello')   # title regexp binary '^hello'

关联表查询

class Category(models.Model):
    name = models.CharField(...)

class Article(models.Model):
    title = models.CharField(...)
    category = models.ForeignKey('Category', on_delete = models.CASCADE)

# 获取文章标题含 hello 的所有分类

categories = Category.object.filter(article__title__contains('hello'))

排序

1
2
3
4
# 按照 name 升序
users = User.objects.order_by('name')
# 按照 name 降序
users = User.objects.order_by('-name')

聚合函数

平均

1
2
3
4
from django.db.models import Avg

result = Book.objects.aggregate(Avg('price'))
# {'price__avg': 23.0}

可修改默认名称:

result = Book.objects.aggregate(my_avg=Avg('price'))
# {'my_avg': 23.0}

计数

1
2
3
4
from django.db.models import Count

result = Book.objects.aggregate(book_num=Count('price'))
# {'book_num': 12}

distinct 参数,设为 True 则去重:

result = Book.objects.aggregate(book_num=Count('price', distinct=True))

最值

1
2
3
4
from django.db.models import Max, Min

result = Book.objects.aggregate(max_price=Max('price'), min_price=Min('price'))
# {'max_price': 100.2, 'min_price', 35.4}

求和

1
2
3
from django.db.models import Sum

result = Book.objects.annotate(total=Sum("bookorder__price")).values("name", "total")

其中,annotate() 方法表示在查询结果中附加对应的字段。values() 方法表示只提取给定的字段。返回 QuerySet 实例。

annotate() 方法类似于 group by

Q 语句:或关系

1
2
3
from django.db.models import Q

books = Book.objects.filter(Q(price__lte=10) | Q(rating__lte=9))    # price <= 10 or rating <= 9

修改

1
2
3
# user 是查询得到的一个实例
user.username = '222'
user.save()

删除

1
2
3
# user 是查询得到的一个实例
user.delete()
user.save()

F 表达式

F 表达式在生成 SQL 语句时,动态获取传给其的值,而无需事先查询数据。用于批量操作:

例 1:批量更改数据

原来的写法
1
2
3
4
employees = Employee.object.all()
for employee in employees:
    employee.salary += 1000
    employee.save()
使用 F 表达式
1
2
3
from django.db.models import F

Employee.object.update(salary=F("salary") + 1000)

例 2:筛选出两字段值相等的数据

原来的写法
1
2
3
4
5
out = []
employees = Employee.object.all()
for employee in employees:
    if employee.name == employee.email:
        out.append(employee)
使用 F 表达式
1
2
3
from django.db.models import F

out = Employee.object.filter(name=F("email"))