How to perform advanced Sub Document Queries in MongoDB? - database

I have a Schema(Case) which has an array of Sub Schema(Assessment).
My Case Schema is as follows:
const caseSchema = new mongoose.Schema({
patient: {
type: String,
default: 'Password can\'t be empty'
}
description: {
type: String,
required: 'Case description can\'t be empty'
},
assessments: [assessmentSchema]
});
My Assessment Schema is as follows
const assessmentSchema = new mongoose.Schema({
doctor: {
type: String,
required: 'Doctor can\'t be empty'
},
doa: Date,
slots: String
});
My problem here is to find all the assessment documents in the entire collection(where the doctor and doa is specified). But as these details are available only in the Sub Schema, I have no idea on how to approach this problem. And I also cannot add a doctor field to the Case Schema as there might be many doctors in a case. I'm using mongoose. I searched many places on the internet and was not able to arrive at a solution. Please help me.

I think you need something like this.
This is an aggregate where $project stage is used to pass the documents with the requested fields.
The fields that pass to the next phase are the ones that match the filter.
Into the filter we use $eq condition to check 'doctor' and 'slots' fields.
Note that you have to use $ operator, that is the positional operator.
db.collection.aggregate([
{
"$project": {
"_id": 0,
"assessments": {
"$filter": {
"input": "$assessments",
"as": "ass",
"cond": {
"$eq": [
"$$ass.doctor",
"doctor1"
],
"$eq": [
"$$ass.slots",
"slots1"
]
}
}
}
}
}
])
Here is a Playground example
Edit: With this new query you can get all fields you want.
db.collection.aggregate([
{
"$project": {
"patient": 1,
"description": 1,
"assessments": {
"$filter": {
"input": "$assessments",
"as": "as",
"cond": {
"$eq": [
"$$as.doctor",
"doctor1"
],
"$eq": [
"$$as.slots",
"slots1"
]
}
}
}
}
}
])
Note there is only a little difference.
Into $project you can decide what fields you want to show/reset/... reference $project
So adding caseSchema into query fields do the works.

Related

Finding documents in mongodb collection by order of elements index of array field

Array field in collection:
"fruits": [ "fruits": [ "fruits": [
{"fruit1": "banana"}, {"fruit2": "apple"}, {"fruit3": "pear"},
{"fruit2": "apple"}, {"fruit4": "orange"}, {"fruit2": "apple"},
{"fruit3": "pear"}, {"fruit1": "banana"}, {"fruit4": "orange"},
{"fruit4": "orange"} {"fruit3": "pear"} {"fruit1": "banana"}
]
I need to find those documents in collections, where "banana" signed before "apple". Does mongodb allows to compare elements in array just like :
if (fruits.indexOf('banana') < fruits.indexOf('apple')) return true;
Or maybe there is any other method to get result i need?
MongoDB's array query operations do not support any positional search as you want.
You can, however, write a $where query to do what you want:
db.yourCollection.find({
$where: function() {
return (this.fruits.indexOf('banana') < this.fruits.indexOf('apple'))
}
})
Be advised though, you won't be able to use indexes here and the performance will be a problem.
Another approach you can take is to rethink the database design, if you can specify what it is you're trying to build, someone can give you specific advise.
One more approach: pre-calculate the boolean value before persisting to DB as a field and query on true / false.
Consider refactoring your schema if possible. The dynamic field names(i.e. fruit1, fruit2...) make it unnecessarily complicated to construct a query. Also, if you require frequent queries by array index, you should probably store your array entries in individual documents with some sort keys to facilitate sorting with index.
Nevertheless, it is achievable through $unwind and $group the documents again. With includeArrayIndex clause, you can get the index inside array.
db.collection.aggregate([
{
"$unwind": {
path: "$fruits",
includeArrayIndex: "idx"
}
},
{
"$addFields": {
fruits: {
"$objectToArray": "$fruits"
}
}
},
{
"$addFields": {
"bananaIdx": {
"$cond": {
"if": {
$eq: [
"banana",
{
$first: "$fruits.v"
}
]
},
"then": "$idx",
"else": "$$REMOVE"
}
},
"appleIdx": {
"$cond": {
"if": {
$eq: [
"apple",
{
$first: "$fruits.v"
}
]
},
"then": "$idx",
"else": "$$REMOVE"
}
}
}
},
{
$group: {
_id: "$_id",
fruits: {
$push: {
"$arrayToObject": "$fruits"
}
},
bananaIdx: {
$max: "$bananaIdx"
},
appleIdx: {
$max: "$appleIdx"
}
}
},
{
$match: {
$expr: {
$lt: [
"$bananaIdx",
"$appleIdx"
]
}
}
},
{
$unset: [
"bananaIdx",
"appleIdx"
]
}
])
Mongo Playground

How to query in a nested array and project only the matching items from array?

The structure looks like this:
{
clientName: "client1",
employees: [
{
"employeename": 1,
"configuration": {
"isAdmin": true,
"isManager": false
}
}
{
"employeename": 2,
"configuration": {
"isAdmin": false,
"isManager": false
}
}...
]
},
{
...
}
`
I want to see the employees who are admins inside a specific client, given that, I have the client name. How can I write a query in MongoDB for this? I want to be able to see (project) only employees who match?
Can I combine it to match multiple conditions? for example: someone who is an admin and a manager.
I have tried doing:
db.collection.find({clientName: "client1", "employees.configuration.isAdmin": true}, {"employees.employeename": 1})
This just return all the employees.
I've also tried using $elemMatch, to no avail.
Any help is appreciated.
You can do it with Aggregation framework:
$match - to filter the document based on clientName property
$filter with $eq - to filter only employees that have admins
db.collection.aggregate([
{
"$match": {
"clientName": "client1"
}
},
{
"$set": {
"employees": {
"$filter": {
"input": "$employees",
"cond": {
"$eq": [
"$$this.configuration.isAdmin",
true
]
}
}
}
}
}
])
Working example

How to fix MongoDB array concatination error?

I have a collection in mongodb with a few million documents. there is an attribute(categories) that is an array that contains all the categories that a document belongs to. I am using following query to convert the array into a comma separated string to add it to SQL server through a spoon transformation.
for example
the document has ["a","b","c",...] and i need a,b,c,.... so i can pit it in a column
categories: {
$cond: [
{ $eq: [{ $type: "$categories" }, "array"] },
{
$trim: {
input: {
$reduce: {
input: "$categories",
initialValue: "",
in: { $concat: ["$$value", ",", "$$this"] }
}
}
}
},
"$categories"
]
}
when i run the query i get the following error and i cannot figure out what the problem is.
com.mongodb.MongoQueryException: Query failed with error code 16702 and error message '$concat only supports strings, not array' on server
a few documents had this attribute as string and not array so i added a type check. but still the issue is there. any help on how to narrow down the issue will be very appreciated.
A few other attributes were the same in the same collection and this query is working fine for the rest of them.
I don't see any problem in your aggregation. It shouldn't give this error. Can you try to update your mongodb version?
However, your aggregation is not working properly reduce wasn't working . I converted it to this:
db.collection.aggregate([
{
"$project": {
categories: {
$cond: [
{
$eq: [{ $type: "$categories" }, "array"]
},
{
'$reduce': {
'input': '$categories',
'initialValue': '',
'in': {
'$concat': [
'$$value',
{ '$cond': [{ '$eq': ['$$value', ''] }, '', ', '] },
'$$this'
]
}
}
},
"$categories"
]
}
}
}
])
Edit:
So, if you have nested arrays in the categories field. We can flat our arrays with unwind stage. So if you can add these 3 stages above the $project stage. Our aggregation will work.
{
"$unwind": "$categories"
},
{
"$unwind": "$categories"
},
{
"$group": {
_id: null,
categories: {
$push: "$categories"
}
}
},
Playground

Merging nested object fields into a single nested array field in MongoDB Aggregation

As part of my aggregation pipeline I have the following scenario. This is the result of grouping previously unwound fields from each document (so in this case there are two documents with the same _id but with a different value for UniqueFieldName)
TopLevelField: [
{
UniqueFieldName: "Values go here!"
},
{
UniqueFieldName: "More values go here too!"
}
]
All I want to do is merge the nested object fields into one field and push all the values into that field as an array, like so.
TopLevelField: {
UniqueFieldName: [
"Values go here!",
"More values go here too!",
],
}
The idea is that I could have multiple fields with multiple values under each field grouped together for easier iteration.
TopLevelField: {
UniqueFieldName: [
"Values go here!",
"More values go here too!",
],
SecondFieldName: [
"This is text",
],
AnotherOne: [
"TEXT",
"Here too!",
"More values",
],
}
The problem I run into is that trying to use dot notation in the $group stage throws an error. It seems that mongo doesn't like to group with nested objects like this?
The easy solution is to just change the TopLevelField to some concatenation of the nested fields like this,
TopLevelField-UniqueFieldName: [
"Values go here!",
"More values go here too!",
],
TopLevelField-SecondFieldName: [
"This is text",
],
TopLevelField-AnotherOne: [
"TEXT",
"Here too!",
"More values",
],
But this is suboptimal for my use case. Is there a solution to this or do I need to rethink the entire pipeline?
You can try this :
db.collection.aggregate([
{ $unwind: '$TopLevelField' },
{
$group: {
_id: '', 'UniqueFieldName': { $push: '$TopLevelField.UniqueFieldName' },
'UniqueFieldName2': { $push: '$TopLevelField.UniqueFieldName2' },
'UniqueFieldName3': { $push: '$TopLevelField.UniqueFieldName3' },
'UniqueFieldName4': { $push: '$TopLevelField.UniqueFieldName4' }
}
}, { $project: { _id: 0 } }, { $project: { 'TopLevelField': '$$ROOT' } }])
Test : MongoDB-Playground

Mongo error - Can't canonicalize query: BadValue Unsupported projection option

I am new to MongoDB and trying to execute a query. I have a company collection and company IDs array. I would like to get the results where attributes.0.ccode exist and attributes.0.ccode is not empty and will be checked within the ids provided in an array( cdata)
var query = Company.find({ _id: { $in: cdata } },{ "attributes.0.ccode": { $exists: true }, $and: [ { "attributes.0.ccode": { $ne: "" } } ] }).select({"attributes": 1}).sort({});
The error I am getting is
"$err": "Can't canonicalize query: BadValue Unsupported projection option: attributes.0.ccode: { $exists: true }",
"code": 17287
I think it's a bracketing issue but can't figure it out where.
Any help is highly appreciated.
In your code { _id: { $in: cdata } } is interpreted as query, and everything else, starting from ,{ "attributes.0.ccode": { $e.. as a Projection (which field to display). Try to refactor your code so _id: {$in ...} and the rest of the query belong to the same higher - level object. Something like this:
var query = Company.find({
_id: {
$in: cdata
},
"attributes.0.ccode": {
$exists: true
},
$and: [
{
"attributes.0.ccode": {
$ne: ""
}
}
]
}).select({"attributes": 1}).sort({});

Resources