How to order document .add() data in firestore in Javascript - javascript

Ive been struggling with ordering the documents in Cloud Firestore.
Im using the collection.add() method. And reading the data and displaying it on the screen.
Heres how i write it into the database:
let shared = {
category : category,
username : user.displayName,
createdAt : date,
}
// Add a new document with a generated id.
const sharedRef = db.collection('shared');
sharedRef.orderBy('createdAt', "desc");
sharedRef.add(shared)
.then(function() {
console.log("Saved to database");
}).catch(function(error) {
console.error("Error adding document: ", error);
});
And to Read the data I just use doc.data forEach.
I Read the data with doc.data() forEach
Is there anyway i can order the shared documents by the date created?

orderBy is for queries that read data, not for adding data. You can't set the order at the time you call add(). The order you see in the console by default is always going to sort by the document ID, which is random for every call to add().
You shouldn't be concerned about the order that the documents appear in the console. You should only be concerned about putting fields in each document that you can use later to sort them in a query.

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.

Reactnative: Query and Filter Firestore Database and save a single returned document field value in a const/var

I have setup a Firebase Firestore Database and would like to filter it for a certain field value in a document.
I have a collection called "PRD" with thousands of documents where all contain the same fields. One of these fields a Document contains is a GTIN Number (String). I am receiving this Number from a Bar Code (called data), and would like to retrieve the Medication Name (called DSCRD, a different Field in all these Documents) using the GTIN Number scanned.
I am having difficulties with retrieving the Value from the Firebase and the documentation doesn't seem to get me any further. I have tried various retrieval methods. At the moment the code for retrieval looks like this:
import { dbh } from "../firebase/config"
import firestore from '#react-native-firebase/firestore'
dbh.collection('PRD')
.where('GTIN', '==', data)
.get()
.then(documentSnapshot => {
console.log('MedData',documentSnapshot.data())
});
I am unsure how to filter the right medicament using the GTIN provided by the Barcode scanner and then save the specific field value for the description of this medicament to a variable.
Firebase is setup correctly since I am able to write whole collections and documents in it.
Here is the database structure, as you can see there is the PRD Collection with all the medicaments and every medicament containing the GTIN and DSCRD Fields:
The problem with your implementation is that you are trying to call documentSnapshot.data() after querying a collection. This is the syntax you would use if you were fetching a single document. Your current query will return a list of documents which you need to handle like this:
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log('MedData', doc.data())
})
});
Assuming that the GTIN will fetch one unique document (will it?) then you can just use the only document returned by the query to get the name of the Medication like this:
var medName
dbh.collection('PRD')
.where('GTIN', '==', data)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log('MedData', doc.data())
medName = doc.data().DSCRD
})
});

How can I add a subscription for each data in a collection in firestore?

Currently I need to know when a record of a colection was changed, so I use the next code:
db.collection("categories").onSnapshot(function(querySnapshot) {
var categoria = [];
querySnapshot.forEach(function(doc) {
categoria.push(doc.data().nombre);
});
mandarnotificacion(categoria);
console.log(": ", categoria.join(","));
});
This code get all records and I want to get the last record updated.
Do you have idea of how can do that?
You should do as follows:
db.collection('categories').onSnapshot(querySnapshot => {
querySnapshot.docChanges().forEach(change => {
if (change.type === 'added') {
console.log('added', change.doc.data());
}
});
});
See more details in the corresponding documentation. In particular, note that "the first query snapshot contains added events for all existing documents that match the query. This is because you're getting a set of changes that bring your query snapshot current with the initial state of the query".
Firestore doesn't have an internal concept of "last record updated". What you will have to do is add a field that records what you want (probably some sort of timestamp field), and use a query that orders based on that field. For example:
db.collection("categories").orderBy("timestamp", "desc").limit(1)
This will give you one document that has the greatest value of the field "timestamp". If you keep timestamp up to date with the latest update, then your query will always give you the latest updated document.

Dialogflow to firestore inline editor javascript: sorting and filtering on different parameters of collection

I have a database in Google cloud firestore with a collection of documents that each includes the following parameters:
country, headline, URL, date.
I need to return two separate things depending on the input in Dialogflow. The first thing is the headlines of the 3 latest news and the second thing is the headlines of the 3 latest news in France.
I am to do it in Dialogflows inline editor, which is javascript, but it doesn't seem entirely the same as javascript e.g. in Node.
I have already solved the first problem with the following code:
function TopNews_Global(agent) {
agent.add('Here are the latest Global news');
let documents = db.orderBy('Date', 'desc').limit(3);
return documents.get()
.then(snapshot => {
snapshot.forEach(doc => {
agent.add('-' + doc.data().Headline + '.');
agent.add('Link:(' + doc.data().URL + ')');
});
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
});
}
Where db is my collection in firebase.
The following code is my solution to the second thing I want to return, and this is where I am stuck. It is not possible to filter and order over two different fields as I do. But there MUST be a way around this - it's a pretty simple thing I wanna do.
https://firebase.google.com/docs/firestore/manage-data/export-import
function TopNews_France(agent) {
agent.add('Here are the latest French news');
let documents = db.orderBy('Date', 'desc').where('Country', '==', 'France').limit(3);
return documents.get()
.then(snapshot => {
snapshot.forEach(doc => {
agent.add('-' + doc.data().Headline + '.');
agent.add('Link:(' + doc.data().URL + ')');
});
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
});
}
Assuming that as you state, db is a collection object, your query is a valid one for Firestore.
db.orderBy('Date', 'desc').where('Country', '==', 'France').limit(3);
Firestore does allow queries across multiple fields, assuming that the only orderBy or range queries are on the same field. Thus, your query is valid (one orderBy and one non-range where on different fields).
You didn't mention what error you are getting, but I suspect it is this (nominally visible in a Javascript console):
The query requires an index. You can create it here: (url)
That is because this is a query which requires a composite index -- it sorts on one field and filters on another.
Thus, to fix this, you just need to create the index. In an ideal case, you can just click on the URL in that error message and be brought to the exact point in the Firebase console to create the exact index you need. If for some reason you need to create it manually, you can also do that through the console. After it completes building, your query should work.
In this case, you will need to create an index for whatever collection db is pointing at, with the fields Date (in descending order) and Country (in either descending or ascending order -- the equality won't care), and with collection scope.

Cloud FireStore: Retrieve 1 document with query

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)

Categories

Resources