I am wondering if it's possible to get multiple documents by a list of ids in one round trip (network call) to the Firestore database.
if you're within 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])}`);
* });
*/
This is specifically for the server SDK
UPDATE: Cloud Firestore Now Supports IN Queries!
myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])
In practise you would use firestore.getAll like this
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()))
}
or with promise syntax
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())))
}
They have just announced this functionality, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .
Now you can use queries like, but mind that the input size can't be greater than 10.
userCollection.where('uid', 'in', ["1231","222","2131"])
With Firebase Version 9 (Dec, 2021 Update):
You can get multiple documents by multiple ids in one round-trip using "documentId()" and "in" with "where" clause:
import {
query,
collection,
where,
documentId,
getDocs
} from "firebase/firestore";
const q = query(
collection(db, "products"),
where(documentId(), "in",
[
"8AVJvG81kDtb9l6BwfCa",
"XOHS5e3KY9XOSV7YYMw2",
"Y2gkHe86tmR4nC5PTzAx"
]
),
);
const productsDocsSnap = await getDocs(q);
productsDocsSnap.forEach((doc) => {
console.log(doc.data()); // "doc1", "doc2" and "doc3"
});
You could use a function like this:
function getById (path, ids) {
return firestore.getAll(
[].concat(ids).map(id => firestore.doc(`${path}/${id}`))
)
}
It can be called with a single ID:
getById('collection', 'some_id')
or an array of IDs:
getById('collection', ['some_id', 'some_other_id'])
No, right now there is no way to batch multiple read requests using the Cloud Firestore SDK and therefore no way to guarantee that you can read all of the data at once.
However as Frank van Puffelen has said in the comments above this does not mean that fetching 3 documents will be 3x as slow as fetching one document. It is best to perform your own measurements before reaching a conclusion here.
If you are using flutter, you can do the following:
Firestore.instance.collection('your_collection_name')
.where(FieldPath.documentId, whereIn:["list", "of", "document", "ids"])
.getDocuments();
This will return a Future containing List<DocumentSnapshot> which you can iterate as you feel fit.
Surely the best way to do this is by implementing the actual query of Firestore in a Cloud Function? There would then only be a single round trip call from the client to Firebase, which seems to be what you're asking for.
You really want to be keeping all of your data access logic like this server side anyway.
Internally there will likely be the same number of calls to Firebase itself, but they would all be across Google's super-fast interconnects, rather than the external network, and combined with the pipelining which Frank van Puffelen has explained, you should get excellent performance from this approach.
You can perform an IN query with the document IDs (up to ten):
import {
query,
collection,
where,
getDocs,
documentId,
} from 'firebase/firestore';
export async function fetchAccounts(
ids: string[]
) {
// use lodash _.chunk, for example
const result = await Promise.all(
chunk(ids, 10).map(async (chunkIds) => {
const accounts = await getDocs(
query(
collection(firestore, 'accounts'),
where(documentId(), 'in', chunkIds)
));
return accounts.docs.filter(doc => doc.exists()).map(doc => doc.data());
})
);
return result.flat(1);
}
Here's how you would do something like this in Kotlin with the Android SDK.
May not necessarily be in one round trip, but it does effectively group the result and avoid many nested callbacks.
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
}
Note that fetching specific documents is much better than fetching all documents and filtering the result. This is because Firestore charges you for the query result set.
For some who are stucked in same problem
here is a sample code:
List<String> documentsIds = {your document ids};
FirebaseFirestore.getInstance().collection("collection_name")
.whereIn(FieldPath.documentId(), documentsIds).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
YourClass object = document.toObject(YourClass.class);
// add to your custom list
}
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
e.printStackTrace();
}
});
For the ones who want to do it using Angular, here is an example:
First some library imports are needed: (must be preinstalled)
import * as firebase from 'firebase/app'
import { AngularFirestore, AngularFirestoreCollection } from '#angular/fire/firestore'
Some configuration for the collection:
yourCollection: AngularFirestoreCollection;
constructor(
private _db : AngularFirestore,
) {
// this is your firestore collection
this.yourCollection = this._db.collection('collectionName');
}
Here is the method to do the query: ('products_id' is an 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()))
})
}
I hope this helps you, it works for me.
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);
});
});
});
}
Yes, it is possible. Sample in .NET SDK for 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);
Documentation in .NET:
Get all snapshots
Field mask
This doesn't seem to be possible in Firestore at the moment. I don't understand why Alexander's answer is accepted, the solution he proposes just returns all the documents in the "users" collection.
Depending on what you need to do, you should look into duplicating the relevant data you need to display and only request a full document when needed.
if you are using the python firebase admin sdk this is how you query for multiple documents using their uids
from firebase_admin import firestore
import firebase_admin
from google.cloud.firestore_v1.field_path import FieldPath
app = firebase_admin.initialize_app(cred)
client = firestore.client(app)
collection_ref = client.collection('collection_name')
query = collection_ref.where(FieldPath.document_id(), 'in', listOfIds)
docs = query.get()
for doc in docs:
print(doc.id, doc.to_dict())
Instead of importing FieldPath you can also simply use the string __name__. Now your query will be collection_ref.where('__name__', 'in', listOfIds)
The best you can do is not use Promise.all as your client then must wait for .all the reads before proceeding.
Iterate the reads and let them resolve independently. On the client side, this probably boils down to the UI having several progress loader images resolve to values independently. However, this is better than freezing the whole client until .all the reads resolve.
Therefore, dump all the synchronous results to the view immediately, then let the asynchronous results come in as they resolve, individually. This may seem like petty distinction, but if your client has poor Internet connectivity (like I currently have at this coffee shop), freezing the whole client experience for several seconds will likely result in a 'this app sucks' experience.
Hi I have 2 arrays like
phonenumber[1,2,3]
amount[10,20,30]
I need to store the data in a single document like
field1: 1
field2: 10
The code I tried is
I took the object like
**
public class Pojo {
String invitee;
Map<Integer,Integer>phoneAmount;
public Pojo(String invitee, Map<Integer, Integer> phoneAmount)
{
this.invitee = invitee;
this.phoneAmount=phoneAmount;
}
}**
And in another class
Map<int[], int[]> phoneAmount = new HashMap<>();
phoneAmount.put(phonenumber,amount);
db.collection("Split").document("invitees").set(phoneAmount).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "DocumentSnapshot successfully written!");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Error writing document", e);
}
});
It shows error like
java.lang.RuntimeException: Could not serialize object. Maps with non-string keys are not supported
at com.google.android.gms.internal.zzejw.zza(Unknown Source)
at com.google.android.gms.internal.zzejw.zza(Unknown Source)
at com.google.android.gms.internal.zzejw.zzbp(Unknown Source)
From the official documentation:
Although Cloud Firestore can store arrays, it does not support querying array members or updating single array elements.
This approach is one that I personally don't recommend it to be used. One of the many reasons also Firebase recommends against using arrays is that it makes the security rules impossible to write.
However, you can still model this kind of data by leveraging the other capabilities of Cloud Firestore.
Let's take an example. Suppose you have a database which look like this:
{
title: "My Book",
categories: [
"science",
"computer science",
"technology"
]
}
You need to know that if you want to query for all books that are part of the "science" category, using this data structure, there is no way to perform this query.
To solve this, you can consider an alternative data structure, where each category is the key in a map and all values are true.
{
title: "My Book",
categories: {
"science": true,
"computer science": true,
"technology": true
}
}
And to query your database you can use this query:
db.collection("books")
.whereEqualTo("categories.science", true)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {/* ... */});
For your particular case, you can use a database structure that looks like this:
{
title: "phonenumber",
categories: {
"12345": true,
"67890": true,
"43215": true
}
}
To query, please use the following code:
db.collection("phonenumbers")
.whereEqualTo("categories.12345", true)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {/* ... */});
Edit 13 Aug 2018:
According to the updated documentation regarding array membership, now it is possible to filter data based on array values using whereArrayContains() method. A simple example would be:
CollectionReference citiesRef = db.collection("cities");
citiesRef.whereArrayContains("regions", "west_coast");
This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.
Given the data structure below in firebase, i want to run a query to retrieve the blog 'efg'. I don't know the user id at this point.
{Users :
"1234567": {
name: 'Bob',
blogs: {
'abc':{..},
'zyx':{..}
}
},
"7654321": {
name: 'Frank',
blogs: {
'efg':{..},
'hij':{..}
}
}
}
The Firebase API only allows you to filter children one level deep (or with a known path) with its orderByChild and equalTo methods.
So without modifying/expanding your current data structure that just leaves the option to retrieve all data and filter it client-side:
var ref = firebase.database().ref('Users');
ref.once('value', function(snapshot) {
snapshot.forEach(function(userSnapshot) {
var blogs = userSnapshot.val().blogs;
var daBlog = blogs['efg'];
});
});
This is of course highly inefficient and won't scale when you have a non-trivial number of users/blogs.
So the common solution to that is to a so-called index to your tree that maps the key that you are looking for to the path where it resides:
{Blogs:
"abc": "1234567",
"zyx": "1234567",
"efg": "7654321",
"hij": "7654321"
}
Then you can quickly access the blog using:
var ref = firebase.database().ref();
ref.child('Blogs/efg').once('value', function(snapshot) {
var user = snapshot.val();
ref.child('Blogs/'+user+'/blogs').once('value', function(blogSnapshot) {
var daBlog = blogSnapshot.val();
});
});
You might also want to reconsider if you can restructure your data to better fit your use-case and Firebase's limitations. They have some good documentation on structuring your data, but the most important one for people new to NoSQL/hierarchical databases seems to be "avoid building nests".
Also see my answer on Firebase query if child of child contains a value for a good example. I'd also recommend reading about many-to-many relationships in Firebase, and this article on general NoSQL data modeling.
Given your current data structure you can retrieve the User that contains the blog post you are looking for.
const db = firebase.database()
const usersRef = db.ref('users')
const query = usersRef.orderByChild('blogs/efg').limitToLast(1)
query.once('value').then((ss) => {
console.log(ss.val()) //=> { '7654321': { blogs: {...}}}
})
You need to use limitToLast since Objects are sorted last when using orderByChild docs.
It's actually super easy - just use foreslash:
db.ref('Users').child("userid/name")
db.ref('Users').child("userid/blogs")
db.ref('Users').child("userid/blogs/abc")
No need of loops or anything more.
I am new with Firebase. I want to implement ondelete cascade in Firebase.
Here is the problem for which I want solution
I have two table "users" and "groups".
{ "users":{
"user1":{
"username":"john",
"full_name":"John Vincent",
"created_at":"9th Feb 2015",
"groups":{
"group1":true,
"group3":true
}
"last_logins":...
},
"user2": ...,
"user3": ...
}"groups": {
"group1"{
"group_name":"Administrators",
"group_description":"Users who can do anything!",
"no_of_users":2,
"members":{
"user1":true,
"user3":true
}
},
"group2"{
"group_name":"Moderators",
"group_description":"Users who can only moderate!",
"no_of_users":1,
"members":{
"user2":true
}
}
}
}
Please pardon me for above code indentation.
Now if I removed user1 from users table then how it should be automatically removed from groups table using Firebase.
This can easily done using SQL but I don't know how to do this in Firebase. One way to do this in Firebase is to remove user1 from users and then makes group1 and group3 to null and then in groups table make user1 to null under group1/member but this need 2-3 calls. So is there any another best way to do this.
Please help me I am stuck here.
The Firebase Database has no knowledge of relations between values in its JSON tree. In SQL/relations terms: it doesn't have the concept of foreign keys. This means that it also doesn't have an option to delete related objects with a cascading delete. You will need to delete each value separately.
But you can combine all those deletes into a single call by using multi-location updates. If you write null to each of the locations for the user, you can delete all of them with one call to updateChildren():
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
Map<String,Object updates = new HashMap<String,Object>();
updates.put("users/user1", null);
updates.put("groups/group1/members/user", null);
// Do a deep-path update
ref.updateChildren(updatedUserData, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
System.out.println("Error updating data: " + firebaseError.getMessage());
}
}
})
With this in place, you could then write security rules that validate that members of a group must also exist under the /users node:
{
"rules": {
"groups":
"$groupid": {
"users": {
"$uid": {
".validate": "newData.parent().parent().parent().parent().child('users').hasChild($uid)"
}
}
}
}
}
}
The validation rule is a bit easier to understand if you read the multiple .parent() calls as newRoot (which unfortunately doesn't exist). So in pseudo-code it is:
newRoot.child('users').hasChild($uid)
In words: a UID can only be a member of a group if it also exists under /users.
My Firebase Database is like this
When the coding below was run:
String loc=(snapshot.child("loc").getvalue()).tostring();
The output I get has different sequence with the database:
Why is that so?
Firebase data is stored as JSON and is inherently unordered.
If you want to access the data in a specific order, you should specify an orderBy clause for your query and access the data using the snapshot's getChildren() method.
Say you want to log the items by ascending key:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getRef();
Query locations = rootRef.orderByKey();
locations.addListenerForSingleValueEvent(new ValueEventListener() {
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot locSnapshot: snapshot.getChildren()) {
System.out.println(locSnapshot.getKey() + ": " + locSnapshot.getValue(String.class));
}
}
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
// ...
}
});
This sample comes (modified) from the Firebase documentation on reading lists of data.
Frank beat me to my edit, check out his correct solution using orderBy....
You need to use forEach rather than the child method (or child.foreach)
Here is a snippet from the doc:
Because of the way JavaScript Objects work, the ordering of data in
the JavaScript Object returned by val() is not guaranteed to match the
ordering on the server nor the ordering of child_added events. That is
where forEach() comes in handy. It guarantees the children of a
DataSnapshot will be iterated in their query-order.
Source: https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach