Django ORM 與 QuerySet 查詢語法完全指南 | Django 教學
Django ORM(Object-Relational Mapping,物件關聯映射)是 Django 框架最強大的核心功能之一。透過 QuerySet(查詢集),你可以完全使用 Python 語法來操作資料庫,從基本的 CRUD 到複雜的聚合分析,都不需要手寫一行 SQL。本文將完整介紹 QuerySet 的所有查詢方法與進階技巧,搭配實戰程式碼範例,作為你日常開發的終極參考。
QuerySet 是什麼?
QuerySet 是 Django ORM 中代表資料庫查詢結果的物件。你可以把它想像成一個「尚未執行的 SQL 查詢」——它描述了你想從資料庫中取得什麼資料,但在你真正需要結果之前,它不會發送任何 SQL 到資料庫。
每個 Django Model 都有一個預設的 Manager(管理器),名稱為 objects,它是所有 QuerySet 操作的起點:
# Article 是一個 Django Model
# objects 是預設的 Manager
# all() 回傳一個 QuerySet
articles = Article.objects.all()
惰性求值(Lazy Evaluation)
QuerySet 最重要的特性就是 惰性求值(Lazy Evaluation),意思是建立 QuerySet 時不會立即執行 SQL,只有在以下情況才會真正「求值」:
# 建立 QuerySet —— 此時 SQL 尚未執行
qs = Article.objects.filter(status='published').order_by('-created_at')
# 以下操作才會觸發 SQL 查詢:
for article in qs: # 迭代
print(article.title)
article_list = list(qs) # 轉成 list
first = qs[0] # 索引取值
count = len(qs) # 計算長度
exists = bool(qs) # 布林判斷
print(qs) # 印出內容
這個設計的好處是,你可以不斷地「鏈式組合」多個過濾條件,Django 最後只會產生一條最佳化後的 SQL 語句,而非每加一個條件就查一次資料庫。
CRUD 操作
以下範例假設我們有一個 Article Model:
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
status = models.CharField(max_length=10, default='draft')
view_count = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
Create(新增)
# 方法一:create() —— 建立並直接存入資料庫
article = Article.objects.create(
title='Django ORM 入門',
content='本文介紹 Django ORM 的基本用法...',
status='published'
)
# 輸出:<Article: Django ORM 入門>
# 方法二:save() —— 先建立實例,再手動儲存
article = Article(title='QuerySet 進階', content='進階查詢技巧...')
article.status = 'draft'
article.save() # 此時才寫入資料庫
# 方法三:bulk_create() —— 批次新增,效能更佳
articles = Article.objects.bulk_create([
Article(title='文章一', content='內容一', status='published'),
Article(title='文章二', content='內容二', status='draft'),
Article(title='文章三', content='內容三', status='published'),
])
# 只執行一次 INSERT 語句,而非三次
Read(查詢)
# all() —— 取得所有資料
all_articles = Article.objects.all()
# get() —— 取得單筆資料(找不到會拋出 DoesNotExist 例外)
article = Article.objects.get(pk=1)
# 注意:如果符合條件的資料超過一筆,會拋出 MultipleObjectsReturned
# filter() —— 篩選符合條件的資料(回傳 QuerySet)
published = Article.objects.filter(status='published')
# exclude() —— 排除符合條件的資料(回傳 QuerySet)
not_draft = Article.objects.exclude(status='draft')
# first() —— 取得第一筆資料(沒資料回傳 None,不會拋例外)
first_article = Article.objects.first()
# last() —— 取得最後一筆資料
last_article = Article.objects.last()
# get_or_create() —— 取得或建立
article, created = Article.objects.get_or_create(
title='唯一標題',
defaults={'content': '預設內容', 'status': 'draft'}
)
# created 為 True 表示是新建立的,False 表示已存在
Update(更新)
# 方法一:修改實例後 save()
article = Article.objects.get(pk=1)
article.title = '更新後的標題'
article.save() # 執行 UPDATE 語句
# 方法二:update() —— 批次更新(直接執行 SQL,不觸發 save() 方法)
Article.objects.filter(status='draft').update(status='published')
# 輸出:2 (回傳受影響的資料筆數)
# update_or_create() —— 更新或建立
article, created = Article.objects.update_or_create(
title='唯一標題',
defaults={'content': '更新後的內容', 'status': 'published'}
)
Delete(刪除)
# 刪除單筆
article = Article.objects.get(pk=1)
article.delete()
# 輸出:(1, {'blog.Article': 1})
# 批次刪除
deleted_count, details = Article.objects.filter(status='draft').delete()
# deleted_count 為刪除的總筆數
# details 為各 Model 刪除的明細(含級聯刪除)
查詢過濾器(Field Lookups)
Django 使用 雙底線語法(double underscore) 來指定查詢過濾器,格式為 欄位名__lookup=值。
精確比對
# exact —— 精確比對(預設,可省略)
Article.objects.filter(status__exact='published')
Article.objects.filter(status='published') # 等同上方
# iexact —— 不分大小寫的精確比對
Article.objects.filter(title__iexact='django orm')
# 會比對到 'Django ORM'、'DJANGO ORM' 等
包含查詢
# contains —— 包含(區分大小寫)
Article.objects.filter(title__contains='Django')
# SQL: WHERE title LIKE '%Django%'
# icontains —— 包含(不分大小寫),最常用
Article.objects.filter(title__icontains='django')
# SQL: WHERE title ILIKE '%django%'
開頭與結尾
# startswith —— 以指定字串開頭
Article.objects.filter(title__startswith='Django')
# endswith —— 以指定字串結尾
Article.objects.filter(title__endswith='指南')
# istartswith / iendswith —— 不分大小寫版本
Article.objects.filter(title__istartswith='django')
數值比較
# gt —— 大於(greater than)
Article.objects.filter(view_count__gt=100)
# gte —— 大於等於(greater than or equal)
Article.objects.filter(view_count__gte=100)
# lt —— 小於(less than)
Article.objects.filter(view_count__lt=50)
# lte —— 小於等於(less than or equal)
Article.objects.filter(view_count__lte=50)
範圍與集合
# in —— 在指定集合中
Article.objects.filter(status__in=['published', 'archived'])
# SQL: WHERE status IN ('published', 'archived')
# range —— 在指定範圍內(含頭尾,等同 SQL 的 BETWEEN)
from datetime import date
Article.objects.filter(
created_at__range=(date(2026, 1, 1), date(2026, 12, 31))
)
空值判斷
# isnull —— 判斷是否為 NULL
Article.objects.filter(published_at__isnull=True) # 尚未發布的文章
Article.objects.filter(published_at__isnull=False) # 已發布的文章
日期與時間
# date —— 比對日期部分
from datetime import date
Article.objects.filter(created_at__date=date(2026, 5, 31))
# year —— 比對年份
Article.objects.filter(created_at__year=2026)
# month —— 比對月份
Article.objects.filter(created_at__month=5)
# day —— 比對日
Article.objects.filter(created_at__day=31)
# 可以組合使用
Article.objects.filter(created_at__year=2026, created_at__month__gte=6)
# 取得 2026 年 6 月之後的文章
排序
# order_by() —— 指定排序方式
# 升冪排序
Article.objects.order_by('created_at')
# 降冪排序(欄位名前加 - 號)
Article.objects.order_by('-created_at')
# 多欄位排序
Article.objects.order_by('status', '-created_at')
# 先依 status 升冪,再依 created_at 降冪
# 隨機排序(注意:在大量資料時效能較差)
Article.objects.order_by('?')
# reverse() —— 反轉現有排序
Article.objects.order_by('created_at').reverse()
# 等同 order_by('-created_at')
限制查詢結果
# 切片(Slicing)—— 等同 SQL 的 LIMIT / OFFSET
Article.objects.all()[:5] # 取前 5 筆(LIMIT 5)
Article.objects.all()[5:10] # 跳過前 5 筆,取 5 筆(OFFSET 5 LIMIT 5)
Article.objects.all()[:1] # 取第 1 筆(回傳 QuerySet)
# 注意:QuerySet 不支援負數索引
# Article.objects.all()[-1] # 會拋出 AssertionError
# count() —— 計算筆數(執行 SELECT COUNT(*),不載入資料)
total = Article.objects.filter(status='published').count()
# 輸出:42
# exists() —— 檢查是否有資料(比 count() > 0 更有效率)
has_articles = Article.objects.filter(status='published').exists()
# 輸出:True
聚合查詢(Aggregate)
聚合(Aggregation) 用於對整個 QuerySet 進行統計計算,回傳一個字典。
from django.db.models import Count, Sum, Avg, Max, Min
# aggregate() —— 對整個 QuerySet 計算,回傳字典
result = Article.objects.aggregate(
total=Count('id'), # 總筆數
total_views=Sum('view_count'), # 總瀏覽次數
avg_views=Avg('view_count'), # 平均瀏覽次數
max_views=Max('view_count'), # 最高瀏覽次數
min_views=Min('view_count'), # 最低瀏覽次數
)
# 輸出:{'total': 100, 'total_views': 5000, 'avg_views': 50.0, 'max_views': 500, 'min_views': 0}
# 可以搭配 filter() 使用
published_stats = Article.objects.filter(status='published').aggregate(
count=Count('id'),
avg_views=Avg('view_count'),
)
注釋查詢(Annotate)
注釋(Annotation) 與聚合不同,它會為 QuerySet 中的 每一筆資料 加上計算欄位。
from django.db.models import Count, Avg
# annotate() —— 為每筆資料加上計算欄位
# 假設 Article 有一個反向關聯 comments(Comment Model 有 ForeignKey 到 Article)
articles = Article.objects.annotate(
comment_count=Count('comments')
)
for article in articles:
print(f'{article.title}: {article.comment_count} 則留言')
# 輸出:
# Django ORM 入門: 15 則留言
# QuerySet 進階: 8 則留言
# 搭配排序 —— 找出留言最多的文章
popular = Article.objects.annotate(
comment_count=Count('comments')
).order_by('-comment_count')[:5]
# 搭配過濾 —— 只取留言超過 10 則的文章
active = Article.objects.annotate(
comment_count=Count('comments')
).filter(comment_count__gt=10)
Q 物件:複雜條件組合
filter() 中的多個參數之間是 AND 關係。當你需要 OR 或 NOT 邏輯時,就必須使用 Q 物件(Q Object)。
from django.db.models import Q
# OR 查詢 —— 使用 | 運算子
# 取得已發布或瀏覽超過 100 次的文章
Article.objects.filter(
Q(status='published') | Q(view_count__gt=100)
)
# SQL: WHERE status = 'published' OR view_count > 100
# NOT 查詢 —— 使用 ~ 運算子
# 取得非草稿的文章
Article.objects.filter(~Q(status='draft'))
# SQL: WHERE NOT status = 'draft'
# AND 查詢 —— 使用 & 運算子
Article.objects.filter(
Q(status='published') & Q(view_count__gt=100)
)
# 複雜條件組合 —— 混合 AND、OR、NOT
# 取得「已發布且瀏覽超過 100 次」或「作者是自己」的文章,但排除已封存的
Article.objects.filter(
(Q(status='published') & Q(view_count__gt=100)) | Q(author=current_user)
).exclude(
Q(status='archived')
)
# Q 物件也可以動態組合
conditions = Q()
if search_keyword:
conditions &= Q(title__icontains=search_keyword)
if status_filter:
conditions &= Q(status=status_filter)
if min_views:
conditions &= Q(view_count__gte=min_views)
articles = Article.objects.filter(conditions)
F 表達式:欄位間比較與操作
F 表達式(F Expression) 讓你在查詢中引用同一筆資料的其他欄位值,而非 Python 變數。這在欄位間比較和原子操作中特別有用。
from django.db.models import F
# 欄位間比較 —— 找出瀏覽次數超過按讚次數的文章
Article.objects.filter(view_count__gt=F('like_count'))
# SQL: WHERE view_count > like_count
# 原子更新 —— 避免競態條件(Race Condition)
# 錯誤做法:在高並發下可能丟失更新
article = Article.objects.get(pk=1)
article.view_count += 1 # 在 Python 層面加 1
article.save()
# 正確做法:使用 F 表達式,直接在資料庫層面加 1
Article.objects.filter(pk=1).update(view_count=F('view_count') + 1)
# SQL: UPDATE article SET view_count = view_count + 1 WHERE id = 1
# F 表達式支援數學運算
Article.objects.annotate(
engagement_score=F('view_count') + F('like_count') * 2
)
# 搭配時間運算
from datetime import timedelta
# 找出更新時間比建立時間晚超過 7 天的文章(代表經過重大修改)
Article.objects.filter(
updated_at__gt=F('created_at') + timedelta(days=7)
)
鏈式查詢(Method Chaining)
QuerySet 的每個方法都會回傳一個新的 QuerySet,因此你可以將多個操作串接在一起,形成可讀性高且功能強大的查詢鏈:
from django.db.models import Q, F, Count, Avg
# 鏈式查詢範例:取得一個分析報表
results = (
Article.objects
.filter(status='published') # 只看已發布的
.filter(created_at__year=2026) # 2026 年的文章
.exclude(view_count=0) # 排除零瀏覽
.annotate(comment_count=Count('comments')) # 加上留言數
.filter(comment_count__gt=5) # 留言超過 5 則
.order_by('-view_count') # 依瀏覽次數降冪排序
[:10] # 取前 10 筆
)
# 動態組建查詢
qs = Article.objects.all()
if category:
qs = qs.filter(category=category)
if search:
qs = qs.filter(
Q(title__icontains=search) | Q(content__icontains=search)
)
if sort == 'popular':
qs = qs.order_by('-view_count')
elif sort == 'latest':
qs = qs.order_by('-created_at')
# 最終只會執行一條 SQL
articles = list(qs[:20])
由於 QuerySet 的惰性求值特性,上面不管串了多少個 filter()、exclude()、order_by(),在最後轉換成 list 之前都不會執行任何 SQL。Django 會在求值的瞬間,將所有條件合併成一條最佳化的 SQL 語句。
實用技巧補充
values() 與 values_list()
# values() —— 回傳字典組成的 QuerySet
Article.objects.values('title', 'view_count')
# 輸出:<QuerySet [{'title': 'Django ORM 入門', 'view_count': 150}, ...]>
# values_list() —— 回傳 tuple 組成的 QuerySet
Article.objects.values_list('title', 'view_count')
# 輸出:<QuerySet [('Django ORM 入門', 150), ...]>
# flat=True —— 當只取一個欄位時,回傳扁平化列表
titles = Article.objects.values_list('title', flat=True)
# 輸出:<QuerySet ['Django ORM 入門', 'QuerySet 進階', ...]>
distinct() 去除重複
# 取得所有不重複的 status 值
statuses = Article.objects.values_list('status', flat=True).distinct()
# 輸出:<QuerySet ['draft', 'published', 'archived']>
raw() 原生 SQL
# 當 ORM 無法滿足需求時,可以使用原生 SQL
articles = Article.objects.raw('SELECT * FROM blog_article WHERE view_count > %s', [100])
for article in articles:
print(article.title)
總結
Django ORM 的 QuerySet 是一套功能完整且效能優秀的資料庫查詢介面。透過本文的介紹,你應該已經掌握了以下核心知識:
- 惰性求值 讓 QuerySet 在真正需要資料時才執行 SQL,搭配鏈式查詢可以組建出高效率的查詢
- CRUD 操作 涵蓋了
create()、save()、bulk_create()、filter()、get()、update()、delete()等完整的資料操作方法 - Field Lookups 提供了
exact、contains、gt、in、range、isnull等豐富的過濾條件 - 聚合與注釋 讓你在資料庫層面完成統計計算,避免將大量資料載入 Python 記憶體
- Q 物件 解決了 OR 和 NOT 邏輯的查詢需求,支援動態條件組合
- F 表達式 實現欄位間比較與原子操作,避免競態條件
熟練掌握這些 QuerySet 方法,你就能用 Python 優雅地處理絕大多數的資料庫查詢需求,大幅提升 Django 開發效率。