🔥

【Firebase】Cloud Firestore クエリ まとめ

7 min read

ウェブ版 CloudFirestoreのクエリ備忘録
日本語版公式ドキュメントに載っていないものがいくつかあったので、まとめてみた。
※コレクショングループについては書いていません。

前提となるデータ構造

Firestoreの中身は以下を想定

Firestoreのデータ構造
// コレクション
users: {
  // ドキュメント
  "000": {
    // フィールド
    address: {
      prefecture: "沖縄",
      region: "沖縄",
    },
    age: 20,
    createdAt: 2020111015:00:00 UTC+9,
    friends: ["003"],
    userName: "山田太郎",
  },
  "001": {
    address: {
      prefecture: "北海道",
      region: "北海道",
    },
    age: 20,
    createdAt: 202011812:00:00 UTC+9,
    friends: [],
    userName: "小島三郎",
  },
  "002": {
    address: {
      prefecture: "東京",
      region: "関東",
    },
    age: 19,
    createdAt: 20201119:14:00 UTC+9,
    friends: ["001"],
    userName: "山田花子",
  },
}

where:クエリ演算子

利用可能なクエリ一覧

演算子 説明 書き方
< ~より小さい .where("age", "<", 25)
<= ~以下 .where("age", "<=", 25)
== ~と等しい .where("userName", "==", "山田太郎")
!= ~と等しくない .where("userName", "!=", "山田太郎")
> ~より大きい .where("age", ">", 20)
>= ~以上 .where("age", ">=", 20)
array-contains 配列内に、右辺の要素が含まれている .where("friends", "array-contains", "003")
array-contains-any 配列内に、右辺の要素のいずれかが含まれている .where("friends", "array-contains-any", ["002", "003"])
in 右辺のいずれかが含まれている .where("friends", "in", ["001", "002"])
not-in 右辺のいずれも含まれない .where("friends", "not-in", ["001", "002"])

<:~より小さい

未成年(20歳未満)のユーザを抽出

未成年のユーザを取得.js
const db = firebase.firestore()
db.collection("users").where("age", "<", 20).get().then(snapShot => {
    snapShot.forEach(doc => {
        console.log(`${doc.id}: ${doc.data().userName}`);
    })
})

上記の結果は以下の通り。

抽出結果
002: 山田花子

<=:~以下

ユーザIDが001以下のユーザを抽出

firebase.firestore.FieldPath.documentId()でドキュメントIDを参照できる

未成年のユーザを取得.js
const db = firebase.firestore()
db.collection("users").where(firebase.firestore.FieldPath.documentId(), "<=", "001").get().then(snapShot => {
    snapShot.forEach(doc => {
        console.log(`${doc.id}: ${doc.data().userName}`);
    })
})

上記の結果は以下の通り。

抽出結果
000: 山田太郎
001: 小島三郎

==:~と等しい

東京在住のユーザを抽出

フィールド内のオブジェクトにあるプロパティはjsで参照するときと同様に、オブジェクト名.プロパティ名で参照できる

東京在住のユーザを抽出.js
const db = firebase.firestore()
db.collection("users").where("address.prefecture", "==", "東京").get().then(snapShot => {
    snapShot.forEach(doc => {
        console.log(`${doc.id}: ${doc.data().userName}`);
    })
})

上記の結果は以下の通り。

抽出結果
002: 山田花子

!=:~と等しくない

東京に住んでいないユーザを抽出

東京在住のユーザを抽出.js
const db = firebase.firestore()
db.collection("users").where("address.prefecture", "!=", "東京").get().then(snapShot => {
    snapShot.forEach(doc => {
        console.log(`${doc.id}: ${doc.data().userName}`);
    })
})

上記の結果は以下の通り。

抽出結果
000: 山田太郎
001: 小島三郎

>=:~以上

作成日が2020年11月9日以降のユーザを抽出

FirebaseのTimestamp型は、日付オブジェクトをクエリに利用できる

作成日が2020年11月9日以前のユーザを抽出.js
// 2020年11月9日の日付インスタンスを生成
const targetDate = new Date("2020-11-09");

const db = firebase.firestore();
db.collection("users").where("createdAt", ">=", targetDate).get().then(snapShot => {
  snapShot.forEach(doc => {
    console.log(`${doc.id}: ${doc.data().userName}`);
  })
})

上記の結果は以下の通り。

抽出結果
000: 山田太郎

array-contains:配列内に、右辺の要素が含まれている

ユーザID「003」がフレンドのユーザを抽出

ユーザID「003」がフレンドのユーザを抽出.js
const db = firebase.firestore();
db.collection("users").where("friends", "array-contains", "003").get().then(snapShot => {
  snapShot.forEach(doc => {
    console.log(`${doc.id}: ${doc.data().userName}`);
  })
})

上記の結果は以下の通り。

抽出結果
000: 山田太郎

array-contains-any:配列内に、右辺の要素のいずれかが含まれている

ユーザID「002, 003, 004」のいずれかがフレンドのユーザを抽出

ユーザID「003」がフレンドのユーザを抽出.js
const db = firebase.firestore();
db.collection("users").where("friends", "array-contains-any", ["001", "002", "003"]).get().then(snapShot => {
  snapShot.forEach(doc => {
    console.log(`${doc.id}: ${doc.data().userName}`);
  })
})

上記の結果は以下の通り。

抽出結果
000: 山田太郎

in:右辺のいずれかが含まれている

ユーザ名が「山田太郎」もしくは「山田花子」を抽出

ユーザ名が「山田太郎」もしくは「山田花子」を抽出.js
const db = firebase.firestore();
db.collection("users").where("userName", "in", ["山田太郎", "山田花子"]).get().then(snapShot => {
  snapShot.forEach(doc => {
    console.log(`${doc.id}: ${doc.data().userName}`);
  })
})

上記の結果は以下の通り。

抽出結果
000: 山田太郎
002: 山田花子

not-in:右辺のいずれも含まれない

ユーザ名が「山田太郎」でも「山田花子」でもないユーザを抽出

ユーザ名が「山田太郎」もしくは「山田花子」を抽出.js
const db = firebase.firestore();
db.collection("users").where("userName", "not-in", ["山田太郎", "山田花子"]).get().then(snapShot => {
  snapShot.forEach(doc => {
    console.log(`${doc.id}: ${doc.data().userName}`);
  })
})

上記の結果は以下の通り。

抽出結果
001: 小島三郎

複合クエリ(And検索)

.where().where()のようにつなげることでAnd検索が可能

20代のユーザを抽出.js
const db = firebase.firestore();
db.collection("users").where("age", ">=", 20).where("age", "<=", 29).get().then(snapShot => {
  snapShot.forEach(doc => {
    console.log(`${doc.id}: ${doc.data().userName}`);
  })
})

上記の結果は以下の通り。

抽出結果
000: 山田太郎
001: 小島三郎

orderBy:並び替え

ソートはorderByで行う。orderBy("age")とすることで、ageで昇順ソート、orderBy("age", "desc")とすると、ageで降順ソートされる。

年齢を昇順で表示

年齢で昇順ソート.js
const db = firebase.firestore();
db.collection("users").orderBy(`age`).get().then(snapShot => {
  snapShot.forEach(doc => {
    const data = doc.data()
    console.log(`${data.userName}, ${data.age}`);
  })
})

上記の結果は以下の通り。

抽出結果
山田花子, 19歳
山田太郎, 20歳
小島三郎, 29

年齢を降順で表示

年齢で降順ソート.js
const db = firebase.firestore();
db.collection("users").orderBy(`age`, `desc`).get().then(snapShot => {
  snapShot.forEach(doc => {
    const data = doc.data()
    console.log(`${data.userName}, ${data.age}`);
  })
})

上記の結果は以下の通り。

抽出結果
小島三郎, 29歳
山田太郎, 20歳
山田花子, 19

複数条件でソート

orderBy().orderBy()とすることで、1つ目のordeBy()の項目が等しかったら、2つ目のorderBy()の項目で並び替えが行われる

limit:最大表示件数

最大表示件数の指定はlimit()で行う。limit(1)とすると最大1件分、limit(10)とすると最大10件分取得する

1件分抽出.js
const db = firebase.firestore();
db.collection("users").limit(1).get().then(snapShot => {
  snapShot.forEach(doc => {
    const data = doc.data()
    console.log(`${data.userName}, ${data.age}`);
  })
})

上記の結果は以下の通り。

抽出結果
山田太郎, 20

できないことまとめ

コレクションやドキュメントに対するワイルドカード

以下のようなワイルドカードは不可

NG.js
const db = firebase.firestore();
db.collection("users").document("*").where( /* 以下省略 */

フィールド内のオブジェクトのキーに対するクエリ

今回のDB構造でいう、addressに対してのクエリはできない
例えば、オブジェクトのキーをエポック秒(UNIX時間)にしたとして、一定の時刻以下のマップを持つユーザ、のようなクエリはできない

NG.js
// DB構造
users: {
  "004": {
    "1577804400": {
      "hoge": "fuga",
    }
  }
}
// 上記の1577804400に対するクエリはできない

Discussion

ログインするとコメントできます