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

# はじめに

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

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

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

# 実際に`or`を使ってみた

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

```js
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が２つのクエリをローカルでうまくマージしてくれているのではないか?という疑いも少しだけあったので、
ブラウザの開発者ツールを使って通信ログも見てみました。

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

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

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

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

# `and`も使ってみた

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

```js
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`するみたいな用途でしか使えないのかもしれません。
ここは少し残念な結果です・・・。

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

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

以下のクエリーを発行したときは、
```js
query(
  collection(getFirestore(), 'messages'),
  and(
    where('aaaa', '==', 'a'),
    where('bbbb', '==', 'b'),
  ),
  orderBy('createdAt', 'desc'),
  limit(10)
)
```
![作成されたインデックス](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341912/60f63cbb-e533-5e07-24c3-048848e09c20.png)

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

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

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

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


# sumとaverageの使い方

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

```ts
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を始めたばかりで、設計、実装、運用に困ってる人たちのために本を書いたのでぜひ読んでみてください！

https://techbookfest.org/product/sbQRpfiiHdwDJcvfSTNUSr?productVariantID=a9F2Wk1HrmdiuXheJPt58z

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

https://techbookfest.org/product/vs3BHXSD87tj80ijFY6Fpu?productVariantID=9m8QF8yRkweKgEPq6Zaesd

https://www.amazon.co.jp/Testable-Firebase-%E6%8C%81%E7%B6%9A%E5%8F%AF%E8%83%BD%E3%81%AAFirebase%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E7%9B%AE%E6%8C%87%E3%81%97%E3%81%A6-%E5%BA%83%E4%B8%8A-%E5%B0%87%E4%BA%BA/dp/4295601659