1、為什么可以用 模型類.query
來查詢數據庫?
在 Flask 中使用 SQLAlchemy ORM 時,所有繼承自 db.Model
的模型類都會自動獲得一個 query
屬性。
其本質是 db.session.query(模型類)
的快捷方式,無需顯式操作 db.session
。
代碼示例,比如定義一個繼承自 db.Model
的模型類 CommonModel
:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # 初始化 SQLAlchemy 實例 # 定義模型類(繼承自 db.Model)
class CommonModel(db.Model): id = db.Column(db.Integer, primary_key=True) # 其他字段...
以下兩種寫法等價
# 寫法 1:通過模型類的 query 屬性(隱式綁定會話)
result1 = CommonModel.query.filter_by(field='value').first() # 寫法 2:通過 db.session 顯式綁定模型類
result2 = db.session.query(CommonModel).filter_by(field='value').first()
2、基礎查詢
2.1、如何定義查詢:設置過濾條件
2.1.1、get(primary_key)
:按主鍵精準定位
- 作用:通過模型類的主鍵(如
id
)直接定位單條記錄,是主鍵查詢的最優選擇。 - 優勢:直接利用數據庫主鍵索引,查詢效率最高。
- 示例(以
User
模型為例):user = User.query.get(1) # 查詢 ID 為 1 的用戶(存在返回實例,不存在返回 None)
2.1.2、filter_by(**kwargs)
:基于關鍵字的簡單等值過濾
- 作用:通過模型類屬性的等值條件(如
username='alice'
)過濾記錄。 - 限制:僅支持等值查詢(
=
),無法處理范圍(>
,<
)或模糊查詢(LIKE
)。 - 示例(以
User
模型為例):active_users = User.query.filter_by(is_active=True).all() # 查詢所有活躍用戶
2.1.3、filter(條件表達式)
:靈活的多條件查詢
- 作用:支持范圍、模糊、邏輯與/或等復雜條件,是
filter_by
的增強版。 - 語法:通過模型類屬性構建表達式(如
User.age > 18
)。 - 示例(以
User
模型為例):from datetime import datetime from sqlalchemy import or_ # 查詢 2023 年后注冊且郵箱以 'example.com' 結尾的用戶(范圍+模糊) users = User.query.filter( User.registered_at > datetime(2023, 1, 1), User.email.like('%@example.com') ).all() # 查詢年齡大于 18 或用戶名含 'test' 的用戶(邏輯或) users = User.query.filter( or_(User.age > 18, User.username.like('%test%')) ).all()
2.1.4、order_by(字段表達式)
:按字段排序
- 作用:指定結果的排序規則(正序 / 倒序)。
- 語法:通過模型類屬性指定排序字段(如 User.registered_at.desc() 倒序)。
- 示例:
# 按注冊時間倒序(最新注冊在前)
recent_users = User.query.order_by(User.registered_at.desc()).all() # 先按年齡正序,再按注冊時間倒序(多字段排序)
users = User.query.order_by(User.age.asc(), User.registered_at.desc()).all()
2.1.5、with_entities(字段列表)
:僅查詢指定字段
- 作用:僅返回指定字段的數據(減少數據傳輸量)。
- 示例:
# 僅查詢用戶的 ID 和用戶名
user_ids = User.query.with_entities(User.id, User.username).all() # 返回 [(1, 'alice'), (2, 'bob')...]
2.2、如何獲取結果:提取查詢數據
定義查詢條件后,需通過方法提取最終結果集(單條、多條、統計值或特定聚合值)。
2.2.1、first()
:獲取首條記錄
- 作用:返回查詢結果中的第一條記錄(無結果返回
None
)。 - 適用場景:已知查詢結果最多一條(如用戶登錄驗證)。
- 示例(以
User
模型為例):user = User.query.filter_by(username='alice').first() # 查找用戶名為 'alice' 的用戶(可能不存在)
2.2.2、all()
:獲取所有符合條件的記錄
- 作用:返回查詢結果的所有記錄(列表,無結果返回空列表
[]
)。 - 適用場景:需要批量處理數據(如展示用戶列表)。
- 示例(以
User
模型為例):users = User.query.filter(User.age > 18).all() # 查找所有年齡大于 18 的用戶(可能為空列表)
2.2.3、one()
:嚴格獲取單條記錄(無結果或多結果拋異常)
- 作用:要求查詢結果必須恰好一條,否則拋出異常(
NoResultFound
或MultipleResultsFound
)。 - 適用場景:業務邏輯要求結果唯一(如查詢“當前生效的配置”)。
- 示例(以
Config
模型為例):from sqlalchemy.exc import NoResultFound, MultipleResultsFound try: current_config = Config.query.filter_by(is_active=True).one() except NoResultFound: print("無生效配置!") except MultipleResultsFound: print("存在多個生效配置,需檢查數據!")
2.2.4、count()
:統計結果數量
- 作用:直接執行
SELECT COUNT(*)
,高效統計符合條件的記錄總數。 - 優勢:避免加載所有數據,性能優于
len(all())
。 - 示例(以
User
模型為例):
# 統計活躍用戶數量(返回整數)active_count = User.query.filter_by(is_active=True).count()
2.2.5、limit(n)
:限制返回數量
- 作用:僅返回前 n 條記錄。
- 示例:
# 獲取分數最高的前 5 個用戶
top_5_users = User.query.order_by(User.score.desc()).limit(5).all()
2.2.6、offset(n)
:跳過前 n 條記錄
- 作用:結合
limit
實現分頁(如跳過前 10 條,取后 10 條)。 - 示例:
# 第 2 頁數據(每頁 10 條)
page_2_users = User.query.order_by(User.id).offset(10).limit(10).all()
2.2.7、使用 paginate()
處理大數據分頁
通過 paginate()
封裝 offset
和 limit
,自動處理分頁邏輯,避免一次性加載大量數據:
# 獲取第 2 頁,每頁 10 條數據(自動處理越界)
page_obj = User.query.order_by(User.registered_at.desc()).paginate(page=2, per_page=10)
current_users = page_obj.items # 當前頁數據
total_pages = page_obj.pages # 總頁數
3、關聯查詢:處理表間關系與預加載優化
在實際項目中,模型類間通常存在關聯(如用戶與帖子的一對多關系)。
通過 relationship
字段定義關聯后,需結合預加載優化避免 N+1 查詢問題。
3.1、模型類關聯關系定義(以用戶-帖子為例)
class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200)) author_id = db.Column(db.Integer, db.ForeignKey('user.id')) # 外鍵關聯用戶 ID class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80)) # 定義一對多關系:user.posts 可獲取用戶所有帖子 posts = db.relationship('Post', backref='author', lazy='select') # backref 為 post.author 提供反向引用
backref
:在關聯模型(如Post
)中自動創建反向引用(post.author
可獲取用戶實例)。lazy
:控制關聯數據的加載時機(默認'select'
,即訪問時加載)。
3.2、預加載優化:避免 N+1 查詢
直接訪問關聯數據(如 user.posts
)時,SQLAlchemy 默認會觸發多次查詢(N+1 問題)。通過 joinedload
預加載,可一次性加載關聯數據,減少數據庫交互。
示例:未優化的 N+1 查詢
# 查詢 10 個用戶,并獲取他們的帖子(觸發 1(用戶查詢) + 10(帖子查詢)次 SQL)
users = User.query.limit(10).all()
for user in users: print(user.posts) # 每次循環觸發一次帖子查詢
示例:優化后的預加載查詢
from sqlalchemy.orm import joinedload # 一次性加載用戶及其帖子(僅 1 次 SQL)
users = User.query.options(joinedload(User.posts)).limit(10).all()
for user in users: print(user.posts) # 數據已預加載,無額外查詢