Django ORM 與 QuerySet 查詢語法完全指南 | Django 教學

2026/05/31 2026/05/24
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 關係。當你需要 ORNOT 邏輯時,就必須使用 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 提供了 exactcontainsgtinrangeisnull 等豐富的過濾條件
  • 聚合與注釋 讓你在資料庫層面完成統計計算,避免將大量資料載入 Python 記憶體
  • Q 物件 解決了 OR 和 NOT 邏輯的查詢需求,支援動態條件組合
  • F 表達式 實現欄位間比較與原子操作,避免競態條件

熟練掌握這些 QuerySet 方法,你就能用 Python 優雅地處理絕大多數的資料庫查詢需求,大幅提升 Django 開發效率。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。