Query value in sub-document dictionary MongoDB - mongodb

I have the following document:
"_id" : 19,
"name" : "Elizabeth Moore",
"achronym" : "EM19",
"calc" : {
"20" : {
"role" : 20,
"score" : 15,
"inRole" : false,
"range" : {
"int" : 80,
"min" : 20
}
and I need to retrieve all _ids having "calc.inRole" false.
I tried:
db.coll.find({'calc.$.inRole': false})
db.coll.find({'calc.inRole': false})
but none of these worked.
How can I achieve that?

Since calc has fields with unknown keys you need to run $objectToArray to transofrm it into array of keys and values. Then you can run $in on that array. If you want to have it as single pipeline step you can use $let operator to define temporary variable:
db.collection.aggregate([
{
$match: {
$expr:{
$let: {
vars: {
arr: { $objectToArray: "$calc" }
},
in: {
$in: [ false, "$$arr.v.inRole" ]
}
}
}
}
},
{
$project: {
_id: 1
}
}
])
Mongo Playground

Related

How can I find the sum and average of a document array?

Currently, I have the following document structure. The range field holds sub JSON objects as an array.
{
"_id" : ObjectId("62f60ba0ed0f1a1a0v"),
"userId" : "1431",
"range" : [
{
"index" : 0,
"clubType" : "driver",
"swingSize" : "full",
"distance" : 200,
"createdAt" : "2022-08-12T08:13:20.435+00:00"
},
{
"index" : 0,
"clubType" : "driver",
"swingSize" : "full",
"distance" : 150,
"createdAt" : "2022-08-12T08:13:20.435+00:00"
},
{
"index" : 0,
"clubType" : "wood",
"swingSize" : "full",
"distance" : 180,
"createdAt" : "2022-08-12T08:13:20.435+00:00"
}
]
}
In the above document, I want to sum and average the indexes with the same clubType and swingSize. So I used mongoose Aggregate like below.
result = await ClubRangeResultSchema.aggregate([
{
$match : {
userId : "1431",
range : {
$elemMatch : {
$and : [
{
createdAt : { $gte : lastDate }
},
{
createdAt : { $lte : lastDate }
}
]
}
}
}
},
{
$group : {
'_id' : {
'clubName' : '$range.clubName',
'swingSize' : '$range.swingSize'
},
'totalDistance' : { $sum : { $sum : '$range.distance' }}
}
}
]);
The result of the above query is all duplicate field names, and the total is also extracted for all data.
How should I modify the query?
You're close but need to do a couple of changes:
you want to $unwind the range array, $group doesn't flattern the array so when you use $range.clubType you are basically grouping the array itself as the value.
You want an additional match after the $unwind, the $elemMatch you use does not filter the range object, it does matches the initial document.
After the changes the pipeline should look like this:
db.collection.aggregate([
{
$match: {
userId: "1431",
range: {
$elemMatch: {
createdAt: "2022-08-12T08:13:20.435+00:00"
}
}
}
},
{
$unwind: "$range"
},
{
$match: {
"range.createdAt": "2022-08-12T08:13:20.435+00:00"
}
},
{
$group: {
"_id": {
"clubName": "$range.clubType",
"swingSize": "$range.swingSize"
},
"totalDistance": {
$sum: "$range.distance"
},
avgDistance: {
$avg: "$range.distance"
}
}
}
])
Mongo Playground

I need limited nested array in mongodb document

I have a document like
{
"deviceId" : "1106",
"orgId" : "5ffe9fe1c9e77c0006f0aad3",
"values" : [
{
"paramVal" : 105.0,
"dateTime" : ISODate("2021-05-05T09:18:08.000Z")
},
{
"paramVal" : 110.0,
"dateTime" : ISODate("2021-05-05T09:18:08.000Z")
},
{
"paramVal" : 115.0,
"dateTime" : ISODate("2021-05-05T10:18:08.000Z")
},
{
"paramVal" : 125.0,
"dateTime" : ISODate("2021-05-05T11:18:08.000Z")
},
{
"paramVal" : 135.0,
"dateTime" : ISODate("2021-05-05T12:18:08.000Z")
}
]
}
Now I need to filter a document which I can do easily with match or find but in that document the subarray i.e. values should have latest 2 values because in future the count can be more than 100.
the output should be like
{
"deviceId" : "1106",
"orgId" : "5ffe9fe1c9e77c0006f0aad3",
"values" : [
{
"paramVal" : 125.0,
"dateTime" : ISODate("2021-05-05T11:18:08.000Z")
},
{
"paramVal" : 135.0,
"dateTime" : ISODate("2021-05-05T12:18:08.000Z")
}
]
}
Try $slice operator, to select number of elements, pass negative value to select documents from below/last elements,
db.collection.aggregate([
{ $set: { values: { $slice: ["$values", -2] } } }
])
Playground
I need for the array values in sorted order by date
There is no straight way to do this, check the below aggregation query, but it will cause the performance issues, i would suggest to change you schema structure to manage this data order by date,
$unwind deconstruct values array
$sort by dateTime in descending order
$group by _id and reconstruct values array and return other required fields
$slice to select number of elements, pass negative value to select documents from below/last elements
db.collection.aggregate([
{ $unwind: "$values" },
{ $sort: { "values.dateTime": -1 } },
{
$group: {
_id: "$_id",
deviceId: { $first: "$deviceId" },
orgId: { $first: "$orgId" },
values: { $push: "$values" }
}
},
{ $set: { values: { $slice: ["$values", 2] } } }
])
Playground

Check if subdocument in range complies with a condition

I'm working on a mongoDB query.
I have several documents which I query with following results:
{
"_id" : 1000.0,
"date" : ISODate("2018-05-25T00:20:00.000Z"),
"value" : true
}
{
"_id" : 1000.0,
"date" : ISODate("2018-05-25T00:26:00.000Z"),
"value" : false
}
{
"_id" : 1000.0,
"date" : ISODate("2018-05-25T00:30:00.000Z"),
"value" : false
}
The original documents are filtered so that I get only document within the last 15 minutes before now and there is no way of knowing how many entries are in that time range.
I need to expand my existing query so that it returns a status based on the "value". If there are no true I need a status 0, if there is at least 1 but not only true I need a status 1, and if there are only true I need a status 2.
For example:
{
"_id" : 1000,
"status" : 1
},
{
"_id" : 1001,
"status" : 2
}
Is there a way of accomplishing this using mongoDB? Or would it be better/easier to do it on java side? Note that there are several _id in the database.
You can gather all values from each group into one array (using $group and $push) and then use $switch to apply your logic. To determine whether array contains any true value or all values are true you can use $anyElementTrue and $allElementsTrue:
db.col.aggregate([
{
$group: {
_id: "$_id",
values: { $push: "$value" }
}
},
{$unwind:"$values"},
{
$project: {
_id: 1,
status: {
$switch: {
branches: [
{ case: { $allElementsTrue: "$values" }, then: 2 },
{ case: { $anyElementTrue: "$values" }, then: 1 },
],
default: 0
}
}
}
}
])

How can I query mongodb collection for an array nested in an array of a document?

I have a mongo collection containing structurally similar documents as illustrated below-
{
"_id" : ObjectId("mongoid"),
"type" : "chemical",
"sourceId" : "27553452120",
"array1" : [
{
"cid" : "1235689",
"outcome" : "test",
"relation" : "=",
"array2" : [
{
"name" : "test1"
},
{
"name" : "test2"
},
{
"value" : 1.628,
"name" : "test3"
},
{
"value" : 1.63,
"name" : "test4"
}
]
}
]
}
I want to query this collection for a case, where array1.array2.length > 1
I tried following sample query on mongo shell:
db.collection.find({"array1.array2":{$exists:true},$where:"this.array1.array2.length>1"}).limit(1).pretty()
but it fails stating
Error: error: {
"ok" : 0,
"errmsg" : "TypeError: this.array1.array2 is undefined :\n#:1:15\n",
"code" : 139,
"codeName" : "JSInterpreterFailure"
}
How can this query be achieved?
This should solve your problem.
db.test.aggregate(
{
$match:{"array1.array2": {$nin:[null,[]]}}
}
).pretty();
Try this
db.collection.find({"array1.array2":{$exists: true, $ne : []} })
According to description as mentioned into above question please try executing following aggregate query as a solution.
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$match: {
array1: {
$elemMatch: {
array2: {
$exists: true
}
}
}
}
},
// Stage 2
{
$project: {
array2size: {
$size: {
$arrayElemAt: ['$array1.array2', 0]
}
},
array1: 1
}
},
// Stage 3
{
$match: {
'array2size': {
$gt: 0
}
}
},
]
);
What about this way:
db.test.find({"array1.array2.1":{$exists:1}})
This is more flexible, it allow you to query for any length not only 1.
Last ".1" it is zero based array index. So if index 1 exists it means array has at least 2 elements. You can search for < N by changing $exists to 0. If no index 3 in array it means array has less then 4 elements.
If you need to search for exact size you can use $size operator
db.test.find({"array1.array2":{$size:5}})

In MongoDB how to find documents where property is object but not Array

I have collection users where each contains property gyms e.g.:
{
"_id" : ObjectId("aaaaa"),
"firstName" : "first",
"lastName" : "second",
"email" : "aaa#aaaa",
"password" : "aaaaa",
"gyms" : [
{
"name" : "aaaaaa",
"address" : "aaaaaaaaaaaaa"
}
]
}
However when I run db.users.aggregate(..) I get:
exception: Value at end of $unwind field path '$gyms' must be an Array, but is a Object
It seems that some of the users documents contain gym:{} or no gym at all rather than array. I need to find these documents, how would I do that?
EDIT:
Aggregate command I run:
db.users.aggregate({ $unwind: "$gyms" }, { $match: { "gyms.address": { $exists: true } } } )
Using {$type: "object"} gives you results containing documents with arrays too. So, to check for pure objects, just add an extra check to ensure that arrays are discarded, viz., obj.0.key: {$exists: false}.
In your case,
db.users.find({"gyms": {$type: "object"}, "gyms.0.name": {$exists: false}}).map(function(u) {
// modify u.gyms to your satisfaction
db.conversations.save(c);
})
Try this may be it also help you
db.users.aggregate([{
"$match": {
"$nor": [{
"gyms": {
"$exists": false
}
}, {
"gyms": {
"$size": 0
}
}, {
"gyms": {
"$size": 1
}
}]
}
}, {
"$unwind": "$gyms"
}]).pretty()
Try something like this:
> db.test.drop()
> db.test.insert({ "x" : ["an array"] })
> db.test.insert({ "x" : { "type" : "object" } })
> db.test.find({ "x" : { "$type" : 3 } })
{ "x" : { "type" : "object" } }