Performing a complex query with firestore v9 - javascript

I'm attempting to perform a complex query with firestore v9. My intention is to get some docs (skip and limit) from a my data structure. What I mean is, I need to get for example 5 docs from a doc -> collections -> doc. and this docs need to be order by there date.
So for example, lets say in my array I contain 2 docs id ['e4z3HNyUCQbiYQRzfVO4RyebMCP2', 'WZImI6tl0IUFKbICEZzcciaUxKG2'], from this 2 docs, I would need to get their userPosts ordered by the creation date, but I would need to pass a skip and limit too. So from this 2 docs, I would limit 3 docs to get, but these 3 docs will be from the 2 posts ids I passed.
const queryBuild = await query(
collection(db, 'posts'),
where('userPosts', 'in', [`post.id`...]),
collection(db, 'userPosts'),
...
);
const querySnapshot = await getDocs(queryBuild);

There is no out-of-the-box way to do what you are looking for with Firestore.
You will need to issue two queries, one for each of the two parent document.
Let say, for example, that in total you only want 3 documents from the two sub-collections.
You first query the first subcollection with the following query:
const q1 = query(collection(db, "posts/e4z3HNyUCQbiYQRzfVO4RyebMCP2/userPosts"), orderBy("creation", "desc"), limit(3));
You then query the second subcollection with the following query:
const q2 = query(collection(db, "posts/WZImI6tl0IUFKbICEZzcciaUxKG2/userPosts"), orderBy("creation", "desc"), limit(3));
Then you merge the two results (i.e. push the documents of the two QuerySnapshots in one Array), order them by descending creation value and take the first three ones.

An alternative to Renaud's answer would be to store the postId of the parent document in each userPost. With that you could add an in clause to a collection group query to get the user posts of up to 10 parent documents.
query(
collectionGroup(db, "userPosts"),
orderBy("creation", "desc"),
where("parentPostId", "in", ["e4z3HNyUCQbiYQRzfVO4RyebMCP2", "WZImI6tl0IUFKbICEZzcciaUxKG2"]),
limit(3)
);
If you have more than 10 IDs, you'll need to perform multiple of such queries, so you'd be back to a similar approach as in Renaud answers, just with 1/10th as many API calls.

Related

With same timestamp firestore is returning the same result on different pages

I have written the following query for extracting documents from firestore.
await ctx.db
.collection("orderCollection")
.orderBy("timeStamp", "desc")
.orderBy("uuid")
.where(key, "==", ctx.uid)
.where("timeStamp", "<", parseInt(timeStamp))
.startAfter(lastResultUUID)
.withConverter(StoreOwnerROrderConvertor)
.limit(limitNum)
.get();
OrderCollection contains the info about the placed orders.
Multiple orders are placed at the same time. Every order has unique ID(UUID). On each page we will extract limitNum pages. lastResultUUID contains the UUID of the last extracted document. Suppose limitNum=5. And there are 10 orders placed at the same time so all order have same timestamp.The timestamp is in milliseconds. On page-1 I receive the 5 results and lastResultUUID is empty, but on page-2, I receive the same 5 results. While lastResultUUID contains the UUID of 5th item from the page-1. I am expecting the next 5 order info.
.startAfter() compares "lastResultUUID" with "timeStamp" in your code. You have to put variables in a same order as you use .orderBy() fields like below:
.startAfter(lastResultTimeStamp, lastResultUUID)
Or just pass whole DocumentSnapshot<T>:
.startAfter(lastDocumentSnapshot)
Just in case, the document you save to database is not a DocumentSnapshot<T>! Read documentation if you don't know what is DocumentSnapshot<T> so you know what to pass to method like .startAfter().

Firebase/Firestore start loop from certain document ID

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.

How to retrieve documents in firestore order with timestamp with React

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.

Generate Firestore document's doc Id based on Users' uids

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

Firestore "startAt" keeps bringing the same data

I am doing a pagination with firestore, the problem is that even if I change the startAt it still brings the same results.
An example of my problem
const snaps = await db.collection('blogs').
.orderBy('createdAt')
.startAt(0)
.limit(5).get();
const snaps2 = await db.collection('blogs').
.orderBy('createdAt')
.startAt(5)
.limit(5).get();
let billList = []
let billList2 = []
snaps.forEach(x => billList.push(x.data()) )
snaps2.forEach(x => billList.push(x.data()) )
console.log(billList)
console.log(billList2)
The pagination API doesn't work the way you are expecting. It startAt() doesn't accept an integer offset. As you can see from the linked API documentation, it requires either:
A DocumentReference of the document to start at
A array of field values relative to the order of the query (themselves also typically taken from documents
The paging API doesn't work with offsets at all. You can't skip ahead by N documents at a time. What you have to do is read N documents, then read the next N documents, and so on. I suggest reading the documentation on pagination for specific examples. Note that the first example is not requesting an offset of 1000000 - it's actually asking to start with cities at or above a population field value of 1000000.

Categories