I have google-cloud-firestore collection with many documents.
I want to get first and last document in collection (sorted by timestamp).
How can I query for this?
I'm not sure by which Timestamp you want to sort, so I'm gonna assume your doc looks something like this
/id/: {
timestamp: FirebaseFirestore.Timestamp
// ... whatever other properties
}
You can get a Query by querying on a CollectionReference<T> via the .orderBy method.
const collectionQuery = firestore
.collection('collectionName')
.orderBy('timestamp', 'asc')
Now we want to get a QuerySnapshot (which will allow us to access the documents), by using the .get() method.
const collectionSnapshot = await collectionQuery.get()
Now we just access the first and the last document.
const firstDocument = collectionSnapshot.isEmpty
? null
: collectionSnapshot.docs[0]
const lastDocument = collectionSnapshot.isEmpty
? null
: collectionSnapshot.docs[collectionSnapshot.docs.length - 1]
And voila, you have your first and last document! :-)
Related
I'm getting a collection from my Firestore database and adding the document values to an array in JS:
var data = myCollection();
var myArray = [];
data.forEach(function(data) {
var splitPath = data.name.split('/');
var documentId = splitPath[splitPath.length - 1];
var name = data.fields.name ? data.fields.name.stringValue : '';
var country = data.fields.vin ? data.fields.vin.stringValue : '';
myArray.push( [ documentId, name, country ] );
});
Suppose I know a document ID, is it possible to get the collection documents from that certain document ID?
I'm not sure if Firestore documents are ordered by date. I am trying to get the most recent documents from a certain document ID.
Suppose I know a document ID, is it possible to get the collection documents from that certain document ID?
When it comes to the Firebase console, all documents are sorted lexicographically by ID and this behavior can't be changed. When it comes to code, it's up to you to choose how to order the results.
I'm not sure if Firestore documents are ordered by date.
No, there is no time component inside the document IDs.
I am trying to get the most recent documents from a certain document ID.
In that case, the simplest solution would be to add a timestamp field in each document and order them according to that field.
However, Firebase Realtime Database pushed IDs do contain a time component. So if you want, you can add that data there. Both databases are working great together.
If you have multiple documents and you want to implement pagination, for example, you can use query cursors and use startAt() or starAfter() if you need that.
I don't know if this is exactly what you need but firebase docs has below example:Order and limit data
import { query, where, orderBy, limit } from "firebase/firestore";
const q = query(citiesRef, where("population", ">", 100000), orderBy("population"), limit(2));
if you adjust where part to your id, then sort by date it should work.
I wrote the following code.
const salons = query(collection(db, "user"), orderBy('', "desc"), limit(5))
const querySnapshot = await getDocs(salons)
await querySnapshot.forEach((doc) => {
console.log(new Date(doc._document.version.timestamp.seconds * 1000).toString())
//processing
})
I want to sort the orderby by "doc._document.version.timestamp.seconds" which is displayed in console.log.
But I don't know how to do it.
This date is automatically registered by firestore, so the position of the value is different.
Does anyone know how I can sort it?
Firestore can only order/filter data on values that it has indexes for. Indexes are only created for fields in your document (and the document ID), not for implicit metadata such as the timestamp it keeps internally.
There is no way to order Firestore results based on the internal timestamp. If you want to be able to order documents on a timestamp, you'll have to store that timestamp as a field in the document, and then pass that field name to orderBy.
I have been trying to search for a way to do pagination for Firebase Realtime Database. I see a lot of tutorial/articles on pagination for Cloud Firestore but nothing for Realtime Database. Below is my code and its working as expected. Can anyone point me in the right direction for adding pagination to this? If even possible? Any help would be appreciated.
const [ufoSightings, setUfoSightings] = useState([]);
const [userStateSelection, setUserStateSelection] = useState("");
useEffect(() => {
let allUfo = [];
//referencing firebase db
const ufoRef = firebase.database().ref("ufos");
//filter database searching for specific state user is looking for
const query = ufoRef
.orderByChild("state")
.equalTo(`${userStateSelection}`)
.limitToFirst(12);
query.once("value").then((snapshot) => {
//storing ufoSightings in state
snapshot.forEach((snap) => {
allUfo.push(snap.val());
});
setUfoSightings(allUfo);
});
}, [userStateSelection]);
when I tried Frank van Puffelen's answer to query for the next page, I got an error that says
Error: startAfter: Starting point was already set (by another call to startAt, startAfter, or equalTo).
After going through this firebase documentation here is a query that worked for me to get next page
const query = ufoRef.orderByChild("state")
.startAt(`${userStateSelection}`,lastKey)
.endAt(`${userStateSelection}`).limitToFirst(12)
.once("value");
The only drawback is lastKey's list value will be fetched every time you query for the next page which is redundant, so you have to take care of that.
The reason why it worked for me:
From this firebase documentation
startAt ( value : number | string | boolean | null , key ? : string ) : Query
The starting point is inclusive, so children with exactly the specified value will be included in the query. The optional key argument can be used to further limit the range of the query. If it is specified, then children that have exactly the specified value must also have a key name greater than or equal to the specified key.
endAt ( value : number | string | boolean | null , key ? : string ) : Query
The ending point is inclusive, so children with exactly the specified value will be included in the query. The optional key argument can be used to further limit the range of the query. If it is specified, then children that have exactly the specified value must also have a key name less than or equal to the specified key.
So the query will try to fetch
state >= userStateSelection && state <= userStateSelection (lexicographically)
which will equal to
state == userStateSelection
To get the next page, you pass in the state and key of the node to start at or after
So say you capture the values in your listener with this:
var lastState, lastKey;
query.once("value").then((snapshot) => {
//storing ufoSightings in state
snapshot.forEach((snap) => {
allUfo.push(snap.val());
lastState = snap.val().state; // 👈
lastKey = snap.key; // 👈
});
setUfoSightings(allUfo);
});
Now you can get the next page with this query:
const query = ufoRef
.orderByChild("state")
.equalTo(`${userStateSelection}`)
.startAfter(lastState, lastKey) // 👈
.limitToFirst(12);
The startAfter() method is relatively new to the Realtime Database, so if you can't find it or are having trouble with it try the (much older) startAt() method with the same arguments.
Also check out some of the many other questions on firebase-realtime-database pagination, as this has been covered here quite frequently already.
In my chat app, I have private chat between the two users. I intend to set the chat document's id using these two user's docId/uid in such a way that it doesn't depend on the order they're combined and I can determine the chat document's docId using the uid of users irrespective of the order of uid.
I know, I can use where clauses to get the chat doc as well. Is there any major flaw with my approach of generating the chat document's docId? Should I let it be generated automatically and use normal where clauses supported by firestore and limit(1) to get the chat?
basically, it seems I'm looking for is to encrypt uid1 in such a way that it returns a number only and then same with uid2 and then add them together to create the ChatId. This way it'll not depend on the order I use to add them and I can get the chatId and maybe convert that number back to a string using Base64 encode. This way, if I know the users participating in the chat, I can generate the same ChatId. Will that work or is there any flaw to it?
Converting each user ID to a number and then adding them together will likely lead to collisions. As a simple example, think of the many ways you can add up to the number 5: 0+5, 1+4, 2+3.
This answer builds upon #NimnaPerera's answer.
Method 1: <uid>_<uid>
If your app doesn't plan on using large groups, you can make use of the <uid>_<uid> format. To make sure the two user IDs are ordered in the same way, you can sort them first and then combine them together using some delimiter.
A short way to achieve this is to use:
const docId = [uid1, uid2].sort().join("_");
If you wanted to have a three-way group chat, you'd just add the new userID in the array:
const docId = [uid1, uid2, uid3].sort().join("_");
You could also turn this into a method for readability:
function getChatIdForMembers(userIds) {
return userIds.sort().join("_");
}
Here's an example of it in action:
const uid1 = "apple";
const uid2 = "banana";
const uid3 = "carrot";
[uid1, uid2].sort().join("_"); // returns "apple_banana"
[uid1, uid3].sort().join("_"); // returns "apple_carrot"
[uid2, uid1].sort().join("_"); // returns "apple_banana"
[uid2, uid3].sort().join("_"); // returns "banana_carrot"
[uid3, uid1].sort().join("_"); // returns "apple_carrot"
[uid3, uid2].sort().join("_"); // returns "banana_carrot"
// chats to yourself are permitted
[uid1, uid1].sort().join("_"); // returns "apple_apple"
[uid2, uid2].sort().join("_"); // returns "banana_banana"
[uid3, uid3].sort().join("_"); // returns "carrot_carrot"
// three way chat
[uid1, uid2, uid3].sort().join("_"); // returns "apple_banana_carrot"
[uid1, uid3, uid2].sort().join("_"); // returns "apple_banana_carrot"
[uid2, uid1, uid3].sort().join("_"); // returns "apple_banana_carrot"
[uid2, uid3, uid1].sort().join("_"); // returns "apple_banana_carrot"
[uid3, uid1, uid2].sort().join("_"); // returns "apple_banana_carrot"
[uid3, uid2, uid1].sort().join("_"); // returns "apple_banana_carrot"
Method 2: Member list properties
If you intend on supporting group chats, you should use automatic document IDs (see CollectionReference#add()) and store a list of chat members as one of it's fields as introduced in #NimnaPerera's answer for better use of queries.
I recommend two fields:
"members" - an array containing each chat member's ID. This allows you to query the /chats collection for chats that contain the given user.
"membersAsString" - a string, built from sorting "members" and joining them using "_". This allows you to query the /chats collection for chats that contain the exact list of members.
"chats/{chatId}": {
"members": string[], // list of users in this chat
"membersAsString": string, // sorted list of users in this chat, delimited using "_"
/* ... */
}
To find all chats that I am a part of:
const myUserId = firebase.auth().currentUser.uid;
const myChatsQuery = firebase.firestore()
.collection("chats")
.where("members", "array-contains", myUserId);
myChatsQuery.onSnapshot(querySnapshot => {
// do something with list of chat documents
});
To find all three-way chats between Apple, Banana and I:
const myUserId = firebase.auth().currentUser.uid;
const members = [myUserId, "banana", "apple"];
const membersAsString = members.sort().join("_");
const groupChatsQuery = firebase.firestore()
.collection("chats")
.where("membersAsString", "==", membersAsString);
groupChatsQuery.onSnapshot(querySnapshot => {
// do something with list of chat documents
// normally this would return 1 result, but you may get
// more than one result if a user gets added/removed a chat
});
A normal flow, would be to:
Get a list of the relevant chats
For each chat, get the most recent message
Based on the most recent message, sort the chats in your UI
You can very well use a combination of two users uids to define your Firestore document IDs, as soon as you respect the following constraints:
Must be valid UTF-8 characters
Must be no longer than 1,500 bytes
Cannot contain a forward slash (/)
Cannot solely consist of a single period (.) or double periods (..)
Cannot match the regular expression __.*__
What I'm not sure to understand in your question is "in such a way that it doesn't depend on the order they're combined". If you combine the uids of two users you need to combine them in a certain order. For example, uid1_uid2 is not equal to ui2_uid1.
As you are asking #lightsaber you can follow following methods to achieve your objective. But my personal preference is using an where clause, because firestore is supporting that compound queries which cannot be done in real time database.
Method 1
Create a support function to generate a chatId and check whether document is exist from that id. Then you can create chat document or retrieve the document using that id.
const getChatId = (currentUserId: string, guestUserId: string) => {
/* In this function whether you changed the order of the values when passing as parameters
it will always return only one id using localeCompare */
const comp = currentUserId.localeCompare(guestUserId);
if (comp === 0) {
return null;
}
if (comp === -1) {
return currentUserId + '_' + guestUserId;
} else {
return guestUserId + '_' + currentUserId;
}
}
Method 2
Use where clause with array-contains query for retrieving the chat document. And when creating add two user Ids to array and set the array with a relevant field name.
Firestore docs for querying arrays
I'm trying to run a simple query, where I search for a document that contains a value inside an object array.
For instance, look at my database structure:
I want to run a query similar to this:
db.collection('identites').where("partyMembers", "array-contains", {name: "John Travolta"})
What is the correct way to achieve this, is it even possible with Firestore?
Thanks.
As Frank has explained in his answer it is not possible, with array-contains, to query for a specific property of an object stored in an array.
However, there is a possible workaround: it is actually possible to query for the entire object, as follows, in your case:
db.collection('identites')
.where(
"partyMembers",
"array-contains",
{id: "7LNK....", name: "John Travolta"}
)
Maybe this approach will suit your needs (or maybe not....).
The array-contains operations checks if an array, contains a specific (complete) value. It can't check if an array of objects, contains an item with a specific value for a property.
The only way to do your query, is to add an additional field to your document with just the value you want to query existence on. So for example: partyMemberNames: ["John Travolta", "Olivia Newton"].
If you want to extract name: "John Travolta" from "partyMembers" array in a document. you can achieve this by some similar approach in which you can loop through all arrays in a document to find this name.
const [names, setNames] = React.useState([])
const readAllNames = async() => {
const snapshot = await firebase.firestore().collection('identites').doc(documentID).get()
const filterData = snapshot.data().question.map(val => val.name === "John Travolta" ? val : null)
setNames( filterData.filter(e=>e) );
}
This technique is used in perticular Document as we are giving .doc(documentID) This way you can get all the arrays having name: "John Travolta" in names constant.