LoginSignup
86
57

More than 5 years have passed since last update.

Firestore で いいね順(Score順)Sort + Paging するポイント

Last updated at Posted at 2017-12-14

1_HvVj0Bb8rAYugkibgRlp9w.png

本記事のゴール

Firestore の Query で、下記形式のデータを対象にした いいね順(スコア順)ソート & ページング を実現します
なお、記載する例は 無限スクロール 用のページングを想定しています

articles Collection

... likeCount createdAt ...
... 42 12/1 10:01 ...
... 1 12/2 11:14 ...
... 24 12/4 07:20 ...
... 8 12/6 10:03 ...
... 24 12/8 23:02 ...
... 8 12/11 19:30 ...
... 100 12/16 17:30 ...
... 8 12/18 03:28 ...

↓ (各ページ2件 でページングする場合はこうしたい

page: 1

... likeCount createdAt ...
... 100 12/16 17:30 ...
... 42 12/1 10:01 ...

page: 2

... likeCount createdAt ...
... 24 12/8 23:02 ...
... 24 12/4 07:20 ...

page: 3

... likeCount createdAt ...
... 8 12/18 03:28 ...
... 8 12/11 19:30 ...

page: 4

... likeCount createdAt ...
... 8 12/6 10:03 ...
... 1 12/2 11:14 ...

まずは普通にページング

Firestore でのページングには 新しく追加された Date 型のフィールドを使っている方が多いのではないでしょうか。
Date 型を利用したフィールドは、管理画面上から わかりやすい表記で確認できます。

スクリーンショット 2017-12-14 22.35.13.png

Realtime DB 時代に使っていた timestamp 値 と比較すると、わざわざ変換せずに作成日等の情報が確認できるため便利です。


Firestore での Date 型を利用したページングは非常に簡単です。
単に基準とするフィールド(今回は createdAt)で並び替えた後、 初回なら適当に大きな Date、2 ページ目からは 最後に取得したアイテムの値を startAfter に渡すだけです。

const lastDate: Date = lastItem.createdAt;
const articlesRef = db.collection('articles');

articlesRef.orderBy('createdAt', 'desc').startAfter(lastDate).limit(10);

なお、startAfter と似た startAt というメソッドもありますが、両者の違いは渡した値を検索に含むか、含まないかです

lastDate:Date = new Date('2017 12/1 10:00');

// 検索範囲 > 2017 12/1 10:00 < 検索範囲
.orderBy('createdAt', 'asc' | 'desc').startAfter(lastDate)

// 検索範囲 ≧ 2017 12/1 10:00 ≦ 検索範囲
.orderBy('createdAt', 'asc' | 'desc').startAt(lastDate)

いいね順にソートする

ソートするだけなら簡単

const lastCount: number = lastItem.likeCount;
const articlesRef = db.collection('articles');

articlesRef.orderBy('likeCount', 'desc').limit(10);

ただこれを

articlesRef.orderBy('likeCount', 'desc').startAfter(lastCount).limit(10);

通常のページング処理と同様に行うと いいね数 が同じスコアの記事が次に検索する対象から外れてしまいます。
だからと言って startAt を使っても、今度は最終アイテムと同じスコアが重複してしまいます。
順位がついているランキングデータではこの Query のまま使えるでしょうが、今回の要件は実現できません。

複数の OrderBy を使う

この問題を解決するために、 いいね順 及び 作成日順 でソートを行います。
これにより、いいね が同数の場合は日付を参照して次のデータを重複せずに取得することができます。

const lastDate: Date = lastItem.createdAt;
const lastCount: number = lastItem.likeCount;
const articlesRef = db.collection('articles');

lastDate.setMilliseconds(lastDate.getMilliseconds() - 1);

articlesRef.orderBy('likeCount', 'desc').orderBy('createdAt', 'desc').startAt(lastCount, lastDate).limit(10);

ポイントをまとめると・・・

  • ページにまたがるアイテムのいいね数が重複することもあるので、 startAt で最終アイテムと同じいいね数から検索開始
  • 使用した orderBy 順に startAt の値を指定する
  • 今回は更新順の取得 => createdAt の値が下がっていく方向に進むので、開始日時を 1ms 減算

articles Collection

... likeCount createdAt ...
... 42 12/1 10:01 ...
... 1 12/2 11:14 ...
... 24 12/4 07:20 ...
... 8 12/6 10:03 ...
... 24 12/8 23:02 ...
... 8 12/11 19:30 ...
... 100 12/16 17:30 ...
... 8 12/18 03:28 ...
articlesRef.orderBy('likeCount', 'desc').orderBy('createdAt', 'desc').startAt(lastCount, lastDate).limit(2)

page: 1 ( likeCount: 大きな値 && createdAt: 大きな値 ~)

... likeCount createdAt ...
... 100 12/16 17:30 ...
... 42 12/1 10:01 ...


page: 2 ( likeCount: 42 以下のスコア && createdAt: 12/1 10:01 - 1ms 以下の時間 ~ )

... likeCount createdAt ...
... 24 12/8 23:02 ...
... 24 12/4 07:20 ...

page: 3 ( likeCount: 24 以下のスコア && createdAt: 12/4 07:20 - 1ms 以下の時間 ~ )

... likeCount createdAt ...
... 8 12/18 03:28 ...
... 8 12/11 19:30 ...

page: 4 ( likeCount: 8 以下のスコア && createdAt: 12/11 19:30 - 1ms 以下の時間 ~ )

... likeCount createdAt ...
... 8 12/6 10:03 ...
... 1 12/2 11:14 ...

おわり

上記のポイントを踏まえて実装することで 冒頭で述べたいいね数順のページングが実装できます (o・∇・)o
Realtime DB の頃は、こういった処理を実現するために予めリストデータを作成しておく必要があり、それの運用・管理が大変でした…^^;
一方で Firestore を利用するとこんなに簡単に実装できてしまいます。

最高です (´▽`)

日記

firestore の記事を書く際に Firestore タグ と CloudFirestore タグ どっちを付けるべきなのか迷いますね…
件数が多いのは Firestore だけど アイコンが付いてて本物っぽいのが CloudFirestore ...

う〜ん

とりあえずどっちもつけとこ^q^

86
57
1

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
86
57