在編寫業務邏輯代碼的時候, 我不幸遇到下面的表結構(已經將主要邏輯抽離出來了):python
class Category(Model): __tablename__ = 'category' # 分類ID id = Column(Integer, primary_key=True, autoincrement=True) # 分類名稱 name = Column(String(length=255)) class Product(Model): __tablename__ = 'product' # 產品 ID id = Column(Integer, primary_key=True, autoincrement=True) # 產品名稱 name = Column(String(length=255)) # 分類 ID category_id = Column(Integer)
如今須要實現的業務是返回分類的列表結果:sql
[ { "id": 1, "name": "分類1", "product_count": 1 }, ... ]
這是一個一對多的模型.
通常的笨拙思路就是:數據庫
data = [] categorys = Category.query.all() for category in categorys: product_count = len(Product.query.filter(Product.category_id == category.id).all()) data.append({ 'id': category.id, 'name': category.name, 'product_count': product_count })
明眼人一看就知道能夠把len(Product.query.filter(Product.category_id == category.id).all())
換成:json
product_count = Product.query.filter(Product.category_id == category.id).count()
可是, 根據這篇文章:[Why is SQLAlchemy count() much slower than the raw query?
](https://stackoverflow.com/que... 彷佛這樣寫會有更好的性能:網絡
from sqlalchemy import func session.query(func.count(Product.id)).filter(Product.category_id == category.id).scalar()
可是, 稍微有點經驗的人就會對上面的寫法嗤之以鼻, 由於product_count
是放在for category in categorys:
裏面的, 這意味着若是categorys
有成千上萬個, 就要發出成千上萬個session.query()
, 而數據庫請求是在網絡上的消耗, 請求時間相對較長, 有的數據庫沒有處理好鏈接池, 創建鏈接和斷開鏈接又是一筆巨大的開銷, 因此 query 的請求應該越少越好. 像上面這樣把 query 放到 for 循環中顯然是不明智的選擇.
因而有了下面一個請求的版本:session
result = db.session.query(Product, Category) \ .filter(Product.category_id == Category.id)\ .order_by(Category.id).all() id_list = [] data = [] for product, category in result: if category and product: if category.id not in id_list: id_list.append(category.id) data.append({ 'id': category.id, 'name': category.name, 'product_count': 0 }) idx = id_list.index(category.id) data[idx]['product_count'] += 1
這樣的寫法十分難看, 並且一樣沒有合理利用 SQLAlchemy 的 count 函數. 因而改爲:app
product_count = func.count(Product.id).label('count') results = session.query(Category, product_count) \ .join(Product, Product.category_id == Category.id) \ .group_by(Category).all() data = [ { 'id': category.id, 'name': category.name, 'product_count': porduct_count } for category, product_count in results]
不過這裏還有一個問題, 就是若是先添加一個Category
, 而屬於這個Category
下沒有Product
, 那麼這個Category
就不會出如今data
裏面, 因此join
必須改爲outerjoin
. 即:函數
results = session.query(Category, product_count) \ .outerjoin(Product, Product.category_id == Category.id) \ .group_by(Category).all()
需求又來了!!!
如今考慮設計Product
爲僞刪除模式, 即添加一個is_deleted
屬性判斷Product
是否被刪除.
那麼count
函數就不能簡單地count(Product.id)
, 而是要同時判斷Product.is_deleted
是否爲真和Product
是否爲None
, 通過悉心研究, 發現使用func.nullif
能夠實現這個需求,即用下面的寫法:性能
product_count = func.count(func.nullif(Product.is_deleted.is_(False), False)).label('count') results = session.query(Category, product_count) \ .join(Product, Product.category_id == Category.id) \ .group_by(Category).all() data = [ { 'id': category.id, 'name': category.name, 'product_count': porduct_count } for category, product_count in results]
可見使用 ORM 有的時候仍是須要考慮不少東西.scala