LoginSignup
6
4

More than 3 years have passed since last update.

[Firestore] Field を追加せずに CollectionGroup でランダムに Document を取得する

Last updated at Posted at 2019-09-10

前置き

Firestore の性質上、ランダム取得は少し工夫が必要です。
StackOverflow "Firestore: How to get random documents in a collection" で Google の人が回答しているようにいくつかの方法がありますが、CollectionGroupQuery への言及がなかったので書きます。

結論

基本的に無理。
特殊な条件を満たせば可能だが、それは CollectionGroup を使う価値が無い場面。

具体例をもとに順を追って検証

例として Flutter の cloud_firestore を使います。
データ構造は、トップに UserCollection、その SubCollecion に Tweet を置いているとします。
この時、「User を限定せず、Tweet の中からランダムに1つ document を取得したい」場面を考えます。
これは一筋縄ではいきません。

それっぽいのを書いてみる

と、こうなるかと思いますが、これはダメです。

// 比較パラメータとして使うためだけに document ID を生成
final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;

// それを使って検索
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
Firestore.instance
    .collectionGroup('user')
    .orderBy('__name__')
    .where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
    .limit(1);
// 後略

__name__ の実体は document ID ではなく、完全に一意な Path です。1
そのため、こうやって怒られます。 2

Invalid query.
When querying a collection group and ordering by document ID,
you must pass a value that results in a valid document path,
but 'jM5rRdSflGyfk8Qy9Puu' is not because it contains an odd number of segments.

つまり、以下のように完全に一意な path を指定すれば動くわけです。実際期待した動きをします。

final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;

Firestore.instance
    .collectionGroup('user')
    .orderBy('__name__')
-   .where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
+   .where('__name__', isGreaterThanOrEqualTo: 'user/XXX/tweet/${dummyTweetDocumentId}')
    .limit(1);

しかし、これじゃあダメですね....
なぜなら、この条件を指定できるということは、client が User の document ID XXX を知っていることを意味するからです。
User を限定せず を達成できていないということです。

だから、階層分 Query を発行する

しかないと思います。あくまで、Field を追加しないことにこだわるなら の話をしています。
今回だと2~4回です。

// まず User をランダムに取得する

final dummyUserDocumentId = Firestore.instance.collection('').document().documentID;
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
final userQuerySnapshot = await Firestore.instance
    .collection('user')
    .orderBy('__name__')
    .where('__name__', isGreaterThanOrEqualTo: dummyUserDocumentId)
    .limit(1)
    .getDocuments();
final user = userQuerySnapshot.documents.first;


// そして、Tweet をランダムに取得する

final dummyTweetDocumentId = Firestore.instance.collection('').document().documentID;
// NOTE: ヒットしなかったら逆向きで検索する必要がありますが、省略しています。
Firestore.instance
    .collection('user/${user.documentID}/tweet')
    .orderBy('__name__')
    .where('__name__', isGreaterThanOrEqualTo: dummyTweetDocumentId)
    .limit(1);
// 後略

結果、どうするのがベストか?

Field を追加するのがシンプルだなーと思いました。
ただ、そこに document ID の生成ロジックを使う場合は少しだけ悩ましいです。
どういうことかと言うと、document 自体の ID と一致させるには add ではなく set で document を追加することになるからです。
set には update の意もあるので、できれば利用は避けたいものです。
なので、document 自体の ID とは別個に(同様のロジックを使って)値を作って入れておくのが良いかなと考えています。そうすれば add で書けるので。
万が一、その Field と document の ID が一致してないといけない仕様があるなら set で書くしかないとは思いますが...

6
4
0

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
6
4