第17章:複数範囲クエリと制約(2026の重要ポイント)📏✨
この章のゴールは「検索フィルタを増やしたくなった時に、その組み合わせがFirestoreで行けるか/行けないかを即判断できる」状態になることだよ😄🔎 (そして、無理なら保存の形を変える発想に切り替えられるようになる💡)
読む:複数範囲クエリって何?どう使う?🧠📚
1) “範囲/不等号フィルタ”ってどれ?👀
Firestoreで「範囲っぽい」やつはだいたいこの仲間👇
>,>=,<,<=(いわゆる範囲)!=(一致しない)not-in(このリスト以外)- (文脈によっては
in/array-contains-any/orも “分岐” が増えて重くなりやすい仲間)
Firestoreの制約は、ざっくり言うと 「インデックスで高速にできる形しか許さない」 から来てるよ🗂️⚡
2) 2026の激アツ変更:複数フィールドで範囲ができるようになった🔥
昔は「範囲(不等号)は1フィールドだけ」って縛りが強かったけど、今は 複数フィールドに対して範囲/不等号を組み合わせられる ように整理されてるよ📈
公式の “複数範囲” ガイドも更新されていて、例としてこんな形が載ってる👇(salary と experience の両方で範囲)(Firebase)
ポイントは 範囲をかけたフィールドを orderBy にも入れる こと!🧷
3) ただし重要:orderBy の順番が “コスト” を左右する💸
複数範囲クエリでは、どの複合インデックスが使われるかが超重要で、orderBy の並べ方で スキャンするインデックス件数 が増減するよ😵💫
公式でも「より絞れる(=選択性が高い)条件のフィールドを先に orderBy に置く」と効率が良い、って説明されてる(Firebase)
迷ったら: “絞り込みが強い方(結果が少なくなる方)を先に orderBy” → コスト下がりやすい💡(Firebase)
4) “複数範囲” とは別に、昔からの制約も普通に残ってる⚠️

ここ、検索UIを作る時にめちゃ踏みがち🥹
not-inはin/array-contains-any/orと同じクエリで併用できない(Firebase)not-inと!=を 同時に使えない(Firebase)not-inまたは!=は 1クエリにつき1回だけ(Standard edition の制約)(Firebase)OR系(or/in/array-contains-any)は内部で展開されて、最大30個の分岐まで(固定)(Firebase)- 不等号を使うと、そのフィールドは “存在するドキュメントだけ” が対象になりがち(
orderByも同様で、フィールド無しは結果から外れる)(Firebase)
さらに、複数範囲クエリ側にも追加制約があるよ👇
- 範囲/不等号フィールドは 最大10個まで(高コスト化防止)(Firebase)
- 「ドキュメントID(
__name__)に等価条件だけ」+「他フィールドに範囲/不等号」は サポートされない(Firebase)
手を動かす:検索フィルタUIを想定して、許される組み合わせを作る🧩🛠️
ここからは「記事一覧(posts)」でよくある検索を作る想定でいくよ😄📰
ステップ1:まず “やりたいフィルタ” を書き出す📝
例👇
status == "published"(等価)authorId == 自分(等価)createdAtをfrom〜to(範囲)likesCount >= 10(範囲)tagを含む(配列系:array-contains)
ステップ2:分類する(これができると勝ち🏆)

- 等価:
== - 範囲/不等号:
> >= < <= != not-in - OR系:
or / in / array-contains-any - 配列:
array-contains(これも癖ある)
この分類ができると「あ、ここで詰まりそう」が事前に見える👀✨
ステップ3:複数範囲クエリを “正しい型” で書く✍️(TypeScript)

例として「作成日レンジ」+「いいね数レンジ」の2軸フィルタをやるよ📅❤️
- まず where で範囲
- 次に 範囲をかけたフィールドを orderBy に入れる
orderByの順は より絞れる方を先 が基本(コスト意識)(Firebase)
import {
collection,
query,
where,
orderBy,
limit,
getDocs,
Timestamp,
} from "firebase/firestore";
import { db } from "./firebase";
// 例:UIから来た値
const from = Timestamp.fromDate(new Date("2026-02-01"));
const to = Timestamp.fromDate(new Date("2026-02-16"));
const minLikes = 10;
const postsRef = collection(db, "posts");
// createdAt と likesCount の “複数範囲”
// ✅ 範囲をかけたフィールドを orderBy に入れるのがコツ
const q = query(
postsRef,
where("createdAt", ">=", from),
where("createdAt", "<=", to),
where("likesCount", ">=", minLikes),
// どっちが絞れる?で順番を考える(例:likesCount>=10 の方が絞れるなら先)
orderBy("likesCount", "desc"),
orderBy("createdAt", "desc"),
limit(20)
);
const snap = await getDocs(q);
const posts = snap.docs.map(d => ({ id: d.id, ...d.data() }));
この “型” は、公式の複数範囲の説明(範囲に使ったフィールドを orderBy に入れる)と同じ方向性だよ(Firebase)
ステップ4:インデックス最適化の考え方(地味に差が出る)🧠⚙️

複数範囲クエリは 複合インデックスが絡みやすい。
しかも orderBy の順が悪いと、結果は同じでも 大量のインデックスを読んで捨てる みたいなことが起きる😇
公式の最適化ガイドだと、
Query Explainで実行計画(どのインデックス使ったか、何件スキャンしたか)を見て改善できる(Firebase)orderByを “より選択性の高い制約から先に” で効率が上がる(Firebase)
って流れが紹介されてるよ📉✨
(補足:Query Explain の実行例はサーバーSDK側(Nodeなど)で紹介されてる(Firebase))
ミニ課題:この検索、Firestoreだけでいける?🤔🧪
次の6つを A:そのまま行けそう / B:設計工夫が必要 / C:そのままは無理寄り で仕分けしてみてね👇✨
status == "published"ANDcreatedAt from〜tocreatedAt from〜toANDlikesCount from〜to(2軸レンジ)tags array-contains "firebase"ANDtags array-contains "react"(配列2本)category in ["tech","life"]ANDtag array-contains "firebase"authorId != "u123"ANDauthorId not-in ["u777","u888"](status=="published" OR status=="scheduled")ANDcreatedAt >= from
解答の目安(チラ見OK👀)
- 1:A(普通に定番)
- 2:A(複数範囲OK!ただし
orderByとインデックス意識)(Firebase) - 3:C(
array-containsは ORグループ内で数や組み合わせ制約が強い。設計変更しがち)(Firebase) - 4:B(OR展開(DNF)や制約に引っかかる可能性。分岐数や組み合わせに注意)(Firebase)
- 5:C(
not-inと!=の併用NG)(Firebase) - 6:B(ORは最大30分岐、展開で重くなる。要注意)(Firebase)
チェック:ここまでできたらOK✅🎉
- 「このフィルタは等価 / 範囲 / OR / 配列」って分類できる😄
- 複数範囲をやる時、範囲に使ったフィールドを
orderByに入れるのが自然に出る🧷(Firebase) not-in/!=/in/array-contains-any/orの “併用制約” を知ってる⚠️(Firebase)orderByは “フィールドが無いドキュメントは落ちる” を覚えてて、必須フィールド設計に意識が向く🧱(Firebase)
AIで設計レビュー&実装を爆速にする🤖⚡

「検索の組み合わせで詰まる前に、AIに“ルールチェック”させる」のが強いよ🧠✨ (エージェント型の開発環境やCLIエージェントが、設計レビューと相性良い)(Google Codelabs)
Gemini CLI に投げる依頼例(コピペ用📎)
Firestore の posts 検索を作りたい。
フィルタ候補:
- status == "published"
- createdAt from-to
- likesCount min-max
- tags (array-contains)
この組み合わせで「できる/できない」を分類して、
できない場合の代案(検索用フィールド案)を3つ提案して。
さらに、複数範囲クエリが必要なケースは orderBy の並びと、
必要になりそうな複合インデックスの方向性も書いて。
Firebaseの生成AI機能を絡める時の“事故防止”メモ🛡️
生成AIをアプリに入れると「コスト爆発(Denial-of-Wallet)」が怖いから、App Check + レート制限をセットで意識すると安心だよ😇 Firebase側はAI機能に **ユーザー単位のレート制限(デフォルト100 RPM)**などの考え方が整理されてる(Firebase)
次の第18章(ページング設計📜)に行くと、この章の「orderByの選び方」がそのまま “無限スクロールの安定性” に直結してくるよ🔥