Cloud FireStore: Retrieve 1 document with query - javascript

I'm trying to retrieve a single document from a collection. I'm now using the code below that returns a collections of items, but I know that there is only one item. So it ain't that clean.
Setup:
private db: AngularFirestore
private itemSubs: Subscription[] = [];
itemAd= new Subject<Item>();
fetchItemFromDatabase(itemId: string) {
this.itemSubs.push(
this.db.collection('items', id => id.where('itemId', '==', itemId)).valueChanges().subscribe((items: Item[]) => {
this.itemAd.next(items);
}));
}
I tried to do it with this.db.collection('items').doc(itemId).get() , but I'm getting an error on get() that it's not found/supported. I also didn't got autocompletion when trying to call this methode (methode found in the official cloud firestore documents).
I looked at around at some other solutions and then tried it with this.db.collection('items').doc(itemId).ref.get().then(...) , but here I got an empty doc back.
So I'm a bit stuck at the moment and I don't want to use that whole collections logic when I know there is only 1 item in it.

There may be multiple documents with itemId equal to a given value. While you may know that there is only one in your app, the database and API cannot know nor enforce that. For that reason the query you run will always return a query snapshot that potentially contains multiple documents.
this.db.collection('items', id => id.where('itemId', '==', itemId))
If you want to enforce that there is only one document with the given item ID, consider using that item ID as the document name instead of storing it as a field in the document.
There can be only one document with a given name, so that means the ID is guaranteed to be unique. And you can then retrieve that document with:
this.db.collection('items').doc(itemId)

Related

Best way to batch create if not exists in firestore

I am working with a project where we create a bunch of entries in firestore based on results from an API endpoint we do not control, using a firestore cloud function. The API endpoint returns ids which we use for the document ids, but it does not include any timestamp information. Since we want to include a createdDate in our documents, we are using admin.firestore.Timestamp.now() to set the timestamp of the document.
On subsequent runs of the function, some of the documents will already exist so if we use batch.commit with create, it will fail since some of the documents exist. However, if we use batch.commit with update, we will either not be able to include a timestamp, or the current timestamp will be overwritten. As a final requirement, we do update these documents from a web application and set some properties like a state, so we can't limit the permissions on the documents to disallow update completely.
What would be the best way to achieve this?
I am currently using .create and have removed the batch, but I feel like this is less performant, and I occasionally do get the error Error: 4 DEADLINE_EXCEEDED on the firestore function.
First prize would be a batch that can create or update the documents, but does not edit the createdDate field. I'm also hoping to avoid reading the documents first to save a read, but I'd be happy to add it in if it's the best solution.
Thanks!
Current code is something like this:
const createDocPromise = docRef
.create(newDoc)
.then(() => {
// success, do nothing
})
.catch(err => {
if (
err.details &&
err.details.includes('Document already exists')
) {
// doc already exists, ignore error
} else {
console.error(`Error creating doc`, err);
}
});
This might not be possible with batched writes as set() will overwrite the existing document, update() will update the timestamp and create() will throw an error as you've mentioned. One workaround would be to use create() for each document with Promise.allSettled() that won't run catch() if any of the promise fails.
const results = [] // results from the API
const promises = results.map((r) => db.doc(`col/${r.id}`).create(r));
const newDocs = await Promise.allSettled(promises)
// either "fulfilled" or "rejected"
newDocs.forEach((result) => console.log(result.status))
If any documents exists already, create() will throw an error and status for that should be rejected. This way you won't have to read the document at first place.
Alternatively, you could store all the IDs in a single document or RTDB and filter out duplicates (this should only cost 1 read per invocation) and then add the data.
Since you prefer to keep the batch and you want to avoid reading the documents, a possible solution would be to store the timestamps in a field of type Array. So, you don't overwrite the createdDate field but save all the values corresponding to the different writes.
This way, when you read one of the documents you sort this array and take the oldest value: it is the very first timestamp that was saved and corresponds to the document creation.
This way you don't need any extra writes or extra reads.

How do I check if collection exist in firestore (not document) in JS

Hoi, I would like to check, using React javascript, if a collection in the Firestore already exists, no matter if it's empty or not. I tried:
if (collection(db, ref)) // is always true somehow
Any ideas? Thanks!
You would need to try to fetch from the collection and see if anything is returned:
const snap = await query(collection(db, ref), limit(1));
if (snap.empty) {
// no docs in collection
}
There is no function available in the SDK that can help you can check if a particular collection exists. A collection will start to exist only if it contains at least one document. If a collection doesn't contain any documents, then that collection doesn't exist at all. So that being said, it makes sense to check whether a collection contains or not documents. In code, it should look as simple as:
const snapshot = await query(collection(db, yourRef), limit(1));
if (snapshot.empty) {
//The collection doesn't exist.
}
One thing to mention is that I have used a call to limit(1) because if the collection contains documents, then we limit the results so we can pay only one document read. However, if the collection doesn't exist, there is still one document read that has to be paid. So if the above query yields no resul## Heading ##t, according to the official documentation regarding Firestore pricing, it said that:
Minimum charge for queries
There is a minimum charge of one document read for each query that you perform, even if the query returns no results.
You have to fetch the collection out of the database and check if it has more than 0 documents. Even, if the collection doesn't exist, it will return 0.
const db = firebase.firestore();
db.collection("YOUR COLLECTION NAME").get().then((res) =>{
if(res.size==0){
//Collection does not exist
}else{
//Collection does exist
}

Select object with children refs in Firestore [duplicate]

I'm using Firebase Cloud Firestore, I want when I'm getting a document with reference field inside to receive the reference field as a document and not as a reference to the document...
I have a collection of users and collection of classes (at school) and each class contains a list of references to users (the students in this class).
When I using document.get() (to get the class by its reference) it returns an object that contains a list of DocumentReference (of the users) so I need to call document.get() around 20 more times just to get the users as objects and not as a reference to them.
I want to be able to call document.get() once and it will bring me all the references as documents, like that:
database and code
Is there an elegant way to do this?
You can't instruct the Firestore client SDK to automatically follow document references during a fetch of a single document. You have to request each individual document. In other words, there is no "join" operation like you might expect in SQL.
See also: What is firestore Reference data type good for?
You can get multiple documents from a collection. Read get mutiple documents section from documentation
Here's an example
db.collection("classes")
.document(idOfTheClassYouWantToGet)
.collection("users")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>(){
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
User user = document.toObject(Users.class);
}
}
}
});

Mongo DB - Why my Users.findOne is Undefined?

I'm working on Meteor, trying to find some values from Mongodb collection.
here is the code:
var sameLogins = Users.findOne({login: 'a'});
console.log(sameLogins);
But it's returning and "undefined".
But record exists in collection:
So, can anybody tell what I'm missing?
Also, in mongo console - everything is working fine:
I was looking in Publish/Subsribe stuff, but i'm using autopublish module yet.
Thank you!
I will leave the answer for this issue for new users having the same problem.
If you're using autopublish package then you should be aware that it's publishing the result of .find() for every collection.
But, Meteor.users.find(), be default, will return only _id and profile fields, so documents in your Meteor.users client collection will have these two fields only.
The most easy workaround for this would be to create your own publication (allUsers, for example) and in it to return those fields you need:
Server:
Meteor.publish('allUsers', () => {
// check for Meteor.userId() is omitted, put it here, if needed
return Meteor.users.find({}, { fields: { ... } });
});
Don't forget to subscribe to it:
Client:
Meteor.subscribe('allUsers');
Update for Meteor:
Right now you are storing a cursor in your variable sameLogins. In order to retrieve the results you want, you must actually execute this query by either calling fetch(). What is returned from findOne without fetch is essentially an object that you could use to iterate over and find mongoDB documents - (called a collection cursor). The cursor is not your result itself.
Calling fetch would like something like:
Users.findOne({login: 'a'}).fetch()

Firebase order query $UIDs by value at a different node

I'm having trouble trying to figure out how to get a list to query as desired. Full disclosure, it may not be possible and if that's the case then at least it will provide me with some closure. I want to query a friend's list based on presence, where friends who are active are queried on top.
Here is my database structure:
friendships
$UID
$UID: true
users
$UID
active:true/false
I need the list to stay synced so I need to use .on('value'). I've tried a couple different methods using child_added as well as iterating through a snapshot using snapshot.forEach() and pushing the childSnapshot key:value pairs into an array so I could then use lodash to order the list via the active key on the client side, but ran into the issue of it pushing a "new" item into the array for each active value change so there would be multiple items for any given user.
Any help or insight would be appreciated, spent the majority of yesterday attempting to figure this out before resorting back to an unorganized list using the following code:
const {currentUser} = firebase.auth();
firebase.database().ref(`/friendships/${currentUser.uid}`).on('value, snapshot => { //redux dispatch action, payload: snapshot.val() })
There's no great solution for this. Firebase doesn't have any sort of join or join query, so you could do usersRef.orderByChild('active').equalTo(true), but that will just return you all active users. You'll need to do separate queries to pull in their friendships.

Categories

Resources