商品分類システムにおける複雑な条件式(AND/OR混在)をDB設計とバックエンドでどう扱うか
商品分類システムを開発しているのですが、特徴による分類のために「AかつB」「AまたはB」「(AまたはB)かつC」など、AND/ORが混在した複雑な特徴を登録する必要が出てきました。 本記事では、こうした複雑な条件式をデータベース設計レベルでどのように実装したのかについて解説します。
1. なぜAND/OR混在の条件式が必要なのか?
例えばロレックスの時計モデル分類で説明すると、
- 「文字盤がブラックまたはブルー」かつ「ジュビリーブレスレット」
- 「ベゼルがフルーテッドベゼルまたはスムースベゼル」かつ「限定モデル」
など、複数属性に対してOR条件を組み合わせてANDで判定したいケースが多くあります。
2. DB設計:条件式を柔軟に表現する
従来の設計
従来は「variation_property_conditions」テーブルに条件をフラットに登録し、すべてAND判定していました。
variation_property_conditions
----------------------------
variation_id | property_key_id | property_value_id | value
----------------------------------------------------------
1 | dial | black | NULL
1 | bracelet | jubilee | NULL
AND/OR対応の拡張
複雑な条件式を扱うために、operator(AND/OR)カラムやgroup_idカラムを追加します。
- operator: この条件がANDかORかを指定
- group_id: OR条件をグループ化し、(A or B) and (C or D) のような式を表現
例:(黒文字盤 OR 青文字盤) AND (ジュビリーブレスレット OR オイスターブレスレット) AND フルーテッドベゼル
variation_property_conditions
---------------------------------------------------------------
variation_id | property_key_id | property_value_id | value | operator | group_id
-----------------------------------------------------------------------------
1 | dial | black | NULL | OR | 1
1 | dial | blue | NULL | OR | 1
1 | bracelet | jubilee | NULL | OR | 2
1 | bracelet | oyster | NULL | OR | 2
1 | bezel | fluted | NULL | AND | 3
3. 分類ロジック設計:条件式の判定ロジック
バックエンド側では、DBから取得した条件をgroup_idごとにグループ化し、 operatorに応じて判定します。
- ANDグループは全て一致
- ORグループは1つでも一致すればOK
- すべてのグループの判定結果をANDでまとめることで、複雑な論理式も表現可能
from collections import defaultdict
def match_variation(variation_id, classification_result, db):
conds = db.query(...).filter(...).all()
groups = defaultdict(list)
for cond in conds:
groups[cond.group_id].append(cond)
for group_id, cond_list in groups.items():
operator = cond_list[0].operator
results = [is_condition_matched(cond, classification_result, db) for cond in cond_list]
if operator == "AND" and not all(results):
return False
if operator == "OR" and not any(results):
return False
return True
4. まとめ
実は実際のプロダクトでは現状group_idは導入しておらず、ORグループは1つしか使えない設計です。 これは
- 実用の上でORで判断したいグループが複数必要な場合がほぼ無いこと
- 運用上、複数のORグループに対応してもユーザー目線で管理画面が煩雑になるデメリットの方が大きい
というのが主な理由です。
5. さらなる高みへ
じつは、3で説明したoperatorとgroup_idの運用では、「入れ子の括弧がある条件式(例:(A or (B and C)) and D)」には対応できていません。
さらに複雑な条件をDBで管理するにはdepthなどの概念を持ち込む必要がありそうですが、今回の開発には必要ないので、また別の機会に考えることにします。