Google Firestore - bagaimana cara mendapatkan dokumen dengan banyak id dalam satu perjalanan pulang pergi?

Saya bertanya-tanya apakah mungkin untuk mendapatkan banyak dokumen berdasarkan daftar id dalam satu perjalanan pulang pergi (panggilan jaringan) ke Firestore.


person Joon    schedule 13.10.2017    source sumber
comment
Tampaknya Anda berasumsi bahwa perjalanan bolak-balik menyebabkan masalah kinerja pada aplikasi Anda. Saya tidak berasumsi demikian. Firebase memiliki riwayat kinerja yang baik dalam kasus seperti ini, karena menyalurkan permintaan. Meskipun saya belum memeriksa bagaimana perilaku Firestore dalam skenario ini, saya ingin melihat bukti masalah kinerja sebelum berasumsi bahwa masalah itu ada.   -  person Frank van Puffelen    schedule 13.10.2017
comment
Katakanlah saya memerlukan dokumen a, b, c untuk melakukan sesuatu. Saya meminta ketiganya secara paralel dalam permintaan terpisah. a membutuhkan waktu 100 md, b membutuhkan waktu 150 md, dan c membutuhkan waktu 3000 md. Akibatnya, saya harus menunggu 3000 ms untuk melakukan tugas tersebut. Ini akan menjadi max dari mereka. Akan lebih berisiko bila jumlah dokumen yang harus diambil banyak. Tergantung pada status jaringan, menurut saya ini bisa menjadi masalah.   -  person Joon    schedule 13.10.2017
comment
Bukankah mengirimkan semuanya sebagai satu SELECT * FROM docs WHERE id IN (a,b,c) membutuhkan waktu yang sama? Saya tidak melihat perbedaannya, karena koneksi dibuat satu kali dan sisanya disalurkan melalui saluran tersebut. Waktu (setelah koneksi awal dibuat) adalah waktu buka semua dokumen + 1 perjalanan pulang pergi, sama untuk kedua pendekatan. Jika perilakunya berbeda bagi Anda, dapatkah Anda membagikan sampelnya (seperti dalam pertanyaan tertaut saya)?   -  person Frank van Puffelen    schedule 13.10.2017
comment
Aku pikir aku kehilanganmu. Saat Anda mengatakan itu disalurkan, apakah yang Anda maksud adalah Firestore secara otomatis mengelompokkan dan mengirim kueri ke server mereka dalam satu perjalanan ke database?   -  person Joon    schedule 14.10.2017
comment
FYI, yang saya maksud dengan perjalanan pulang pergi adalah satu panggilan jaringan ke database dari klien. Saya bertanya apakah beberapa kueri secara otomatis dikelompokkan sebagai satu perjalanan pulang pergi oleh Firestore, atau Apakah beberapa kueri dilakukan sebagai beberapa perjalanan pulang pergi secara paralel.   -  person Joon    schedule 14.10.2017
comment
Apakah Anda membaca jawaban saya yang saya tautkan di komentar pertama saya? Ini menjelaskan konsep pipeline lebih baik daripada yang bisa saya lakukan di komentar ini.   -  person Frank van Puffelen    schedule 14.10.2017
comment
Ya, saya sudah membaca jawaban Anda, tetapi masih belum jelas apakah akan ada beberapa panggilan jaringan atau hanya satu. Sepertinya akan ada n panggilan jaringan secara paralel untuk n item, bukan hanya satu panggilan jaringan yang melakukan n kueri sekaligus.   -  person Joon    schedule 14.10.2017
comment
Mereka tidak benar-benar paralel, tapi tersalurkan. Namun hasilnya praktis sama. Sekali lagi: Saya belum memeriksa apa yang dilakukan Firestore, tapi saya akan melakukan beberapa pengukuran sebelum berasumsi bahwa dibutuhkan N*(read_time+transfer_time+latency). Jika sudah disalurkan, mendekati N*(transfer_time)+read_time+latency.   -  person Frank van Puffelen    schedule 14.10.2017
comment
Saya yakin ini dijawab oleh @FrankvanPuffelen (bekerja di Firebase) di pertanyaan lain: stackoverflow.com/questions/35931526/   -  person Nick Franceschina    schedule 30.01.2018
comment
Untuk semua orang yang mencoba mencari cara menjalankan beberapa permintaan dalam satu lingkaran karena SDK seluler (Flutter untuk iOS/Android) tidak menawarkan fungsi getAll(); lihat postingan ini: stackoverflow.com/a/42176121/9248277 (menjelaskan cara menangani daftar Futures secara paralel)   -  person Lucas Aschenbach    schedule 21.04.2020


Jawaban (13)


jika Anda berada dalam Node:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L978

/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
*   console.log(`First document: ${JSON.stringify(docs[0])}`);
*   console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/

Ini khusus untuk SDK server

PEMBARUAN: Cloud Firestore [sdk sisi klien] Kini Mendukung Kueri IN!

https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])

person Nick Franceschina    schedule 24.01.2018
comment
Bagi siapa pun yang ingin memanggil metode ini dengan array referensi dokumen yang dihasilkan secara dinamis, Anda dapat melakukannya seperti ini: firestore.getAll(...arrayOfReferences).then() - person Horea; 06.03.2018
comment
@NickFranceschina tahukah Anda cara ini dapat digunakan pada lib vanilla Angularfire atau Firebase? - person Kisinga; 23.01.2019
comment
Maafkan saya @KamanaKisinga ... Saya belum melakukan hal-hal firebase apa pun selama hampir setahun dan tidak dapat membantu saat ini (hei lihat, saya sebenarnya memposting jawaban ini satu tahun yang lalu hari ini!) - person Nick Franceschina; 24.01.2019
comment
@NickFranceschina ???????? Cukup adil Terima kasih - person Kisinga; 24.01.2019
comment
bagaimana dengan firestore.getAll.call(null, arrayOfReferences) jika operator spread belum support? - person Julian Paolo Dayag; 12.02.2019
comment
SDK sisi klien kini juga menawarkan fungsi ini. lihat jawaban jeodonara sebagai contoh: stackoverflow.com/a/58780369 - person Frank van Puffelen; 02.12.2019
comment
peringatan: filter masuk dibatasi hingga 10 item saat ini. Jadi Anda mungkin akan menyadari bahwa itu tidak ada gunanya ketika Anda akan mencapai produksi. - person Martin Cremer; 12.01.2020
comment
sebenarnya Anda perlu menggunakan firebase.firestore.FieldPath.documentId() dan bukan 'id' - person Maddocks; 26.01.2020
comment
Mendapatkan kesalahan aneh ini Nilai yang sesuai untuk FieldPath.documentId() harus berupa string atau DocumentReference. - person funct7; 24.03.2020
comment
Terima kasih, ini menyelamatkan hidupku. - person Swetabja Hazra; 16.06.2020
comment
Android: List<String> lst;, FirebaseFirestore.getInstance().collection("collection_name").whereIn(FieldPath.documentId(), lst).get().addOnCompleteListener(...); - person Alaa M.; 04.07.2020
comment
Saya tidak pernah memikirkan untuk meneruskan firestore.FieldPath.documentId() ke mana. Ini adalah penggunaan In query yang bagus! - person Ayyappa; 09.10.2020

Mereka baru saja mengumumkan fungsi ini, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Sekarang Anda dapat menggunakan kueri seperti, tetapi ingat bahwa ukuran masukan tidak boleh lebih besar dari 10.

userCollection.where('uid', 'in', ["1231","222","2131"])

person jeadonara    schedule 09.11.2019
comment
Ada kueri di mana, bukan di mana. Dan saya tidak tahu cara mendesain kueri untuk banyak dokumen dari daftar id dokumen milik koleksi tertentu. Tolong bantu. - person Compile error end; 17.11.2019
comment
@Compileerrorend bisakah Anda mencobanya? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get() - person jeadonara; 03.12.2019
comment
terima kasih, khususnya untuk firebase.firestore.FieldPath.documentId() - person Ivan Chernykh; 22.12.2019
comment
@jeadonara jika array input lebih besar dari 10, apa yang harus saya gunakan? - person Ramesh Vishnoi; 24.11.2020
comment
@RameshVishnoi Anda dapat menggunakan Promise.all() ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/) . - person jeadonara; 29.11.2020

Dalam praktiknya Anda akan menggunakan firestore.getAll seperti ini

async getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    const users = await this.firestore.getAll(...refs)
    console.log(users.map(doc => doc.data()))
}

atau dengan sintaks janji

getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}
person Sebastian    schedule 27.11.2018
comment
ini harus menjadi jawaban yang dipilih karena memungkinkan Anda menggunakan lebih dari 10 id - person sshah98; 22.04.2020
comment
Ini berhasil! Terima kasih. Di mana dokumentasi mengenai hal ini? saya mencari getAll dan tidak dapat menemukannya di mana pun. - person TravRob; 07.12.2020
comment
@TravRob ini mungkin tersedia dalam varian Firebase tertentu, seperti Node, tapi yang pasti tidak ada di API JavaScript. - person zxbEPREF; 21.02.2021

Anda bisa menggunakan fungsi seperti ini:

function getById (path, ids) {
  return firestore.getAll(
    [].concat(ids).map(id => firestore.doc(`${path}/${id}`))
  )
}

Itu bisa dipanggil dengan satu ID:

getById('collection', 'some_id')

atau serangkaian ID:

getById('collection', ['some_id', 'some_other_id'])
person JP de la Torre    schedule 17.05.2018

Tidak, saat ini tidak ada cara untuk mengelompokkan beberapa permintaan baca menggunakan Cloud Firestore SDK dan oleh karena itu tidak ada cara untuk menjamin bahwa Anda dapat membaca semua data sekaligus.

Namun seperti yang dikatakan Frank van Puffelen dalam komentar di atas, hal ini tidak berarti bahwa mengambil 3 dokumen akan 3x lebih lambat daripada mengambil satu dokumen. Yang terbaik adalah melakukan pengukuran Anda sendiri sebelum mencapai kesimpulan di sini.

person Sam Stern    schedule 16.10.2017
comment
Masalahnya adalah saya ingin mengetahui batasan teoritis kinerja Firestore sebelum bermigrasi ke Firestore. Saya tidak ingin bermigrasi dan kemudian menyadari bahwa itu tidak cukup baik untuk kasus penggunaan saya. - person Joon; 17.10.2017
comment
Bagi saya, tidak ada dukungan batching tidak baik. Karena sebagian besar panggilan basis data aplikasi saya bertujuan untuk mendapatkan banyak (seringkali ratusan) dokumen dengan banyak id. Agar berperforma baik, panggilan-panggilan itu perlu dilakukan secara batch untuk saya. - person Joon; 17.10.2017
comment
@Joon sepertinya Anda harus mengevaluasi ulang struktur data Anda agar lebih berperforma dalam database NoSQL seperti Cloud Firestore. Saran terbaik yang bisa saya berikan adalah berpikir mundur dari pertanyaan. Pikirkan kueri yang ingin Anda jalankan, dan susun data Anda sehingga Anda bisa mengekspresikannya dengan sederhana. Semua kueri di Cloud Firestore cepat. - person Sam Stern; 17.10.2017
comment
Hai, ada juga pertimbangan cose di sini. Katakanlah saya telah menyimpan daftar semua ID teman saya dan jumlahnya 500. Saya bisa mendapatkan daftar tersebut dalam 1 biaya baca tetapi untuk menampilkan Nama dan URL foto mereka, saya harus membayar 500 kali baca. - person Tapas Mukherjee; 26.10.2017
comment
Jika Anda mencoba membaca 500 dokumen, dibutuhkan 500 kali pembacaan. Jika Anda menggabungkan informasi yang Anda perlukan dari 500 dokumen ke dalam satu dokumen tambahan, hanya diperlukan satu kali pembacaan. Duplikasi data yang disebut semacam itu cukup normal di sebagian besar database NoSQL, termasuk Cloud Firestore. - person Frank van Puffelen; 27.10.2017
comment
@FrankvanPuffelen apakah tidak ada cara lain selain apa yang Anda gambarkan sebagai 500 dokumen menjadi satu dokumen tambahan. Saya kesulitan menerima batasan ini dalam database NoSQL. dapatkah Anda menjelaskan beberapa postingan atau diskusi terkait tentang praktik umum dalam kasus ini? Terima kasih banyak - person Sitian Liu; 28.10.2017
comment
@FrankvanPuffelen Misalnya, di mongoDb, Anda dapat menggunakan ObjectId seperti ini stackoverflow.com/a/32264630/648851. - person Sitian Liu; 28.10.2017
comment
Pertimbangan lain di sini menurut saya (seseorang dari tim Firestore perlu mengonfirmasi) adalah jika Anda memiliki data yang memenuhi kueri Anda yang di-cache secara lokal, yang dilakukan secara otomatis dengan Firestore, TIDAK dikenakan biaya 500 kali pembacaan. Anda hanya dikenakan biaya untuk pembacaan yang memerlukan dokumen/pembaruan dari server. - person wildcat12; 19.04.2018
comment
@ wildcat12 saat ini Anda akan dikenakan biaya 500 kali pembacaan karena kami tidak menentukan cara untuk mengatakan berikan saya hanya data cache, SDK akan selalu menindaklanjuti data cache dengan pemeriksaan terbaru. Tapi itu adalah sesuatu yang sedang kami kerjakan. - person Sam Stern; 19.04.2018
comment
@SamStern Apakah masih belum ada dukungan untuk kueri batching? - person Dr. C. Hilarius; 03.12.2018
comment
Seperti yang dikatakan @FrankvanPuffelen, duplikasi data cukup umum terjadi di database NoSQL. Di sini Anda harus bertanya pada diri sendiri seberapa sering data ini perlu dibaca, dan seberapa mutakhir data tersebut. Jika Anda menyimpan 500 informasi pengguna, katakanlah nama + foto + ID mereka, Anda bisa mendapatkannya sekaligus. Namun jika Anda perlu memperbaruinya, Anda mungkin harus menggunakan fungsi cloud untuk memperbarui referensi ini setiap kali pengguna memperbarui nama/fotonya, sehingga menjalankan fungsi cloud + melakukan beberapa operasi penulisan. Tidak ada implementasi yang benar/lebih baik, itu hanya tergantung pada kasus penggunaan Anda. - person schankam; 11.06.2019

Jika Anda menggunakan flutter, Anda dapat melakukan hal berikut:

Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();

Ini akan mengembalikan Masa Depan yang berisi List<DocumentSnapshot> yang dapat Anda ulangi sesuai keinginan Anda.

person Monis    schedule 02.06.2020
comment
'daftar berisi beberapa ID dokumen' maksimal 10 item ya? - person Krishna Shetty; 22.02.2021

Tentunya cara terbaik untuk melakukannya adalah dengan mengimplementasikan kueri Firestore yang sebenarnya di Cloud Function? Maka hanya akan ada satu panggilan pulang pergi dari klien ke Firebase, yang sepertinya adalah apa yang Anda minta.

Anda benar-benar ingin mempertahankan semua logika akses data Anda seperti sisi server ini.

Secara internal kemungkinan akan ada jumlah panggilan yang sama ke Firebase itu sendiri, namun semuanya akan dilakukan melalui interkoneksi super cepat Google, bukan melalui jaringan eksternal, dan dikombinasikan dengan pipeline yang dijelaskan oleh Frank van Puffelen, Anda akan mendapatkan performa luar biasa dari pendekatan ini.

person Chris Wilson    schedule 04.03.2018
comment
Menyimpan implementasi di Cloud Function adalah keputusan yang tepat dalam beberapa kasus ketika Anda memiliki logika yang kompleks, namun mungkin tidak dalam kasus di mana Anda hanya ingin menggabungkan daftar dengan beberapa id. Yang hilang adalah cache sisi klien dan format pengembalian standar dari panggilan biasa. Hal ini menyebabkan lebih banyak masalah kinerja daripada yang diselesaikan dalam beberapa kasus di aplikasi saya ketika saya menggunakan pendekatan ini. - person Jeremiah; 07.03.2018

Inilah cara Anda melakukan hal seperti ini di Kotlin dengan Android SDK.
Mungkin tidak harus dalam satu perjalanan bolak-balik, namun hasilnya mengelompokkan secara efektif dan menghindari banyak callback yang bersarang.

val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }

Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
    //Do what you need to with the document list
}

Perhatikan bahwa mengambil dokumen tertentu jauh lebih baik daripada mengambil semua dokumen dan memfilter hasilnya. Hal ini karena Firestore menagih Anda untuk kumpulan hasil kueri.

person Markymark    schedule 20.08.2019
comment
Bekerja dengan baik, persis seperti yang saya cari! - person Georgi; 22.09.2019

Saya harap ini membantu Anda, ini berhasil untuk saya.

getCartGoodsData(id) {

    const goodsIDs: string[] = [];

    return new Promise((resolve) => {
      this.fs.firestore.collection(`users/${id}/cart`).get()
        .then(querySnapshot => {
          querySnapshot.forEach(doc => {
            goodsIDs.push(doc.id);
          });

          const getDocs = goodsIDs.map((id: string) => {
            return this.fs.firestore.collection('goods').doc(id).get()
              .then((docData) => {
                return docData.data();
              });
          });

          Promise.all(getDocs).then((goods: Goods[]) => {
            resolve(goods);
          });
        });
    });
  }
person Muhammad Mabrouk    schedule 07.11.2019

Bagi yang ingin melakukannya menggunakan Angular, berikut contohnya:

Pertama, beberapa impor perpustakaan diperlukan: (harus diinstal sebelumnya)

import * as firebase from 'firebase/app'
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'

Beberapa konfigurasi untuk koleksi:

yourCollection: AngularFirestoreCollection;

constructor(
    private _db : AngularFirestore,
) { 
    // this is your firestore collection
    this.yourCollection = this._db.collection('collectionName');
}

Berikut adalah metode untuk melakukan query: ('products_id' adalah Array of ids)

getProducts(products_ids) {
    var queryId = firebase.firestore.FieldPath.documentId();
    this.yourCollection.ref.where(queryId, 'in', products_ids).get()
        .then(({ docs }) => {
            console.log(docs.map(doc => doc.data()))
        })
}
person Santi Nunez    schedule 25.02.2021

:) Setelah Anda menggunakan semut, bukankah Anda gagal?
person Horea    schedule 23.12.2017

Ya, itu mungkin. Contoh di .NET SDK untuk Firestore:

/*List of document references, for example:
    FirestoreDb.Collection(ROOT_LEVEL_COLLECTION).Document(DOCUMENT_ID);*/
    List<DocumentReference> docRefList = YOUR_DOCUMENT_REFERENCE_LIST;
    
    // Required fields of documents, not necessary while fetching entire documents
    FieldMask fieldMask = new FieldMask(FIELD-1, FIELD-2, ...);
    
    // With field mask
    List<DocumentSnapshot> documentSnapshotsMasked = await FirestoreDb.GetAllSnapshotsAsync(docRefList, fieldMask);
    
    // Without field mask
    List<DocumentSnapshot>documentSnapshots = await FirestoreDb.GetAllSnapshotsAsync(docRefList);

Dokumentasi dalam .NET:

  1. Dapatkan semua snapshot

  2. Field mask

person Om Palsanawala    schedule 11.06.2021
comment
Meskipun tautan ini mungkin menjawab pertanyaan tersebut, lebih baik sertakan bagian penting dari jawaban di sini dan berikan tautan untuk referensi. Jawaban hanya tautan bisa menjadi tidak valid jika halaman tertaut berubah. - Dari Ulasan - person Amir Dora.; 11.06.2021
comment
Ya, ini benar-benar menjawab pertanyaan itu. Saya juga telah menambahkan cuplikan kode contoh beserta tautan referensinya. - person Om Palsanawala; 11.06.2021

Hal terbaik yang dapat Anda lakukan adalah tidak menggunakan Promise.all sebagai klien Anda dan kemudian harus menunggu .all pembacaan sebelum melanjutkan.

Ulangi bacaannya dan biarkan mereka menyelesaikannya secara mandiri. Di sisi klien, ini mungkin bermuara pada UI yang memiliki beberapa gambar pemuat kemajuan yang menentukan nilai secara independen. Namun, ini lebih baik daripada membekukan seluruh klien sampai .all pembacaan teratasi.

Oleh karena itu, segera buang semua hasil sinkron ke tampilan, lalu biarkan hasil asinkron masuk saat diselesaikan, satu per satu. Ini mungkin tampak seperti perbedaan kecil, tetapi jika klien Anda memiliki konektivitas Internet yang buruk (seperti yang saya alami saat ini di kedai kopi ini), membekukan seluruh pengalaman klien selama beberapa detik kemungkinan besar akan menghasilkan pengalaman 'aplikasi ini jelek'.

person Ronnie Royston    schedule 17.09.2018
comment
Ini tidak sinkron, ada banyak kasus penggunaan untuk menggunakan Promise.all... tidak harus membekukan apa pun – Anda mungkin perlu menunggu semua data sebelum dapat melakukan sesuatu yang berarti - person Ryan Taylor; 08.03.2019
comment
Ada beberapa kasus penggunaan ketika Anda perlu memuat semua data Anda, oleh karena itu penantian (seperti spinner dengan pesan yang sesuai, tidak perlu membekukan UI apa pun seperti yang Anda katakan) dapat sepenuhnya dibutuhkan oleh Promise.all... Itu sangat bergantung pada jenis produk apa yang Anda buat di sini. Komentar semacam ini menurut pendapat saya sangat tidak relevan dan tidak boleh ada kata-kata terbaik di dalamnya. Hal ini sangat bergantung pada setiap kasus penggunaan berbeda yang mungkin dihadapi seseorang dan apa yang dilakukan aplikasi Anda untuk pengguna. - person schankam; 11.06.2019