Nếu bạn đang dùng Flask-SQLAlchemy 3.x mà vẫn viết Post.query.filter(…).all(), bạn đang dùng giao diện truy vấn cũ của SQLAlchemy 1.x. Cách chuẩn hiện nay là db.select() kết hợp db.session.execute(), kèm db.session.get() để lấy một bản ghi theo khóa chính. Bài này đổi một truy vấn mẫu sang kiểu mới, để code thống nhất với tài liệu SQLAlchemy 2.0 và dễ mở rộng về sau.
Vấn đề
Kiểu cũ đọc ngắn gọn, nhưng nó thuộc về thời SQLAlchemy 1.x. Bạn viết truy vấn qua thuộc tính query của model, mỗi model một kiểu truy cập riêng, và khi cần kết hợp nhiều bảng, gom nhóm hay dùng hàm tổng hợp, cú pháp cũ nhanh chóng trở nên chắp vá. Nghiêm trọng hơn, Post.query.get(id) là cách lấy bản ghi cũ: nó đi qua lớp Query trung gian thay vì gọi thẳng session, nên khi nâng cấp hoặc đọc tài liệu mới, bạn sẽ thấy mọi ví dụ đều viết khác code của mình. Dự án càng lớn, hai kiểu truy vấn song song càng khó bảo trì: người mới vào team phải học cả cách cũ lẫn cách mới chỉ để đọc một file models.
from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), nullable=False) views = db.Column(db.Integer, default=0)popular = Post.query.filter(Post.views >= 30).order_by(Post.views.desc()).all()one = Post.query.get(1)
Giải pháp
Kiểu SQLAlchemy 2.0 viết truy vấn như một câu lệnh rõ ràng: db.select(Post) mô tả lấy gì, .where() lọc, .order_by() sắp xếp, rồi db.session.execute() chạy câu lệnh đó. Kết quả trả về là các hàng, nên gọi .scalars().all() để lấy thẳng danh sách đối tượng Post, không còn lớp vỏ bọc trung gian. Muốn lấy một bản ghi theo id, dùng db.session.get(Post, 1): ngắn hơn, rõ hơn và là API chuẩn của session. Muốn đếm tổng số bài, dùng db.select(db.func.count()).select_from(Post) rồi .scalar_one(), thay vì len() trên cả danh sách đã tải về.
popular = db.session.execute( db.select(Post) .where(Post.views >= 30) .order_by(Post.views.desc())).scalars().all()one = db.session.get(Post, 1)total = db.session.execute( db.select(db.func.count()).select_from(Post)).scalar_one()
Một lợi ích khác: vì truy vấn là đối tượng select độc lập, bạn có thể xây nó từng phần — bắt đầu từ stmt = db.select(Post), rồi chỉ thêm .where() khi người dùng thực sự truyền tham số lọc.
Áp dụng vào dự án của bạn
Đừng đổi toàn bộ codebase trong một lần. Hãy đặt quy tắc: code mới viết bằng db.select(), code cũ đổi dần mỗi khi bạn chạm vào file đó. Bắt đầu từ ba chỗ hay gặp nhất: các hàm lấy danh sách có lọc và sắp xếp, các chỗ dùng Model.query.get() và các chỗ đếm bằng len(query.all()). Mỗi lần đổi, chạy lại test của route đó — kết quả trả về phải giống hệt trước khi đổi, vì đây là đổi cú pháp, không phải đổi logic. Khi cả team đã quen, bạn sẽ thấy code truy vấn đọc thống nhất từ model này sang model khác, và tài liệu SQLAlchemy mới đọc tới đâu áp dụng được tới đó.
Bài viết liên quan
- Flask: dùng Application Factory (create_app) để app dễ test, dễ mở rộng ngay từ đầu
- Flask: sửa lỗi N+1 query trong Flask-SQLAlchemy bằng joinedload và selectinload
- Flask: phân trang kết quả truy vấn với db.paginate(), khỏi viết tay offset/limit
Câu hỏi thường gặp
Model.query còn chạy trên Flask-SQLAlchemy 3.x không?
Còn chạy, nhưng đó là giao diện tương thích ngược của thời SQLAlchemy 1.x. Tài liệu hiện hành viết mọi ví dụ bằng select(), nên code mới nên dùng kiểu mới để khỏi phải học hai hệ cú pháp.
.scalars() để làm gì, bỏ đi được không?
Không nên bỏ. execute() trả về các hàng kết quả, còn .scalars() bóc lấy đối tượng ở cột đầu tiên của mỗi hàng. Thiếu nó, bạn nhận về Row chứ không phải Post, và truy cập post.title sẽ lỗi.
session.get() khác gì filter_by(id=…).first()?
session.get() tra cứu trực tiếp theo khóa chính và có thể tận dụng đối tượng đã có sẵn trong session, nên thường gọn và nhanh hơn. filter_by() phù hợp khi điều kiện không phải khóa chính, ví dụ lọc theo slug hay email.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Tài liệu Flask-SQLAlchemy: Queries — https://flask-sqlalchemy.palletsprojects.com/en/stable/queries/
- Tài liệu SQLAlchemy 2.0: Selecting ORM Entities — https://docs.sqlalchemy.org/en/20/orm/queryguide/select.html
