zid
zid12mo ago

Complex queries based off of items in an array

I'd like to be able to: .widthIndex("by_userId_primarySkills_secondarySkills", q => q.includes("primarySkills", arrayItem)) I have some complex query requirements and the above would help immensely! But perhaps there's another way to efficiently accomplish this? I have a users table and a separate table skills Each skills document has a type field that can hold one of three types primary, secondary, and lookingFor. Each user can have up to 3 primary types/docs, 3 secondary types/docs, and 6 lookingFor types/docs. With this schema, it's challenging to write a complex query without a lot of application logic and costly ($$) additional reads. But if I could denormalize the data for users to have users.primarySkills where primarySkills is an array, and if I could query at the array item level, that would be a seamless solution
9 Replies
zid
zidOP12mo ago
I should note that the above is a simplified example, mainly to illustrate the problem. i mention this because the following workaround might not make complete sense with the above at face value. my current workaround is to create a separate table purely for querying purposes and use a join thereafter. Problem is, I would have to forego querying of secondarySkill , essentially limiting my end user's ability to filter optimally.
usersSelections (purely for querying)
userId
status
primarySkill
usersSelections (purely for querying)
userId
status
primarySkill
Essentially, im trying to find a way to write efficient queries where the fields i want to query against live on two separate tables.
Michal Srb
Michal Srb11mo ago
Hey @zid, I think our answer is that Convex is a relational database, and performing joins should be totally ok. You shouldn't really have to worry about the cost (although our current pricing with its minimum document size is perhaps steering you in the wrong direction). convex-helpers have some handy utils for performing joins. I'm curious what is the actual query you're trying to write (I understand I think the data model, but it's not clear what you're trying to query).
zid
zidOP11mo ago
Hey Michal! Sorry for the delay, I just saw this. I'm actually in between errands atm so simply touching base but I will say that for this specific issue, I re-made the entire experience so not too concerned with this one anymore but i will try to share briefly from what i recall. First, from a user perspective, I want the user to be able to query users withIndex essentially from two tables: .widthIndex("by_boolOne_boolTwo_primarySkill_secondarySkill" - boolOne is from users, and it is a boolean - boolTwo is from users also, and it is a boolean - primarySkill is a record from skills, that can be of 2 types primary or secondary. type: v.union(v.literal("primary"), v.literal("secondary"))
- secondarySkill is a record from table 2, ...
skills: defineTable({
userId: v.id("users"),
type: v.union(v.literal("primary"), v.literal("secondary")),
skill: v.union(...
skills: defineTable({
userId: v.id("users"),
type: v.union(v.literal("primary"), v.literal("secondary")),
skill: v.union(...
Michal Srb
Michal Srb11mo ago
Thanks @zid! How would you describe the query without referring to the code? For example: "I want the logged in user to see the list of primary skills they have". I'm still a bit unclear on the use case.
zid
zidOP11mo ago
there is apage with a list of users. i want each user to be able to filter these users using a variety of options. These options can be stacked on top of each other. An example could be, I want a list of users with: - a primary skill of {value} - a secondary skill of {value} - is looking for {value} opportunities ecommerce stores could be more illustrative as their filtering menus involve a wide array of checkboxes/filters that can be stacked on top of each other.
Michal Srb
Michal Srb11mo ago
I see. Are "all" the filters required, or "some"? If "some", this can be done as a union of three individual joins. If "all", you could either perform an intersection of the individual joins, or have the data denormalized in a table:
| user_id | primary_skill | secondary_skill | looking_for |
| user_id | primary_skill | secondary_skill | looking_for |
with all the permutations. Then you could use withIndex. I think you have probably figured this all out already. "Joins" (in JS) and denormalization plus withIndex are the tools at your disposal for efficient querying, and you could also filter in JS for a less efficient approach. Any other database would use one of these under the hood, with Convex you get to decide what approach fits your app the best.
zid
zidOP11mo ago
its not clear to me what three individual joins means precisely. Do you mean make three separate queries and concatenate them together with my own app logic? the latter is the approach i've been trying to figure out, but the main challenge iswriting an elegant algorithm that handles all the potential permutations. Specifically, I need to account for scenarios where I essentially want to ignore primary_skill, thus including all users regardless of their primary skill. Same with the other arguments (secondarySkill, etc). hm by tweaking the experience a little, arguably somewhat cleaner/more intuitive, i reduced the permutations into a manageable if/else statement tree. Still have to test
Michal Srb
Michal Srb11mo ago
Yeah, making seperate database queryies and doing a union or intersection in JavaScript is what I meant. To ignore primary_skill you could set it to null in the denormalized table, maybe?
zid
zidOP11mo ago
Yep, null is one of the allowed values. What helped was to introduce a single piece of state client-side to be responsible for managing a specific type/category of choices which points to multiple fields in the document. Appreciate your help Michal, thank you!

Did you find this page helpful?