8

I want to create Firestore documents if they don't exist - if they do exist, skip them (don't update). Here's the flow

var arrayOfRandomIds = [array of 500 random numbers];
for (var id of arrayOfRandomIds)
{
 var ref = db.collection("tickets").doc(id);
 batch.set(ref, {name: "My name", location: "Somewhere"}, { merge: true });
}
batch.commit();

I just want to know, would this overwrite any existing documents if they exist? I don't want anything overwritten, just skipped.

Thanks.

1
  • Do you have a limit in the number of documents you "treat". I guess that your arrayOfRandomIds is an a array of 500 random numbers, because 500 is the limit for batched write. So can you have number of documents greater than 500? Commented Jan 30, 2020 at 16:48

4 Answers 4

9

I think you can use security rules to accomplish that. That way you won't be charged for an additional document read to see if it already exists.

service cloud.firestore {
  match /databases/{database}/documents {
    match /tickets/{id} {
      allow create;
    }
  }
} 
Sign up to request clarification or add additional context in comments.

5 Comments

Interesting. Does it disallow creation of existing IDs?
Yes. "allow create" only applies if the data doesn't exist.
Thanks. Is there a way to track which ids were created? For instance, if I try to create IDs 1,2,3,4,5, but 2 and 3 already exist, is there a way to detect that 2 and 3 weren't created?
Here if I have object of objects in a document and I add merge of a new mixed attributes of updated and same old values if I add role like that match /tasks/experts/{id} { allow create; }, would it add updated attributes only when I use set() with {merge: true} or it will overwrite all even same values attributes and costs me more?
Adding only "allow create" works because set() call actually overwrites (updates) the existing document, and since the security rules dont "allow for update", the set request fails for an existing doc. However, in case you also want to update that document later, you also need to add "allow update". And as soon as the updates are allowed, the set() request that was failing for existing doc, will now be allowed and will overwrite the existing doc. Ba-Dum-Tsss !!!
9

Meanwhile there is a "create but don't overwrite" function. Assuming you are using JavaScript here is the reference: https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html#create

Here is the corresponding example code from the docs:

let documentRef = firestore.collection('col').doc();

documentRef.create({foo: 'bar'}).then((res) => {
  console.log(`Document created at ${res.updateTime}`);
}).catch((err) => {
  console.log(`Failed to create document: ${err}`);
});

Using .create() instead of .set() should do the trick for you without relying on security rules for application logic.

4 Comments

this should be the answer, a lot of people oblivious to it, I wonder why firebase does not include it in guide since "dont create if exist" is not uncommon operation
This is fine for one-off calls, but will fail the entire batch if a document exists that tries to be created.
@user5269602 I am trying to create documents if they do not exist in a batch fashion. Do you have any suggestions for that case?
Does anyone know when this function was added? Doug's answer from 2020 mentions it wasn't possible, so sometime after then stackoverflow.com/a/59990058/13281032
4

Firestore doesn't have a native "create but don't overwrite" operation. Here are the only available operations:

  • update: only change the contents of an existing document
  • set without merge: create or overwrite
  • set with merge: create or update if exists

Instead of a batch, what you can do instead is perform a transaction that checks to see if the document exists, then creates it conditionally if it does not already exist. You will have to write that logic inside your transaction handler.

4 Comments

Thanks, Doug. Might go with that. How does that affect performance and cost? As in, running 500 check-if-exists-then-create-if-not transactions for instance? Just thinking of the most efficient solution. Thanks.
Well, there's always going to be a round trip for check-and-set operations like this. There's no way to avoid that - the server must be consulted for each document that you need to operate on. If you remove the need to check the document first, then you also remove the need for a transaction. But the way you've described the problem, I don't see how you can avoid that check.
@DougStevenson, I tried to use Transaction at client and it worked but there is a catch, the security rules for "read" should not have any restrictions. I had a simple security rule that matched uid { allow read: if request.auth.uid == resource.data.cust.uid }, this rule doesnt let the get() request of transaction pass through, as the doc doesn't exist so UID match fails. Now i have to choose between a secure "read" rule, or this transaction. Any suggestion ?
Nevermind ... adding an !exists(path/to/doc) solved it.
1

I want to create Firestore documents if they don't exist - if they do exist, skip them (don't update).

In that case, you should check if a particular document actually exists in a collection, right before the write operation takes place. If it does not exist, create it, otherwise take no action.

So you should simply use set() function, without passing merge: true.

8 Comments

Thanks. My concern is that the user could create 1000 docs, and might be costly to be checking if each exists before creating. Also, it's a bit slow on the app. I was hoping for a faster & more efficient solution.
If you need to check a document for existence, a read operation is required. So to write a document to Firestore only if it does not exist, you'll be charged with a read operation followed by a write operation. If the document already exists, you'll be charged only with a read operation.
Makes sense. Just wondering, what if I did something like db.collection("tickets").where("id","in",[array of ids]).get(), then loop through and create if not in array?
That's the exact same thing. It requires a read operation followed by a write operation.
@kernelman Please post a new question using its own MCVE, so I and other Firebase developers can help you.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.