0

Firebase/Firestore Experts.

So I have got a question about structure, as I have now run into a problem, and it got me thinking if I actually have the right structure.

I'll try to explain... I have an application where it is possible for companies to create a profile. Each company has a companyId. Each company can create courses, which means I have a collection named "Courses". This collection looks like this:

CourseCollection

  • Doc (CompanyID)
    • Subcollection (Categories)
    • Subcollection (Courses)
  • Doc (CompanyID)
    • Subcollection (Categories)
    • Subcollection (Courses)

The reasoning for structuring like this was so it was easy to have a collection that had the specific companies and the company's own categories and courses. I thought that might be a smarter solution than having a flat collection of all courses ever created and having a companyId as a property on the course. I don't know if this solution is better or if it is better to have a flat structure.

Just to mention, a course document holds an "events" array, where each "event" has a start date.

Anyway, now I want to set up a function to run daily. The function I am setting up needs to run through all courses for all companies and see if it has any active events for "today" and then send out an email to a user that is signed up for the course. However, then I found myself not being able to query the "Courses" collection - The following returns me nothing

export const dailyCourses = async () => {
  const coursesByCompanies = query(collection(db, 'courses'));
  const checkCourseForNewClientProgramSnapshot = await getDocs(coursesByCompanies);
  checkCourseForNewClientProgramSnapshot.forEach((courseSnapshot) => {
    console.log('courseSnapshot', courseSnapshot.data());
  });
};

However, if I specify a companyId and the subcollection, I can get all the courses for the given companyId

export const dailyCourses = async () => {
  const coursesByCompanies = query(collection(db, 'courses/**someId**/courses'));
  const checkCourseForNewClientProgramSnapshot = await getDocs(coursesByCompanies);
  checkCourseForNewClientProgramSnapshot.forEach((courseSnapshot) => {
    console.log('courseSnapshot', courseSnapshot.data());
  });
};

Now, that got me thinking that it might not be possible to loop over all root documents in the courses collection and get all the "courses" (subcollection) for each company, without me knowing each companyId beforehand to look up each course in the subcollection?? Which means I could have to query all companies in my "companies" collection before and get all ids of the companies which might not be the best, as all companies may haven't created any courses, which would cause me to do a lot of unnecessary reads?

So now I'm thinking that I might be better off actually having a flat structure, after all, containing all courses and then querying them by companyId. Then I can loop all courses and the events and see which events match the current day (today).

As you might hear, I'm a bit confused about which is the best solution, which is why I'm looking for any expert advice. I'm thinking I'm not the first to have run into an issue like this or similar before.=

I know a lot of you would say "Read the docs" or "It depends on the use case", etc - I have read the docs, however, this doesn't say anything about which solution I would/should use in THIS specific case, so I'm REALLY hoping that someone could give me their thoughts and perspectives. It should also be said I'm still quite new to Firebase/Firebase, so if you can see that there is an even better solution for it than I have proposed, I'm all ears :)

THANK YOU for your time in advance :) I'm really hoping a helpful soul is coming my way ;)

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
envy
  • 327
  • 1
  • 5
  • 14

2 Answers2

1

it got me thinking if I actually have the right structure.

We are usually structuring a Firestore database according to the queries that we want to perform. So the following answer will most likely help.

The reasoning for structuring like this was so it was easy to have a collection that had the specific companies and the company's own categories and courses.

The shared structure looks good to me.

I thought that might be a smarter solution than having a flat collection of all courses ever created and having a companyId as a property on the course. I don't know if this solution is better or if it is better to have a flat structure.

None of the solutions is better than the other. If any structure allows you to query and get the correct results, then that's the one you should go ahead with.

The function I am setting up needs to run through all courses for all companies

That can be achieved in a very simple way, by using a collection group query, which will allow you to get the desired data from all sub-collections called for example, "courses".

Besides that, your first code snippet doesn't work because there is no top-level collection called "courses". The second snippet works because you are pointing exactly to the courses of a particular company.

Now, that got me thinking that it might not be possible to loop over all root documents in the courses collection and get all the "courses" (subcollection) for each company

No need for that.

As you might hear, I'm a bit confused about which is the best solution, which is why I'm looking for any expert advice.

There is no best solution. However, your schema can solve the queries that you were talking about.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thanks a lot for taking the time to answer, actually helped quite a lot... One question though: If I change it to "collectionGroup", then I wont only get the "courses", but also the categories, as I have 2 subcollections under "each" course/companyId... Hope that makes sense? If there any way around this, so I only get the "courses" ? – envy Aug 02 '22 at 07:36
  • No, you'll **only** get the "courses". You will **not** get any other data that exists in any other sub-collections. – Alex Mamo Aug 02 '22 at 07:52
  • AH, but now I'm getting from all subcollections throughout the entire DB that is called courses... I have multiple places where I have subcollections under called "courses"... I only want the subcollections called "courses" under the root collection "courses" and not throughout the entire application – envy Aug 02 '22 at 08:29
  • That's the expected behavior since this is how a collection group query works. So in this case, you have two options here, you either rename all the other sub-collections and provide an alternative name so it can be excluded, or you can implement the solution in this [article](https://medium.com/firebase-developers/how-to-query-collections-in-firestore-under-a-certain-path-6a0d686cebd2). – Alex Mamo Aug 02 '22 at 08:33
  • That makes sense... However, then just a question... Wouldn't it then be a "better" solution to flatten the data and have a collection of "courses" where I can just filter then by "companyId" when I need to... Then it allows be to easily get all courses, but also only the courses for specific companies? Or would this solution be "too many" reads as I'm potentially going through "all" course documents everytime I want to filter by companyId ? – envy Aug 02 '22 at 08:47
  • That can be a solution too, but if you perform a query using filtering and ordering, don't forget to create an [index](https://stackoverflow.com/questions/50305328/firestore-whereequalto-orderby-and-limit1-not-working). Besides that, in Firestore you'll pay according to the number of documents you request and **not** on the number of documents you search. So it doesn't really matter if you search 10 documents in a collection of 100 documents or in a collection of 100 MIL documents, you'll always have to pay the same price. As you see, the number of documents within a collection is irrelevant. – Alex Mamo Aug 02 '22 at 08:56
  • 1
    Ah, I actually though you paid more for searching a 1000 documents rather than 100 documents... That was actually why I have grouped the DB structure as I did (Having them grouped in subcollection under each companyId) - Then I might need to reconsider my structure :D – envy Aug 02 '22 at 09:13
0

For simplicity I'd prefer having a separate collection for courses with a reference to the company it belongs to.

Not only would it reduce reads, but it makes your code a little easier to write and maintain. Plus if there are some option in the future to have public courses etc.

Not really sure if there is more I can add to it, but it's as simple as the above if I was to do it.

Werner7
  • 333
  • 1
  • 6
  • So just to clearly understand: So you would have all courses in one collection (All courses for all companies?) - And then have a reference like companyId on the course? Im just thinking that it would increase reads, as you would need to read all documents in the courses collection instead of only the one belonging to the company (the subcollection for the company)? – envy Aug 01 '22 at 22:35
  • https://cloud.google.com/firestore/docs/query-data/queries#simple_queries You would set up a simple query, where CompanyId == x. This will only read/return those that match. – Werner7 Aug 01 '22 at 22:56
  • Okay, but the "course document" doesn't contain the companyId, as that is defined by the structure instead.. Doc (CompanyID) -Subcollection (Categories) - Subcollection (Courses) (These document doesn't contain companyId as the is the doc above that has defined this, as it is stored under the companyId) Hope that makes sense? Also, I'm not interested in the companyId - Im just interested in getting all the course documents (And not the categories) for all companies – envy Aug 02 '22 at 07:53