Query array of object in firestore - firebase

i have a collection that is called employees, which includes documents and each document contains some data, and an array of objects that is called orgsanizations, for instance:
orgsanizations: [
{
orgId: 'org1',
registrationDate: '08/05/2021',
status: 'pending'
},
{
orgId: 'org2,
registrationDate: '12/01/2021',
status: 'approved'
}
];
I am trying to retrieve all the documents in employees that contains orgId === org1 in the orgsanizations, here is what i tried to do but keeps returning empty array.
const allEmployees = async () => {
const employeesList = db.collection('employees');
const snapshot = await employeesList
.where('orgsanizations', 'array-contains', { orgId: 'org1' })
.get();
if (snapshot.empty) {
console.log(snapshot.empty);
} else {
snapshot.forEach((doc) => {
console.log(doc.data());
});
}
};
};
Is there a solution for this or should start considering changing the structure to something else?
Thanks in advance

You can't check for the contents of a map, using array-contains. There are a couple of solutions for this...
Create a second array called orgIds, which contains only the orgId strings. You can then find any documents which contain these orgIds. To achieve this, you will need to write the orgId into the map AND the orgIds array.
Create an organizations sub-collection of your employee document and use a collectionGroup query.
{
organizations: [
{orgId: 'org1', registrationDate: '08/05/2021', status: 'pending'},
{orgId: 'org2', registrationDate: '12/01/2021', status: 'approved'}
],
orgIds: ['org1', 'org2']
}
const employeesList = db.collection('employees');
const snapshot = await employeesList
.where('orgIds', 'array-contains', 'org1')
.get();
You may also want to change your registrationDate to either a Timestamp or an ISO8601 string, so that you can sort them (if needed).

Related

How to filter in sub collection's documents (firebase)?

My problem is that I use wrong query to get the date.
const SaveDateBase = async ( e) => {
e.preventDefault()
await setDoc(doc(db, "Users", "Pompy", "Pompy", user.uid), {
displayName: user.displayName,
uid: user?.uid,
modulyPV}).then(()=>{
console.log("moduly", modulyPV)
})
};
useEffect(() => {
const getUsers = async (users) => {
const URC = query(collection(db, "Users").document("Pompy").collection("Pompy"), where("uid", "==", user?.uid));
const data = await getDocs(URC)
setModulyPV(data.docs.map((doc) => ({...doc.data(), id: doc.id})))
}
getUsers();
},[])
The date are saved in date base, and I can successfully update/delete them, but I do something wrong to fetch (read?) them.
I guess is problem with the code.
You can get the data in diff ways, first "Pompy" seems to be your document where you are storing a nested collection then you document "Pompy" So for retrieve that specific document should be something like:
let snapshot = await db
.collection('Users')
.doc('Pompy')
.collection('Pompy')
.get()
snapshot.forEach(doc =>{
console.log('data:', doc.data())
})
Then to query into the nested collection would be something like querying the nested collections.
https://cloud.google.com/firestore/docs/samples/firestore-data-get-sub-collections?hl=es-419#firestore_data_get_sub_collections-nodejs
You can also use collection groups.
https://firebase.google.com/docs/firestore/query-data/queries#collection-group-query
const pompys = query(collectionGroup(db, 'Pompy'), where("uid", "==", user?.uid));

Firestore query to firebase query

I need the equivalent firebase realtime database query from the firestore query below
await db.collection('Events')
.where('EventName', '>=', 'startcode')
.where('EventName', '<', 'endcode').get()
.then((snapshot) => {
snapshot.forEach(doc => {
const data = doc.data()
searchList.push(data)
})
})
You can try filtering:
const snapshot = await firebase
.database()
.ref("/ref/to/events")
.orderBy("EventName")
.startAt("start_code")
.endBefore("end_code")
.once("value")
console.log(snapshot.val())
startAt() Return items greater than or equal to the specified key or
value, depending on the order-by method chosen.
endAt() Return items
less than or equal to the specified key or value, depending on the
order-by method chosen.
To use this query your db structure should look something like:
{
events: {
event1: {
EventName: "event1name"
},
event2: {
EventName: "event2name"
}
}
}

How to access all documents, my all documents only have sub collection in firestore

I have create document like this in react native, I am using rnfirebase library
firestore()
.collection('WaterCanData')
.doc(EntryDate)
.collection('Entries')
.doc(values.customerName)
.set({
CustomerName: values.customerName,
CansOut: values.cansOut,
JarsOut: values.jarsOut,
EmptyCansIn: values.emptyCansIn,
JarsIn: values.jarsIn,
Bottles: values.bottles,
Ice: values.ice
})
.then(() => {
console.log('Entry added!!!!!!!!!');
})
When I try to retrieve EntryDate from WaterCanData Coellection I am not able to fetch it(Document name appears in italic font), So how should I retrive this document which contains a subcollection, Below I have attached my ss of data structure
Data structure
Data structuree
The reason your document appears in italics is because it doesn't currently exist. In Cloud Firestore, subcollections can exist without requiring their parent document to also exist.
Non-existant documents will not appear in queries or snapshots in the client SDKs as stated in the Firebase Console.
This document does not exist, it will not appear in queries or snapshots
If you want to be able to get your entry dates, you need to create the document (which can be empty).
firebase.firestore()
.collection('WaterCanData')
.doc(EntryDate)
.set({}); // an empty document
To create the document at the same time as an entry on it's subcollection, you can use a batched write like so:
const db = firebase.firestore();
const batch = db.batch();
// get references to the relevant locations
const entryDateRef = db
.collection('WaterCanData')
.doc(EntryDate);
const customerRef = entryDateRef
.collection('Entries')
.doc(values.customerName);
// queue the data to write
batch.set(entryDateRef, {});
batch.set(customerRef, {
CustomerName: values.customerName,
CansOut: values.cansOut,
JarsOut: values.jarsOut,
EmptyCansIn: values.emptyCansIn,
JarsIn: values.jarsIn,
Bottles: values.bottles,
Ice: values.ice
})
// make changes to database
batch.commit()
.then(() => {
console.log('Entry added!!!!!!!!!');
});
This will then allow you to list all of the entry dates in your database using something like:
firebase.firestore().collection('WaterCanData')
.get()
.then((querySnapshot) => {
querySnapshot.forEach(doc => {
const entryDate = doc.id;
// const customerEntriesRef = doc.ref.collection('Entries');
console.log('Entry date found: ' + entryDate);
}
});
If (as an example) you wanted to also find how many entries were linked to a given date, you would need to also query each subcollection (here the code gets a little more confusing).
firebase.firestore().collection('WaterCanData')
.get()
.then((querySnapshot) => {
const fetchSizePromises = [];
// for each entry date, get the size of it's "Entries" subcollection
querySnapshot.forEach(doc => {
const entryDate = doc.id;
const customerEntriesRef = doc.ref.collection('Entries');
// if this get() fails, just store the error rather than throw it.
const thisEntrySizePromise = customerEntriesRef.get()
.then(
(entriesQuerySnapshot) => {
return { date: entryDate, size: entriesQuerySnapshot.size }
},
(error) => {
return { date: entryDate, size: -1, error }
}
);
// add this promise to the queue
fetchSizePromises.push(thisEntrySizePromise)
}
// wait for all fetch operations and return their results
return Promise.all(fetchSizePromises);
})
.then((entryInfoResults) => {
// for each entry, log the result
entryInfoResults.forEach((entryInfo) => {
if (entryInfo.error) {
// this entry failed
console.log(`${entryInfo.date} has an unknown number of customers in its Entries subcollection due to an error`, entryInfo.error);
} else {
// got size successfully
console.log(`${entryInfo.date} has ${entryInfo.size} customers in its Entries subcollection`);
}
}
});
Using below code you can console every document id inside waterCanData collection. In your database you have only one document, then it will console your document id. (10042021)
firestore()
.collection('WaterCanData')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.id)
});
})

Get subcollection from firestore from same object - React typescript

My interface looks like this:
Interface react:
export interface UserTask {
assignee: String;
date: String;
}
export interface TaskData {
id: String;
taskName: string;
taskIcon: string;
taskLog: UserTask[]
}
Firebase Data model (Structure):
- taskData (Main Collection)
- taskIcon (fields)
- taskName (fields)
- taskLog (sub-collection)
- assignee (fields)
- date (fields)
React code to get firestore data:
import { firestore } from '../firebase';
...
function toTaskData(doc): TaskData {
if (!doc.exists) {
throw new Error("TaskData not found!");
} else {
return { id: doc.id, ...doc.data() } as TaskData;
}
}
const getTaskData = async() => {
const taskDataRef = firestore.collection('taskData');
await taskDataRef.get().then(({docs}) => {
setTaskData(docs.map(toTaskData));
});
}
Response JSON from firestore:
I am only getting an response which contains an array of the taskName and taskIcon and not the subcollection taskLog, my question is how to I retrieve the subcollection or map it to UserTask array?
Firestore queries are shallow: when you fetch a document you don't fetch the the data contained in any of its linked subcollections.
If you want to get a Firestore Document together with all the documents from one of its subcollection, you need to do two fetches: one fetch for the "parent" doc and one for all the docs of the subcollection.
For example, you can do something along the following lines, using Promise.all():
const taskDataRef = firestore.collection('taskData');
const querySnapshot = await taskDataRef.get();
const promises = querySnapshot.docs.map(doc => doc.ref.collection('taskLog').get());
const querySnapshotsArray = Promise.all(promises);
querySnapshotsArray.forEach(querySnapshot => {
// Do something with the querySnapshot
// e.g. querySnapshot.docs....
// or querySnapshot.forEach(...)
});
If you want to combine the data of one parent (a taskData doc) together with all the children taskLog docs, note that Promise.all() returns a single Promise that resolves to an array of the results of the input promises which is in the same order than the input Array. In other words, querySnapshotsArray has the same order than promises.
Note that doing so will cost the read of ALL the taskData docs and ALL the taskLog documents from ALL the taskLog subcollections. It may be less exepensive to organize your front end in such a way the subcollections are only fetched on demand (e.g. if the user clicks on a button or expands a section, etc...)

Query firestore to get all tokens from userid's

In my cloud function i have an array that contains all userId's that need to get a cloud-message(notification)
const aNotify = [{id: 'id001', text: 'specialTextFor001'}, {id: 'id002', text: 'specialTextFor002'};
This is how the devices collection looks like. the Document ID is the token ID but to find them i need to query on the userId
Is it possible to do it through the DB like with a where clause or do I need to do this by getting all devices and in cloud method do a foreach... ?
In order to find a device document corresponding to a userId, you have to use a simple query like:
const db = admin.firestore();
db.collection('devices').where("userId", "==", element.id).get();
see the corresponding doc here.
Since you need to make a query for each element of the aNotify array, you need to use Promise.all(), since get() returns a Promise.
Something like the following will work. You have to adapt it in order to correctly return the promises in your Cloud Function (since you didn't share your Cloud Function code it is difficult to give more guidance on this point).
const db = admin.firestore();
var aNotify = [{ id: 'id001', text: 'specialTextFor001' }, { id: 'id002', text: 'specialTextFor002' }];
var promises = []
aNotify.forEach(function (element) {
promises.push(db.collection('devices').where("userId", "==", element.id).get());
});
return Promise.all(promises)
.then(results => {
results.forEach(querySnapshot => {
querySnapshot.forEach(function (doc) {
console.log(doc.id, " => ", doc.data());
//here, either send a notification for each user of populate an array, or....
//e.g. return admin.messaging().sendToDevice(doc.data().token, ....);
});
});
});
Note that the results array has exactly the same order than the promises array. So it is not complicated to get the text property of the corresponding object of the aNotify array when you send the notifications.

Resources