django-3模型操作
from django.db import modelsclass Book(models.Model):title = models.CharField(max_length=200) # 书名author = models.CharField(max_length=100) # 作者publish_date = models.DateField() # 出版日期price = models.DecimalField(max_digits=10, decimal_places=2) # 价格stock = models.IntegerField(default=0) # 库存,默认0def __str__(self):return self.title
创建对象
save
# 1. 创建实例
book = Book(title="Django 入门",author="张三",publish_date=date(2023, 1, 1),price=59.99,stock=100
)# 2. 保存到数据库
book.save() # 执行 INSERT 语句
create
直接创建并保存,返回创建的实例:
book = Book.objects.create(title="Python 编程",author="李四",publish_date=date(2022, 5, 10),price=49.99,stock=50
)
bulk_create
高效创建多条记录(仅执行一次 SQL):
books = [Book(title="Java 实战", author="王五", publish_date=date(2021, 3, 15), price=69.99, stock=30),Book(title="JavaScript 指南", author="赵六", publish_date=date(2023, 2, 20), price=55.50, stock=40)
]Book.objects.bulk_create(books) # 批量插入
get_or_create
查询记录,若不存在则创建:
book, created = Book.objects.get_or_create(title="Django 入门", # 查询条件defaults={ # 若不存在,新增时的其他字段"author": "张三","publish_date": date(2023, 1, 1),"price": 59.99,"stock": 100}
)
# created 是布尔值:True 表示新建,False 表示查询到已有记录
查询
基础查询
all
查询所有记录
all_books = Book.objects.all()
get
查询单条记录(必须匹配一条,否则报错)
book = Book.objects.get(id=1) # 通过 ID 查询
book = Book.objects.get(title="Django 入门") # 通过字段查询
filter
查询符合条件的多条记录
# 价格大于 50 的书
expensive_books = Book.objects.filter(price__gt=50)# 作者是张三且库存大于 0 的书
zhang_books = Book.objects.filter(author="张三", stock__gt=0)
exclude
# 排除价格小于等于 50 的书(即查询价格 >50 的书)
cheap_books = Book.objects.exclude(price__lte=50)
高级查询
条件表达式
__gt
:大于(price__gt=50 → 价格 >50)__lt
:小于__gte
:大于等于__lte
:小于等于__contains
:包含(模糊查询,区分大小写)__icontains
:包含(不区分大小写)__in
:在列表中(author__in=[“张三”, “李四”])__range
:在范围内(publish_date__range=(start_date, end_date))__isnull
:是否为 null(author__isnull=True)
# 书名包含 "Django" 的书(不区分大小写)
django_books = Book.objects.filter(title__icontains="django")# 2023 年出版的书
from datetime import date
start = date(2023, 1, 1)
end = date(2023, 12, 31)
books_2023 = Book.objects.filter(publish_date__range=(start, end))
F查询
F() 表达式用于直接引用模型字段的值,允许在数据库层面进行字段间的比较或运算,而无需先将数据加载到 Python 内存中。
# 示例:查询库存大于销量的书籍(假设有 sales 字段)
books = Book.objects.filter(stock__gt=F('sales'))# 解释:直接在数据库中比较 stock 和 sales 字段,避免了 Python 层面的计算
字段运算
# 示例1:所有书籍涨价 10%
Book.objects.all().update(price=F('price') * 1.1)# 示例2:某本书库存减少 5
book = Book.objects.get(id=1)
book.stock = F('stock') - 5
book.save()# 注意:保存后需要刷新实例才能看到最新值(因为 F() 是数据库层面的操作)
book.refresh_from_db() # 从数据库重新加载数据
跨关系使用
class Author(models.Model):name = models.CharField(max_length=100)age = models.IntegerField()class Book(models.Model):title = models.CharField(max_length=200)author = models.ForeignKey(Author, on_delete=models.CASCADE)publish_year = models.IntegerField()# 示例:查询出版年份大于作者年龄的书籍(假设作者年龄与出版年份有逻辑关联)
books = Book.objects.filter(publish_year__gt=F('author__age'))
Q 查询
Q() 表达式用于构建复杂的查询条件,支持逻辑运算符(与、或、非),可以组合多个查询条件。
|
# 示例:查询价格大于 100 元 或 作者是 "张三" 的书籍
books = Book.objects.filter(Q(price__gt=100) | Q(author="张三") # | 表示逻辑或
)
&
# 示例:查询价格大于 100 元 且 作者是 "张三" 的书籍
# 等价于 filter(price__gt=100, author="张三"),但 Q() 更灵活
books = Book.objects.filter(Q(price__gt=100) & Q(author="张三") # & 表示逻辑与
)
~
# 示例:查询作者不是 "张三" 的书籍
books = Book.objects.filter(~Q(author="张三") # ~ 表示逻辑非
)
嵌套使用
# 示例:查询(价格 >100 且 2023 年出版) 或 (作者是张三且库存 >0)的书籍
books = Book.objects.filter(Q(price__gt=100, publish_year=2023) | Q(author="张三") & Q(stock__gt=0)
)
# 等价于 Q(author="张三") & Q(price__gt=100)
books = Book.objects.filter(author="张三", Q(price__gt=100))
F () 与 Q () 结合使用
# 示例:查询(库存 > 销量 且 价格 > 50) 或 (作者是张三)的书籍
books = Book.objects.filter((Q(stock__gt=F('sales')) & Q(price__gt=50)) | Q(author="张三")
)
排序与限制
order_by
# 按价格升序(默认)
books_by_price = Book.objects.order_by("price")# 按价格降序(加负号)
books_by_price_desc = Book.objects.order_by("-price")
first / last
first_book = Book.objects.first()
last_book = Book.objects.last()
reverse
反转 QuerySet 顺序(需先排序)
reversed_books = Book.objects.order_by("price").reverse() # 等效于 order_by("-price")
切片
# 取前 10 条
top10_books = Book.objects.all()[:10]# 分页:取第 11-20 条
page2_books = Book.objects.all()[10:20]
分组聚合
分组聚合(Group By + Aggregation)是处理统计分析类需求的强大工具,常用于计算分组数据的总和、平均值、数量等。
from django.db.models import Avg, Sum, Count# 平均价格
avg_price = Book.objects.aggregate(Avg("price")) # {'price__avg': 58.33}# 总库存
total_stock = Book.objects.aggregate(Sum("stock")) # {'stock__sum': 220}# 按作者分组,统计每个作者的书籍数量
author_book_count = Book.objects.values("author").annotate(Count("id"))
# 结果:[{'author': '张三', 'id__count': 1}, {'author': '李四', 'id__count': 1}, ...]
去重计数(distinct=True)
# 统计每个作者的不同出版社数量(假设有 publisher 字段)
result = Book.objects.values('author').annotate(publisher_count=Count('publisher', distinct=True)
)
对 DateTimeField 字段,可按年、月、日等粒度分组(需结合 Trunc 函数):
from django.db.models.functions import TruncYear, TruncMonth# 按出版年份分组,统计每年出版的书籍数量
result = Book.objects.annotate(publish_year=TruncYear('publish_date') # 提取年份
).values('publish_year').annotate(book_count=Count('id')
)# 按出版月份分组(如 2023-01, 2023-02)
result = Book.objects.annotate(publish_month=TruncMonth('publish_date') # 提取年月
).values('publish_month').annotate(book_count=Count('id')
)
多个聚合值
# 按作者分组:统计书籍数量、平均价格、总库存
result = Book.objects.values('author').annotate(book_count=Count('id'),avg_price=Avg('price'),total_stock=Sum('stock')
)
多字段分组 + 过滤
# 按作者和出版年份分组,统计每组书籍数量
result = Book.objects.values('author', 'publish_year').annotate(book_count=Count('id')
).order_by('author', 'publish_year') # 按分组字段排序# 结果格式:
# [
# {'author': '张三', 'publish_year': 2023, 'book_count': 2},
# {'author': '张三', 'publish_year': 2022, 'book_count': 1},
# ...
# ]
子查询与聚合结合
# 子查询:统计每个作者的书籍数量
author_book_count = Book.objects.filter(author=OuterRef('pk')
).annotate(count=Count('id')
).values('count')# 主查询:查询所有作者,并附加其书籍数量
authors = Author.objects.annotate(book_count=Subquery(author_book_count[:1]) # 取子查询结果的第一条
)
分组后过滤(filter() 与 having 区别)
- filter():在分组之前过滤数据(类似 SQL 的 WHERE)。
- annotate() 后再 filter():在分组之后过滤(类似 SQL 的 HAVING)。
# 示例1:先过滤(2020年以后出版的书),再分组统计
result1 = Book.objects.filter(publish_year__gt=2020).values('author').annotate(book_count=Count('id')
)# 示例2:先分组,再过滤分组结果(只保留书籍数量 > 2 的作者)
result2 = Book.objects.values('author').annotate(book_count=Count('id')
).filter(book_count__gt=2) # 此处 filter 等效于 HAVING book_count > 2
对关联模型(如外键、多对多)进行分组聚合时,需通过双下划线(__)关联字段。
class Author(models.Model):name = models.CharField(max_length=100)country = models.CharField(max_length=50) # 作者所属国家class Book(models.Model):title = models.CharField(max_length=200)author = models.ForeignKey(Author, on_delete=models.CASCADE) # 外键关联作者price = models.DecimalField(max_digits=10, decimal_places=2)
# 按作者的国家分组,统计每个国家的书籍总数和平均价格:
from django.db.models import Count, Avgresult = Book.objects.values('author__country').annotate(total_books=Count('id'),avg_book_price=Avg('price')
)# 结果格式:
# [
# {'author__country': '中国', 'total_books': 10, 'avg_book_price': 55.5},
# {'author__country': '美国', 'total_books': 8, 'avg_book_price': 62.3},
# ...
# ]
常用聚合函数
- Count
- Sum
- Avg
- Max
- Min
- StdDev: 标准差(仅部分数据库支持)
- Variance: 方差(仅部分数据库支持)
values
返回字典列表(键为字段名)。
# 获取所有书籍的标题和作者
book_data = Book.objects.values('title', 'author__name')
# 结果:[{'title': 'Django 入门', 'author__name': '张三'}, ...]
values_list
# 获取所有书籍标题(扁平列表)
titles = Book.objects.values_list('title', flat=True)
# 结果:['Django 入门', 'Python 编程', ...]
only
仅加载指定字段(其他字段访问会触发新查询)。
# 只加载书名和作者(适合列表展示等场景)
books = Book.objects.only('title', 'author__name')
defer
延迟加载指定字段(与 only 相反)。
# 不加载大文本字段 content(适合不需要展示详情的场景)
books = Book.objects.defer('content')
select_related
select_related:用于外键 / 一对一关系,通过 JOIN 一次性加载关联对象(适用于 “单对象” 关联)。
# 普通查询(会产生 N+1 条 SQL:1 条查书籍,N 条查对应作者)
books = Book.objects.all()
for book in books:print(book.author.name) # 每次访问 author 都会触发新查询# 优化后(仅 1 条 SQL,通过 JOIN 加载书籍和关联的作者)
books = Book.objects.select_related('author').all()
for book in books:print(book.author.name) # 无额外查询
prefetch_related
用于多对多 / 反向外键关系,通过单独查询关联对象再在 Python 中关联(适用于 “多对象” 关联)。
# 优化多对多查询(书籍与标签)
books = Book.objects.prefetch_related('tags').all()
for book in books:print([tag.name for tag in book.tags.all()]) # 无额外查询
# 加载书籍、作者及其所有作品
books = Book.objects.select_related('author').prefetch_related('author__books').all()
原始 SQL 查询
raw
raw(sql, params=None)
:执行原始 SQL 并返回模型实例
books = Book.objects.raw("SELECT * FROM myapp_book WHERE price > %s", [50])
extra()
附加 SQL 片段(不推荐,建议用 annotate 或 F/Q)
books = Book.objects.extra(where=["price > 50"])
QuerySet 基本特性
QuerySet 是 Django ORM 中用于与数据库交互的核心对象,它代表数据库中一组记录的集合,支持链式操作和延迟执行。
延迟执行(Lazy Evaluation)
QuerySet 不会立即执行数据库查询,直到真正需要使用数据时才会触发 SQL 执行。这是 QuerySet 最核心的特性之一。
# 定义 QuerySet(未执行查询)
books = Book.objects.filter(author="张三")# 以下操作会触发 SQL 执行:
print(books) # 打印时
for book in books: # 迭代时pass
list(books) # 转换为列表时
if books: # 判断布尔值时
可链式调用
# 链式调用:价格大于 50 且 2023 年出版的书,按价格降序
books = Book.objects.filter(price__gt=50).filter(publish_year=2023).order_by("-price")
不可变对象
QuerySet 是不可变的,每次链式调用都会返回一个新的 QuerySet,原 QuerySet 不会被修改:
qs1 = Book.objects.filter(author="张三")
qs2 = qs1.filter(price__gt=50) # qs1 不变,qs2 是新的 QuerySet
复制 QuerySet
由于 QuerySet 是不可变的,可通过 all() 复制:
qs1 = Book.objects.filter(author="张三")
qs2 = qs1.all() # 复制 qs1,后续操作不影响 qs1
QuerySet 与其他对象的区别
更新数据
save
单个实例更新
# 1. 查询实例
book = Book.objects.get(id=1)# 2. 修改字段
book.price = 65.99 # 涨价
book.stock -= 1 # 库存减1# 3. 保存到数据库
book.save() # 执行 UPDATE 语句
update
# 所有张三的书涨价 10%
Book.objects.filter(author="张三").update(price=F("price") * 1.1)
# 注意:F() 用于引用字段本身,避免先查询再计算的竞态问题
bulk_update
# 1. 查询需要更新的实例
books = Book.objects.filter(author="张三")# 2. 修改实例字段
for book in books:book.stock += 5 # 库存各加5# 3. 批量更新(指定需要更新的字段)
Book.objects.bulk_update(books, ["stock"])
条件更新与 Case/When
from django.db.models import Case, When, IntegerField# 对不同书籍设置不同库存
Book.objects.update(stock=Case(When(title__icontains="Django", then=100), # Django 相关书籍库存设为 100When(author="张三", then=50), # 张三的书库存设为 50default=0, # 其他默认 0output_field=IntegerField())
)
删除
book = Book.objects.get(id=1)
book.delete() # 执行 DELETE 语句# 删除所有库存为 0 的书
Book.objects.filter(stock=0).delete()
- 删除操作不可逆,谨慎使用。
- 若模型设置了 on_delete 关联关系(如外键),删除时会触发对应的级联行为(如 CASCADE 级联删除)。
其他操作
iterator
适合大数据量,减少内存占用(一次加载一批)
# 处理 100 万条记录,每次加载 1000 条
for book in Book.objects.iterator(chunk_size=1000):process(book)
refresh_from_db
刷新实例(从数据库重新加载)
book = Book.objects.get(id=1)
# 其他操作可能修改了数据库中的记录...
book.refresh_from_db() # 从数据库重新加载最新数据
count
# 总书籍数量
total = Book.objects.count()# 张三的书籍数量
zhang_count = Book.objects.filter(author="张三").count()
exists
# 判断是否有价格大于 100 的书
has_expensive = Book.objects.filter(price__gt=100).exists() # 返回布尔值