LoginSignup
18
3

Firestoreの新機能!whereで"or"と"and"、集計関数で"sum"と"average"が追加

Last updated at Posted at 2023-03-22

はじめに

Firestoreの大型アップデートは、記憶に新しいのがcountです。
今まで全データを取得しないと数えられなかったFirestoreのドキュメント数が1000件1クエリ換算で実行できるようになったのは、場合によっては今まで自分で苦労していた部分がサクッとできてしまうかなり大きなアップデートだったと思います。

今回はそれを超える変化だと思います。
条件に、orandが増えるのと、countと同じように集計関数としてsumaverageが来ました!

orのクエリーが追加された9.18.0だと、inを使ったクエリーはorを使わないと10個を超えなかったんですが、現状はorがなくても大丈夫になったので大幅に内容を更新しました。

実際にorを使ってみた

試したクエリーがこれです。

onSnapshot(
  query(
    collection(getFirestore(), 'messages'),
    or(
      and(where('userId', '==',  uid), where('public', '==', false)),
      where('public', '==', true)
    )
  ), (snapshot) => {
    console.log(snapshot.docs.map(doc => doc.data()))
})

公開したメッセージと公開してない自分のメッセージを表示するようなクエリーです。

そもそもorは、SQLの世界ではなるべく避けるものであって、クエリーが遅くなる可能性もあり、できれば使いたくない演算子だと思います。

ただFirestoreはインデックスがないクエリーが不可能になっているので、うまくパフォーマンスが維持できているようです。

Javascript SDKが2つのクエリをローカルでうまくマージしてくれているのではないか?という疑いも少しだけあったので、
ブラウザの開発者ツールを使って通信ログも見てみました。

確認した結果、ちゃんとorとしてクエリが送信され、その結果としてちゃんと値が返ってきていました!
しかも今回onSnapshotを使ったのでリアルタイム監視にも普通に使えるということです。

さらにorderByを追加することも、limitを追加することもできました。これにより今まではローカルでしかできなかったマージをFirestore側でやってくれるようなもので、同じlimitをかけて後でマージするような処理的にもコスト的に無駄なことが避けられそうです!

いやー、興奮するアップデートですね。

さらに今回のアップデートに合わせて、inなどの複数の値を使った項目のクエリーの上限が10から30に増えました!
これも大きな更新かと思います!

andも使ってみた

orがこれだけ変わるのであれば、andでこれがいけるのでは!?と思って試したクエリがこちらです。

onSnapshot(
  query(
    collection(getFirestore(), 'messages'),
    and(
      where('aaaa', '>', '0'),
      where('bbbb', '<', '100')
    )
  ), (snapshot) => {
    console.log(snapshot.docs.map(doc => doc.data()))
})

Firestoreのつらみとして語られる別フィールドの不等号は複数使えないというやつです。
これがないために範囲指定に限界があるのですが、これはエラーになってしまいました。

Invalid query. All where filters with an inequality (<, <=, !=, not-in, >, or >=) must be on the same field. But you have inequality filters on 'aaaa' and 'bbbb'

うーん、この結果からして、orのクエリに対してさらにandするみたいな用途でしか使えないのかもしれません。
ここは少し残念な結果です・・・。

複合インデックスが必要なケースは変わっているか?

これも残念変わっていませんでした。
そもそもandororderByを内包させることができません。そのため、orderByは外に書く必要があるのですが、andでもorでもorderByと二つ以上のフィールドの条件がついたときは複合インデックスの作成が必要でした。

以下のクエリーを発行したときは、

query(
  collection(getFirestore(), 'messages'),
  and(
    where('aaaa', '==', 'a'),
    where('bbbb', '==', 'b'),
  ),
  orderBy('createdAt', 'desc'),
  limit(10)
)

作成されたインデックス

andを使わないときと変わらない結果ですね。

orを使った場合はどうでしょうか?

query(
    collection(getFirestore(), 'messages'),
    or(
      where('aaaa', '==', 'a'),
      where('bbbb', '==', 'b'),
    ),
    orderBy('createdAt', 'desc'),
    limit(10)
  )

orの場合は、この場合であれば、aaaacreatedAtのインデックス、bbbbcreatedAtのインデックスの2つの複合インデックスが必要でした。
クエリを2つ書いたときと同じ結果ですね。ここも改善したという感じではないですね。

sumとaverageの使い方

v10.5.0からsumとaverateも使えるようになりました。
使い方は以下のようになります。

const aggregateSnapshot = await getAggregateFromServer(query, {
  countOfDocs: count(),
  totalHours: sum('hours'),
  averageScore: average('score')
});

const countOfDocs: number = aggregateSnapshot.data().countOfDocs;
const totalHours: number = aggregateSnapshot.data().totalHours;
const averageScore: number | null = aggregateSnapshot.data().averageScore;

おー。
こんな感じになるんですね。これをみる限り、queryを決めて、それに対していろんな集計関数をフィールド名で求めることができるようになります。

試してみての注意点ですが、対象のqueryにwhereなどの条件がつく場合は、複合インデックスが必要になるようです。
orderByなどと同じ感じですね。仕組みを考えると確かにないと無理そうです。

終わりに

Firestoreで苦しんでいる人をたまに見かけてはアドバイスをしてみたり、自分でも改善する方法を模索していましたが、このようなアップデートでどんどん苦しみが減ってきそうでうれしいです!

今後もどんどん改善が来るのを期待したいと思います!

最後にちょっと紹介。

Firebaseを始めたばかりで、設計、実装、運用に困ってる人たちのために本を書いたのでぜひ読んでみてください!

それはそれとして、実際の案件でちゃんとテストも書いていきたいという人は、以下の本もおすすめです!

18
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
3