Vegas Engineering by FORBIS 記事一覧
← 記事一覧へ戻る
Architecture ・ 第18回

検索を"設定"で組む — SQLテンプレート+動的WHERE+一時表の設計

検索クエリを外部ファイル化し、条件で動的に組み立て、結果を一時表に置いてページングする。柔軟で速い検索基盤。

Vegas 開発チーム ・ 2026-07-03 ・ 読了 7分

WHYなぜ検索を"設定"にするのか

通販基幹では、受注・会員・商品といった主要ドメインごとに一覧検索が無数に存在する。これらをアプリのコードに直接埋め込むと、検索条件が一つ増えるたびにビルドとデプロイが必要になり、レビューの粒度も粗くなる。そこで私たちは検索の中核であるSQLを外部ファイル(テンプレート)として切り出し、"設定"として管理する方針を採る。

FLOW組み立てから取得までの流れ

検索リクエストは、テンプレートの読み込み、条件による置換、実行、結果の一時表への格納、そしてページング取得という一連のパイプラインを通る。一度実行した結果を一時表に固定することで、ページ送りのたびに重い本クエリを再実行せずに済む。読み取り最適化DB(CQRSの読み側)と組み合わせれば、集計や結合を含む検索でも安定した応答が得られる。

SQLテンプレ {@param} 条件で動的置換 型別・束縛 実行 結果を一時表 検索ID ページング取得 検索パイプライン
図:テンプレ→置換→実行→一時表→ページング の一方向パイプライン。結果を固定するので、ページ送りで本クエリを再実行しない。

DYNAMIC動的WHEREの組み立て

テンプレートには {@param} のようなプレースホルダを埋め込む。置換時には値の型に応じて、数値はそのまま、文字列はクォート付き、複数値は IN 展開、未指定の任意条件はブロックごと削除、といった具合に組み立てる。JSON項目の抽出条件なども、DBの関数式に展開して同じ枠組みで扱える。任意条件をON/OFFできるため、WHERE句は利用者の指定に応じて伸縮する。

条件すべてOFF WHERE 1=1 条件ON(3件) AND status = ? AND kind IN (?, ?) AND created >= ? 指定に応じてWHEREが伸縮する
図:WHEREの伸縮。任意条件はON/OFFで着脱し、実際の値はプレースホルダ束縛で渡す。

CODEテンプレと安全な置換

テンプレートは静的な骨格と、束縛対象のプレースホルダで構成する。動的部分は「構造の生成」と「値の束縛」を厳密に分離する。値そのものを文字列連結でSQLに埋め込んではならない。

order_search.sql.tmplSQL
SELECT id, status, created
FROM   order_read
WHERE  1=1
{@cond_status}   -- AND status = :status
{@cond_kind}     -- AND kind IN (:kind_list)
{@cond_from}     -- AND created >= :from
ORDER BY created DESC
build_query.pseudoPseudo
frag = []; binds = {}
# 任意条件はキーがある時だけ追加。値は必ず束縛で渡す
if has(input, "status"):
    frag += "AND status = :status"
    binds["status"] = as_str(input.status)     # 型検証
if has(input, "kind"):
    ph = expand_in(input.kind, binds, "kind")  # IN展開も束縛
    frag += "AND kind IN (" & ph & ")"
# カラム名/並び順はホワイトリスト照合のみ許可
col = whitelist(input.sort, allowed = ["created", "id"])
sql = render(template, frag)                    # 構造だけ差し込み
run(sql, binds)                                 # 値は束縛で

TRADEOFFトレードオフと後始末

この方式の最大のリスクは、文字列置換がそのままSQLインジェクションの温床になり得ることだ。動的に組み立てるのは「SQLの構造」だけに限り、利用者が与える「値」はプレースホルダ束縛で必ず分離する。テンプレート側で扱わざるを得ないカラム名・並び順・演算子は、ホワイトリスト照合を通したものだけを許可する。入力は型検証を経てから束縛する。

TAKEAWAY

検索SQLを外部テンプレート化し、条件で構造だけを動的に組み立て、値はプレースホルダ束縛で分離する。結果は一時表に固定してページング取得する。SQLを"設定"としてレビュー可能にしつつ、インジェクション対策(束縛・型検証・ホワイトリスト)と一時表の後始末を設計の前提に置くことが要点だ。