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() # 返回布爾值