Mongodb $match and $project aggregation - mongodb

I have this very simple set of documents.
> db.ysTest.aggregate({$project:{_id:1,unitStatus:1}});
{
"result" : [
{
"_id" : ObjectId("514309f3e18aa7d14100217a"),
"unitStatus" : "es_pws"
},
{
"_id" : ObjectId("514309f3e18aa7d141002816"),
"unitStatus" : "es_run"
},
{
"_id" : ObjectId("514309f0e18aa7d14100021e")
}
],
"ok" : 1
}
When use 'aggregate' using $match & $project, i expect 1 document but i get them all.
note: I'm using aggregate because this is going to be part of a more complicated match, but i tried to keep it simple for this example.
> db.ysTest.aggregate({
... $match: {
... unitStatus: {$exists: true, $nin: ["es_pws", "es_stl"]}
... },
... $project: {_id: 1,unitStatus:1}
... });
{
"result" : [
{
"_id" : ObjectId("514309f3e18aa7d14100217a"),
"unitStatus" : "es_pws"
},
{
"_id" : ObjectId("514309f3e18aa7d141002816"),
"unitStatus" : "es_run"
},
{
"_id" : ObjectId("514309f0e18aa7d14100021e")
}
],
"ok" : 1
}
What am i doing wrong ?

By looking at your document, query and the comments it is clear that you're not using $group operator and $match is simply a select clause which filter the result based on your given criteria. in your case
... $match: {
... unitStatus: {$exists: true, $nin: ["es_pws", "es_stl"]}
... }
But $match and $group doesn't guarantee that it will return one document. what guarantee is your schema, query criteria.

Related

Ho use $sum (aggregation) for array of object and check greater than for each sum

My document structure is as follow :
{
"_id" : ObjectId("621ccb5ea46a9e41768e0ba8"),
"cust_name" : "Anuj Kumar",
"product" : [
{
"prod_name" : "Robot",
"price" : 15000
},
{
"prod_name" : "Keyboard",
"price" : 65000
}
],
"order_date" : ISODate("2022-02-22T00:00:00Z"),
"status" : "processed",
"invoice" : {
"invoice_no" : 111,
"invoice_date" : ISODate("2022-02-22T00:00:00Z")
}
}
How to do the following query...
List the details of orders with a value >10000.
I want to display only those objects whose sum of prices is greater than 10000
I try this
db.order.aggregate([{$project : {sumOfPrice : {$sum : "$product.price"} }}])
Output
{ "_id" : ObjectId("621ccb5ea46a9e41768e0ba8"), "sumOfPrice" : 80000 }
{ "_id" : ObjectId("621ccba9a46a9e41768e0ba9"), "sumOfPrice" : 16500 }
{ "_id" : ObjectId("621ccbfaa46a9e41768e0baa"), "sumOfPrice" : 5000 }
I want to check this sumOfPrice is greater than 10000 or not and display those order full object.
You can just add a $match stage right after that checks for this conditions, like so:
db.collection.aggregate([
{
$addFields: {
sumOfPrice: {
$sum: "$product.price"
}
}
},
{
$match: {
sumOfPrice: {
$gt: 10000
}
}
}
])
Mongo Playground
You can also use $expr operator with the find query as:
db.order.find({
$expr: {
$gt: [ {$sum: '$product.price'}, 10000 ]
}
})
Mongo Playground

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}})

mongodb: check condition in aggregation

I am using mongodb 3.6. I have many document in my collection.inside the document i do not have any Domain field. I create Domain for some document.
Now I want to use aggregate for filtering this collection. that is mean, I want those documents that have not Domain as a field.
db.Events.aggregate([
{$project : {
Domain : {$filter: {
input: "$Domain",
cond:{if: {Domain : {$exists: false}}, then: {"$BusinessCode": 1} }}}
}
}
],{
allowDiskUse: true
})
when I execute this script I got error:
Assert: command failed: {
"ok" : 0,
"errmsg" : "Unrecognized expression '$exists'",
"code" : 168,
"codeName" : "InvalidPipelineOperator"
} : aggregate failed
seems $exists is not supported into $filter expression.
How could I do that?
Another question is: Can I use 2 $project like this:
db.Events.aggregate([
{$project : {
Domain : {$filter: {
input: "$Domain",
cond:{if: {Domain : {$exists: false}}, then: {"$BusinessCode": 1} }}}
}
},
{
$match : {BusinessCode: /(([1-2]?[0-9])-([0-9]*)-([0-9]*)-([0-9]*)-([0-9]*)-([0-9]*)-([0-9]*))/}
},
{
$project : {BusinessCode : {$arrayElemAt:[{$split : ["$BusinessCode", "-"]},0]}}
},
{
$addFields: {"Domain": "$BusinessCode"}
},
],{
allowDiskUse: true
})
I want to check, does specific field is there into document. if does not exist, BusinessCode projected and other stuff..
***************************Edit****************
this is my sample of documents:
"DeviceId" : "xxxxxxx",
"UserId" : UUID(""),
"UserFullName" : "test-user",
"SystemId" : "com.messaging",
"SystemTitle" : "message",
"EventId" : "messaging.message",
"EventTitle" : "test",
"EventData" : [],
"BusinessCode" : "1-2-4-4-5-6-9",...
After execute this script, I expect "Domain" append to my document like this:
"EventTitle" : "test",
"EventData" : [],
"BusinessCode" : "1-2-4-4-5-6-9"
"Domain": "1" // 1 is first number of BusinessCode that splitted
but if Domain was exist script goes to next document and check again.
So you're looking for a something like COALESCE in SQL and it is called $ifNull in MongoDB. For instance:
db.Events.save({Domain: "4"})
db.Events.save({BusinessCode: "1-2-4-4-5-6-9"})
db.Events.aggregate([
{
$project: {
Domain: {
$ifNull: [ "$Domain", { $arrayElemAt: [ { $split : ["$BusinessCode", "-"] },0] } ] }
}
}
])

combining distinct on projection in mongodb

Is there a query i can use on the following collection to get the result at the bottom?
Example:
{
"_id" : ObectId(xyz),
"name" : "Carl",
"something":"else"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny",
"something":"else"
},
{
"_id" : ObectId(bbb),
"name" : "Carl",
"something":"other"
}
I need a query to get this result:
{
"_id" : ObectId(xyz),
"name" : "Carl"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny"
},
A set of documents with no identical names. Its not important which _ids are kept.
You can use aggregation framework to get this shape, the query could look like this:
db.collection.aggregate(
[
{
$group:
{
_id: "$name",
id: { $first: "$_id" }
}
},
{
$project:{
_id:"$id",
name:"$_id"
}
}
]
)
As long as you don't need other fields this will be sufficient.
If you need to add other fields - please update document structure and expected result.
as you don't care about ids it can be simplified
db.collection.aggregate([{$group:{_id: "$name"}}])

MongoDb sum list with condition

I have the following data:
{ "_id" : ObjectId("55fbffbdebdbc43337b08946"), "date" : 1442578343617,
"body" : { "entries" : [
{ "url" : "google.com/randomString", "time" : 143.832},
{ "url" : "youtube.com/randomString", "time" : 170.128},
{ "url" : "google.com/randomString", "time" : 125.428}
] } }
And I want to sum the time that takes to load the google.com webpages.
What I am trying to do is:
db.har.aggregate([
{$match: {date: 1442578343617, "body.entries.url": /google/}},
{ $unwind : "$body.log.entries"},
{ $group : {"_id" : 123,"total" : {$sum:"$body.entries.time"}}}
])
But the result I get is the total sum: { "_id" : 123, "total" : 439.388 }
How do I filter by body.entries.url?
Thank you very much for your time
Here you are unwinding wrong array body.log.entries.
You need to first match by date timestamp to filter out documents and then use $unwind and again match body.entries.url like :
db.collection.aggregate([{
$match: {
date: 1442578343617
}
}, {
"$unwind": "$body.entries"
}, {
$match: {
"body.entries.url": /google/
}
}, {
$group: {
"_id": null, //you can use any other param here
"total": {
$sum: "$body.entries.time"
}
}
}])
Filtering by url before unwinding keeps all the documents that contain a google url. But it will also keep the other urls of a document that contains google (in this case: youtube). So when you unwind you will still have those youtube urls and never filter them.
So just:
db.har.aggregate([
{$match: {date: 1442578343617},
{$unwind : "$body.log.entries"},
{$match: {"body.entries.url": /google/},
{$group: {"_id" : 123,"total" : {$sum:"$body.entries.time"}}}
])