Firestore update all documents in collections - javascript

I have a firestore collections named users, each users have a generated id with a field score :
users
0e8X3VFL56rHBxxgkYOW
score : 4
3SeDjrgAWMmh3ranh2u
score : 5
I use redux-firestore and i want to reset all my users score at 0, something like
firestore.update({ collection: 'users' }, { score : 0 }
I can't achieve this because update method need a document id
Do you know how to do this ?

You can get all the documents in the collection, get their id's and perform updates using those id's:
db.collection("cities").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.update({
capital: true
});
});
});

For some strange reason the accepted answer ( thehamzarocks ) wasn't working for me, none of the documents were updated. Maybe there's a bug in AngularFire2. Anyway, I decided to loop over the docs array of the QuerySnapshot instead of using its forEach method, and add each update to a batch queue. Batching bulk operations is also more efficient than sending a new update request for each update operation.
resetScore(): Promise<void> {
return this.usersCollectionRef.ref.get().then(resp => {
console.log(resp.docs)
let batch = this.afs.firestore.batch();
resp.docs.forEach(userDocRef => {
batch.update(userDocRef.ref, {'score': 0, 'leadsWithSalesWin': 0, 'leadsReported': 0});
})
batch.commit().catch(err => console.error(err));
}).catch(error => console.error(error))
}

Batch updates are nice but bare in mind that they are limited to 500 document updates per transaction.
If this reset isn't done often maybe simplest approach is:
async function resetScores() {
const collection = await db
.collection("users")
.get()
collection.forEach(doc=> {
doc.ref
.update({
score: 0
})
})
}

I came across this post while searching for similar solutions. Firestore now has batched writes, which will update all documents in one go. This could be an ideal solution for fewer documents.
Updating #thehamzarocks's answer:
const batch = db.batch()
db.collection('cities').get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const docRef = db.collection('cities').doc(doc.id)
batch.update(docRef, { capital: true })
});
batch.commit();
});

Firestore doesn't have the ability to bulk update documents without knowing their IDs. You will have to somehow know the document ID of each document to update (perform a query, or do batches of queries), and update each one individually.

Sorry if the question is old but I thought providing a new answer to this question might be useful to someone else too. I managed to bulk update the entries of a list using the following command:
this.db
.list<User[]>('users')
.set('/', users);
Edit: I'm using AngularFireDatabase.

Related

prevent firestore number from going nevgative?

I'm New to firestore. I need a little help.
I got a function, that updates the amount of "stock shares". its a "sell" function, so the amount can only go down.
The problem is. I don't wanna go below 0.
So I wanna get the PREVIOUS amount of shares. before I update to the new amount.
So I can make sure I don't go below 0.
There are 2 ways to do this. 1 is by using firestore rules, 2 is by getting the Prev amount like i said.
Can you guys help me get the Prev amount before the UPDATE stage?
code:
function sellStock(){
db.collection("Stocks")
.where("ticker", "==", props.name)
.get()
.then((querySnapshot) => {
if(!querySnapshot.empty){
querySnapshot.forEach(function(doc){
db.collection("myStocks")
.doc(doc.id)
.update({
shares: doc.data().shares - amount
})
"shares" will be the prev amount.
"amount" will be the amount of shares we wanna sell.
Try this way:
.update({
shares: (doc.data().shares - amount) >= 0 ? (doc.data().shares - amount) : 0
})
Updated after discussion in the comments.
There is an important aspect to consider with your business logic: Do you need to execute an atomic operation on several documents? Example: You are subtracting the value of amount to the value of shares, since it is a selling operation but I guess that somewhere else (in another document) you are also adding some value, for example in the bank account of the seller.
In such a case you should use a Transaction: "if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction". You need to include in the Transaction all the documents that need to be locked while the operation is ongoing (i.e. all the docs that are involved in the operation, the ones on which you subtract and the ones on which you add).
However, since you want to update several documents, returned by a query, you cannot use a Transaction of one of the mobile/web SDKs (e.g. iOS, Android, Web/JavaScript), because the mobile/web SDKs use optimistic concurrency controls to resolve data contention.
What you can do is to use one of the Admin SDKs, like the Node.js one, since it uses pessimistic concurrency controls and therefore offers the possibility to run a Transaction on a query (see that you can pass a query to the get() method). So you could do that in a Callable Cloud Function.
Here is an example of a Transaction that will atomically update all the docs on which you substract. Since you didn't share the entire business logic (we don't know which are the docs that you need to update by adding a value) it's a bit difficult to go deeper in the example.
exports.updateTickers = functions.https.onCall((data, context) => {
// get the value of the filter (i.e. props.name) via the data Object
const filter = data.filter;
const amount = data.amount;
const db = admin.firestore();
return db.runTransaction(transaction => {
let queryRef = db.collection("Stocks").where("ticker", "==", filter);
return transaction.get(queryRef)
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const currentValue = doc.get('amount');
if (currentValue - amount > 0) (
transaction.update(doc.ref, { likes: currentValue - amount })
)
});
});
})
.then(() => {
return { result: "Amounts update successful" }
})
});
The only sure-fire way to prevent the shares count from going below 0 is to enforce that in security rules:
allow write: if request.resource.data.shares >= 0;
Any other method can be bypassed by a malicious user who uses your project configuration data with their code.
With this in place, the simplest (and fastest) way to subtract the amount from the shares count is with an atomic increment operation:
db.collection("myStocks")
.doc(doc.id)
.update({
shares: firebase.firestore.FieldValue.increment(-1 * amount)
})

Firebase onSnapshot() listener... Can I somehow get data from before change

I have a collection in a firebase. I'm listening to changes with onSnapshot method.
If any item from a collection is changed in any way listener is fired. I am getting new data, which was inserted into my collection with docChanges method
db.collection('collection')
.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
const payload = {
id: change.doc.id,
data: change.doc.data(),
};
...... some action
});
});
and now... I need to compare new data (which was just inserted) with old data (from before insert) and I am wondering if there is any way to do that?
The Firestore SDK doesn't give you any ways to detect the differences between the "before" and "after" states of a document's fields. You will have to remember both snapshots and look for differences in the fields yourself. You might want to do a search for some ideas on how to go about this, for example: Generic deep diff between two objects

How to push an array value in Firebase Firestore

I am trying to push an array element but am destroying all the content there and replacing with the pushed data:
db .collection('households')
.doc(householdId)
.set( { users: [uid], }, { merge: true }, )
.then(() => { resolve(); })
.catch(() => reject());
I thought the merge true doesn't destroy the data that is already there? Struggling a little with the firestore api docs.
This is the structure of my data:
households
2435djgnfk
users [
0: user1
1: user2
]
Thank you!
You should use Firestore Transaction for this.
const householdRef = db.collection('households').doc(householdId);
const newUid = '1234'; // whatever the uid is...
return db.runTransaction((t) => {
return t.get(householdRef).then((doc) => {
// doc doesn't exist; can't update
if (!doc.exists) return;
// update the users array after getting it from Firestore.
const newUserArray = doc.get('users').push(newUid);
t.set(householdRef, { users: newUserArray }, { merge: true });
});
}).catch(console.log);
Updating an array or a stored object without getting it first will always destroy the older values inside that array/object in firestore.
This is because they are fields and not actual document themselves. So, you have to first get the document and then update the value after that.
I think now you can do it better with the update command on document by using FieldValue.arrayUnion without destroying data that was added meanwhile. Like this:
const admin = require('firebase-admin');
let db = admin.firestore();
const FieldValue = admin.firestore.FieldValue;
let collectionRef = db.collection(collection);
let ref = collectionRef.doc(id);
let setWithOptions = ref.update(arrayFieldName, FieldValue.arrayUnion(value));
As described in https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html
Arrays in Firestore don't work like this. According to the documentation:
Although Cloud Firestore can store arrays, it does not support querying array members or updating single array elements.
If you want to change any element in an array, you have to read the array values from the document first, make changes to it in the client, then write the entire array back out.
There are probably other ways to model your data that are better for your use case. That page of documentation linked above has some solutions.

Firestore - get specific fields from document

What I need:
I want to save articles or notes in Firestore with their respective fields:
Title
Content (texts or paragraphs)
Creation date
Owners (to share that article with other
people and who can edit them like: https://firebase.google.com/docs/firestore/solutions/role-based-access)
But when I show the list of articles I don't need the "content" field (to save bandwidth). I've read that (maybe I'm wrong), it is not possible to make a query to get only specific fields from a document with Firestore.
If it were normal SQL to obtain specific columns from articles (without its content) It would be something like:
SELECT title, creation_date, ...
FROM table_name;
So I've opted to separate the content for two root-level collections (for flexibility and scalability)
My current structure:
Articles collection:
- `articles` [collection]
- `ARTICLE_ID` [document]
- `creatorId` [field]
- `title` [field]
- `date` [field]
- `owners` [obj field]
- {user1_id}: true
- {user2_id}: true
...
Contents collection:
- `contents` [collection]
- `{ARTICLE_ID}` [document]
- `content` [field]
To get articles list in realtime:
firebase.firestore().collection('articles')
.where(`owners.${user.uid}`, '==', true)
.onSnapshot(querySnapshot => {
const articles = []
querySnapshot.forEach((doc) => {
articles.push({
id: doc.id,
...doc.data()
})
})
// do something with articles array
})
To show in another view and get the entire article with its content:
const db = firebase.firestore()
const articleRef = db.collection('articles').doc(articleId)
const contentRef = db.collection('contents').doc(articleId) // same Id as article
articleRef.get().then(articleDoc => {
if (articleDoc.exists) {
contentRef.get().then(contentDoc => {
if (contentDoc.exists) {
const article = {
...articleDoc.data(),
...contentDoc.data()
}
// full article obj
}
})
}
})
My questions
Do you think it's better to do two queries (getArticle and getContent) at the same time and wait with Promise.all() instead of nesting the querys like I do?
Is there a better way to get the article and its content with one query or more efficiently? Some tips or ideas?
Thank you very much in advance!
According to the Firestore Query.select documentation you should be able to select the fields you want.
let collectionRef = firestore.collection('col');
let documentRef = collectionRef.doc('doc');
return documentRef.set({x:10, y:5}).then(() => {
return collectionRef.where('x', '>', 5).select('y').get();
}).then((res) => {
console.log(`y is ${res.docs[0].get('y')}.`);
});
Neither approach is pertinently better than the other. But there are a few key differences.
When you nest the reads, the second read only starts after the first read has completed. When you use Promise.all() both reads start at the same time, so can (partially) run in parallel.
On the other hand: when you use Promise.all() your completion handler (the code you run in then()) won't execute until both documents have loaded. If you nest the calls, you can update the UI after just the first document has loaded.
In the end, the differences are likely to be small. But since they may be significant to your use-case, measure the results and see what works best for you.
In order to output a single field from a Firestore document (version 9) - for example the 'title' in the articles collection you can use the following code snippet:
const q = query(collection(db, 'articles'))
let results = [];
await getDocs(q);
results = getLocation.docs.map((doc) => doc.data()['title']);
results.sort()
The results array will contain only the title field, sorted alphabetically
(Note you have to reference the Firestore db and import 'getDocs', 'query' and 'collection' modules from Firestore)
Firebase Hosting would be your best bet for static content such as articles. If you look at AMP-HTML for example, they strongly make the case for ultra-fast page loads and highlight benefits of edge caching. Firebase hosting is advertised to also support global edge caching.
Firestore and Firebase Realtime Database are database engines. These are not the proper tool for serving up articles.

Updating firestore using previous state

Is it possible to update firestore using the previous state?
So for example I have an address document which has a users field which holds an array of users associated with the address.
whenever I want to add a new user to this array I need the previous array otherwise I will end up overwriting the current data with the new data.
So I end up with something like.
firestore()
.collection("addresses")
.doc(addressId)
.get()
.then(doc => {
this.db
.collection("addresses")
.doc(addressId)
.update({
users: [...doc.data().users, id]
})
});
Is there a way to access the previous data without having to nest calls?
if not
Is there a better way to manage relationships?
If you need the previous value to determine the new value, you should use a transaction. This is the only way to ensure that different clients aren't accidentally overwriting each other's actions.
Unfortunately transactions also need nested calls, since that is the only way to get the current value, and even have one extra wrapper (for the transaction.
var docRef = firestore()
.collection("addresses")
.doc(addressId);
return db.runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(docRef).then(function(doc) {
transaction.update(docRef, { users: [...doc.data().users, id ]});
});
}).then(function() {
console.log("Transaction successfully committed!");
}).catch(function(error) {
console.log("Transaction failed: ", error);
});
The optimal solution is to use a data structure that doesn't require the current value to add new values. This is one of the reasons Firebase recommends against using arrays: they're inherently hard to scale when multiple users may be adding items to the array. If there is no need for maintaining order between the users, I'd recommend using a set-like structure for the users:
users: {
id1: true,
id2: true
}
This is a collection with two users (id1 and id2). The true values are just markers, since you can't have a field without a value.
With this structure, adding a user is as easy as:
firestore()
.collection("addresses")
.doc(addressId)
.update({ "users.id3": true })
Also see the Firestore documentation on
Working with Arrays, Lists, and Sets

Categories