質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.61%

【Firestore】取得したソート順序のまま、Reference型を使ってデータを再取得したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,683

score 15

前提・実現したいこと

Firestoreで以下のようなデータ構造を作成しています。

CollectionA 
 documentAA -- key:value value:A (String型)
 documentAB -- key:value value:B (String型)
 documentAC -- key:value value:C (String型)

CollectionB
 documentBA -- key:ref  value:/CollectionA/documentAC (reference型)
            -- key:date value:2019/4/12 HH:MM:SS (timestamp型)
 documentBB -- key:ref  value:/CollectionA/documentAB (reference型)
            -- key:date value:2019/4/11 HH:MM:SS (timestamp型)
 documentBC -- key:ref  value:/CollectionA/documentAA (reference型)
            -- key:date value:2019/4/10 HH:MM:SS (timestamp型)

CollectionBでdateの降順に取得し、
そこからCollectionA内のvalueを降順に合わせて出力したいのですが、うまくいっておりません。
こういった類はデータ構造から見直しを行う必要がありますでしょうか?
(なお、一括取得後にクライアント側でソートする方法以外を検討しています)

<出力例>
C
B
A

試したこと

以下のようなソースを実行しましたが、CBAの順序で出力されずにBCA等順序がバラバラになってしまいます。
どうやらcollectionBの取得時は降順になっているのですが、その後の再取得処理にて、順序がおかしくなっているようにみえます。

let db = Firestore.firestore()
let collectionBRef =  db.collection("CollectionB").order(by:"date",descending: true)

// ここでCollectionBをソートして取得
collectionBref.getDocuments(){(querySnapshot,err) in
  for document in querySnapshot!.documents {
    let data = document.data() as NSDictionary
    let ref:DocumentReference = data["ref"] as! DocumentReference

    //ソートされた順で、随時CollecitonAの情報を取得
    ref.getDocument(){(document, error) in 
      let data = document.data() as! NSDictionary
      let str = data["value"] as! String
      print(str) //この出力時には順序が保持されない
    }
  }
}

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

順序がバラバラになっ (たように見え) てしまうのは、 getDocument() の呼び出しが非同期に処理される ためかと存じます。解決するには、クライアント側で順序を保持しておき、全ての document が取得された後に print() するようなコードを書く必要があるのではないでしょうか。

原因

Firebase SDK は Firestore への読み書きを行う際、その処理が非同期に行われるような設計になって居ります。態々取得結果への処理をクロージャで書いて getDocument() 等のメソッドに渡しているのもこのためですね。換言すれば、 getDocument() 等を 呼び出した段階では document の取得を "要求" するだけ であって、その時点で結果は得られないということです。そして実際の取得結果はその後 取得できたものから次々と (クロージャに) 渡される ため、必ずしも "要求" の順序と一致するとは限りません。

結果、御呈示のコードでは次のような順序で処理が行われ、 CollectionA の取得結果が順不同となってしまいます。

  1. CollectionB の document を date の降順で要求する
  2. 1 の要求に対する結果が返り、 date 順に並んだ document の集合が得られる
  3. 集合の各 CollectionB document に対して、 CollectionA への reference を用いて紐づく document を一つ一つ "要求" する (この時点では希望の順番通りになっている)
  4. 3 で行った複数の要求が並列処理され、データ取得に成功したものから次々に渡される (この時点で希望の順序は失われている)

解決策

getDocument() の結果が非同期に順不同に渡されるのは避けられませんので、その 順序を維持するには自身で何らかの工夫をする 必要があります。例えば、次のような方法があるのではないでしょうか。

  1. CollectionB の各 document が持つ reference だけを要素とした配列 references と、同じ長さの配列 resolvedDocuments を用意しておく
  2. references の全ての要素に対して getDocument() をするが、そのクロージャでは単に references 内で documentID が一致する reference と同じ index で resolvedDocuments に結果を保持しておくだけとする
  3. 但し、最後の document が得られたら (== 全ての document の取得が終わったら) 、改めて resolvedDocuments を引数にして print() なりなんなりの処理を行う別の関数を呼び出す処理としておく

データ構造の見直しについて

非正規化などでデータ構造を見直すべきか否かは、 各 collection の document がどのような性質のデータを持ち、どのように用いられるか次第 かと存じますので、この処理による順序性を維持する目的だけでは「こうすべき」「ああすべき」とは言えないように思います。

例えば CollectionA が本当に value フィールドしか持たないのであれば、それ自体を CollectionB の document に持たせてもいいかもしれません。あるいは他にも複数のフィールドがあったとしても、今回の御質問のような read がとにかく多く発生するのであれば、 reference ではなく object にして CollectionA の document そのものを含めてしまう非正規化を行うのも手でしょう (最初の getDocuments() の時点で必要なデータが全て揃うので、順序性を気にする必要がなくなります) 。但しこの場合、 CollectionA への更新をするときは各 CollectionB document に含めたすべてのコピーを更新する必要がありますから、更新が頻繁に生じる場合は不適なやり方と言えます。

何れにせよ、 Firestore 自体が 「必要なデータを都度取得する」 / 「クエリの機能を単純化し、複雑な処理が必要ならデータを取得してからクライアント側で行う」 思想で作られていることもあり、クライアント側で多少のデータ加工や工夫が必要な事自体は不自然なことでは (そして常に避けられることでは) 御座いませんから、それ自体だけがデータ構造見直しの理由にはならないという事です。

とはいえ、「データ量が少なく、性能やら何やらを気にするほどではないから、多少 write のコストが高くてもクライアント実装を簡単にするために非正規化する」という判断も十分あり得ますから、その辺りは御事情に合わせて御検討下さい。

余談

因みに、この「複数の getDocument() 呼び出しの順序を維持し、全ての結果が得られてから関数を呼ぶ」という処理は多くの人に需要があるようで、 Node.js 用の SDK では (恐らくは Promise という強力な仕組みが存在するおかげで) Firestore.getAll() という関数 として実装されていたりします。

ただ残念ながら、 iOS SDK では Feature Request は行われている ものの、優先度が低いとして実装されるには至って居りません。当面は上記のように自分で何とかするしかなさそうです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/21 11:00

    回答ありがとうございます!まだiOS SDKでは実装されてなかったんですね。
    まずはクライアントサイドで処理するように変えたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る