Mongo: how to retrieve ONLY subdocs that match certain properties - mongodb

Having, for example, a collection named test and the following document is inside:
{
"_id" : ObjectId("5692ac4562c824cc5167379f"),
"list" : [
{
"name" : "elem1",
"type" : 1
},
{
"name" : "elem2",
"type" : 2
},
{
"name" : "elem3",
"type" : 1
},
{
"name" : "elem4",
"type" : 3
},
{
"name" : "elem4",
"type" : 2
}
]
}
Let's say I would like to retrieve a list of only those subdocuments inside list that match:
type = 2.
I've tried the following query:
db.getCollection('test').find({
'_id': ObjectId("5692ac4562c824cc5167379f"),
'list.type': 1
})
But the result I get contains every subdocument inside list, and I guess this is because inside list there are at least one document which's type equals 1.
Instead of that, the result I am interested to obtain would be every subdocument inside list that matches 'list.type': 1:
{
"_id" : ObjectId("5692ac4562c824cc5167379f"),
"list" : [
{
"name" : "elem1",
"type" : 1
},
{
"name" : "elem3",
"type" : 1
}
]
}
...so $and $elemMatch is not what I am really looking for as they return just the first matching element.
Anyone knows how to achieve what I am looking for?

db.myCol.aggregate([
{ $unwind: "$list" },
{ $match: { "list.type":1 } },
{ $group: { "_id":"$_id", list: {$push:"$list"}} }
])

Related

Mongodb Query to get the nth document

I need to create a query in mongodb that needs to return the SECOND TO THE LAST document. I am planning to use $group for this query but i dont know what aggregation function to use. I only know $first and $last.
I have an example collection below and also include the expected output. Thank you!
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84be"),
"userid" : ObjectId("5ffe702d59a9205db81fcb69"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),
"userid" : ObjectId("5ffe644f9493e05db9245192"),
"action" : "EDITPROFILE"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bc"),
"userid" : ObjectId("5ffe64949493e05db9245197"),
"action" : "DELETETRANSACTION"
} ]
"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bb"),
"userid" : ObjectId("5ffe64b69493e05db924519b"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84ba"),
"userid" : ObjectId("5ffe65419493e05db92451d4"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b9"),
"userid" : ObjectId("5ffe65689493e05db92451d9"),
"action" : "CHANGEACCESS"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),
"userid" : ObjectId("5ffe65819493e05db92451dd"),
"action" : "DELETETRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b7"),
"userid" : ObjectId("5ffe65df9493e05db92451f3"),
"action" : "EDITPROFILE",
]
OUTPUT:
{"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),"userid" : ObjectId("5ffe644f9493e05db9245192"),"action" : "EDITPROFILE"},
{"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),"userid" : ObjectId("5ffe65819493e05db92451dd"),"action" : "DELETETRANSACTION"}
You can't have two _id keys in one single object.
I've made the parent object's id to _parentId you can give it's a name anything you want except _id
Aggregation:
db.collection.aggregate([
{
$unwind: "$auditlogs"
},
{
"$project": {
"_parentId": "$_id",
"_id": "$auditlogs._id",
"action": "$auditlogs.action",
"userid": "$auditlogs.userid",
}
}
])
Playground
You can slice the array by -2 to get the last two item, then by 1 to get first one. Therefore, the array will be left the second to the last. Finally, unwind auditlogs so it can be changed from array to object which is structure that you want.
db.collection.aggregate([
{
$project: { auditlogs : { $slice: [ "$auditlogs", -2 ] } }
},
{
$project: { auditlogs : { $slice: [ "$auditlogs", 1 ] } }
},
{
$unwind: "$auditlogs"
}
])

Search array for dates within a range

I am new to MongoDB, keep that in mind as I ask my questions please. I have the following collection named 'documents' where I have an array named attributes which contains key:value pairs of different types.
{
"_id" : ObjectId("5d376c67f6c305c7571f7dd7"),
"name" : "testContract.pdf",
"fileType" : "pdf",
"attributes" : [
{
"abc" : 1
},
{
"def" : ISODate("2012-12-01T08:00:00Z")
},
{
"ghi" : "test"
}
]
}
{
"_id" : ObjectId("5d376ca4f6c305c7571f7dd8"),
"name" : "1099.pdf",
"fileType" : "pdf",
"attributes" : [
{
"def" : ISODate("2012-06-03T07:00:00Z")
},
{
"ghi" : "taxes"
}
]
}
What I would like to do is return one or more documents from this collection that fit within a date range. For example I can return all documents that have a fileType of 'pdf' with the following query -->
db.documents.find({"fileType":"pdf"});
But what I am trying to figure out is can I search an array containing different data types like dates in a range successfully while also being able to search for strings. I can also search the attributes array with the following -->
db.documents.find({"attributes":{"ghi":"test"}});
Here is an example of what I am trying to get but it is not working...
db.documents.find({'attributes':$match:{[{'def':{$gte:new Date('2011-11-30')}}]}});
Is something like this what you are looking for?
Based on the following documents:
{
"_id" : ObjectId("5d38aad64850fbd5d13f14bd"),
"name" : "testxx.pdf",
"attributes" : [
{
"abc" : 1
},
{
"def" : ISODate("2012-12-01T08:00:00Z")
},
{
"ghi" : "test"
}
]
}
{
"_id" : ObjectId("5d38b0eae4adbe945b6cbb89"),
"name" : "testyy.pdf",
"attributes" : [
{
"abc" : 2
},
{
"def" : ISODate("2013-12-01T08:00:00Z")
},
{
"ghi" : "test1"
}
]
}
{
"_id" : ObjectId("5d38b12f21e647b8d384d841"),
"name" : "testzz.pdf",
"attributes" : [
{
"abc" : 3
},
{
"def" : ISODate("2012-05-01T08:00:00Z")
},
{
"ghi" : "test"
}
]
}
Query def > 2010/11/30 - returns all 3 docs above
db.chkdates.find({'attributes.def':{$gte:new Date(2010,11,30)}}).pretty()
Adding another key/value pair and range looks like:
db.chkdates.find({'attributes.def':{$gte:new Date(2011,12,12),
$lte:new Date(2012,10,12)},
'attributes.ghi':'test'}).pretty()
Returns only 1 document:
{
"_id" : ObjectId("5d38b12f21e647b8d384d841"),
"name" : "testzz.pdf",
"attributes" : [
{
"abc" : 3
},
{
"def" : ISODate("2012-05-01T08:00:00Z")
},
{
"ghi" : "test"
}
]
}
You can use the $gte and $lte for querying within a date range. It would look something like this:
{'def': { $gte: qryDateFrom, $lte: qryDateTo }}
Depending on if you're using aggregate pipleline or a regular mongoose query, you'd just apply this accordingly.
For instance, using $match in an aggregate with a string match included, it would look like this:
$match: {
$and: [
{'id': { $ne: req.user._id }},
{'def': { $gte: qryDateFrom, $lte: qryDateTo }}
]
}

Reference multiple fields with aggregation function

Let's say I have some mongo DB query which returns following two documents. (I am using aggregation & projection which returns me this result set).
{
"name" : {
"value" : "ANDERSON"
},
"ID" : {
"value" : "2356"
},
}
{
"employeename" : {
"value" : "DAVID"
},
"ID" : {
"value" : "2356"
},
}
My DB is schema less & I am storing attributes and there values. There are multiple attributes which represents the same information. For e.g. here "name" & "employeename" represents the same thing. I want the final output in some common attribute (say "Employee Name"). This common attribute can have value either from "name" or "employeename".
I think this problem can be solved by adding one more pipe in with the aggregation. I tried $or (it returns true/false not the value)
db.getCollection('mycollection').aggregate([
{ "$project" : {
"name" : 1,
"ID" : 1, "employeename" : 1
}},
{ "$project":{
"Employee Name": {$or : ["$name", "$employeename"]}
}}
])
Final Output should be
{
" Employee Name" : {
"value" : "ANDERSON"
},
"ID" : {
"value" : "2356"
},
}
{
" Employee Name" : {
"value" : "DAVID"
},
"ID" : {
"value" : "2356"
},
}
Can somebody tell me how to write this mongo DB command?
What you want is the $ifNull operator, you can also shorten your pipeline to one $project stage.
db.getCollection('mycollection').aggregate([
{ "$project" : {
"EmployeeName" : { "$ifNull": [ "$name", "$employeename" ] },
"ID" : 1,
}}
])

MongoDB select documents where field1 equals nested.field2 in aggregate pipeline

I have joined two collections on one field using '$lookup', while actually I needed two fields to have a unique match. My next step would be to unwind the array containing different values of the second field I need for a unique match and then compare these to the value of the second field it needs to match higher up. However, the second line in the snippet below returns no results.
// Request only the page that has been viewed
{ '$unwind' : '$DSpub.PublicationPages'},
{ '$match' : {'pageId' : '$DSpub.PublicationPages.PublicationPageId' } }
Is there a more appropriate way to do this? Or can I avoid doing this altogether by unwinding the "from" collection before performing the '$lookup', and then match both fields?
This is not as easy at it looks.
$match does not operate on dynamic data (that means we are comparing static value against data set). To overcome that - we can use $project phase to add a bool static flag, that can be utilized by $match
Please see example below:
Having input collection like this:
[{
"_id" : ObjectId("56be1b51a0f4c8591f37f62b"),
"name" : "Alice",
"sub_users" : [{
"_id" : ObjectId("56be1b51a0f4c8591f37f62a")
}
]
}, {
"_id" : ObjectId("56be1b51a0f4c8591f37f62a"),
"name" : "Bob",
"sub_users" : [{
"_id" : ObjectId("56be1b51a0f4c8591f37f62a")
}
]
}
]
We want to get only fields where _id and $docs.sub_users._id" are same, where docs are $lookup output.
db.collecction.aggregate([{
$lookup : {
from : "collecction",
localField : "_id",
foreignField : "_id",
as : "docs"
}
}, {
$unwind : "$docs"
}, {
$unwind : "$docs.sub_users"
}, {
$project : {
_id : 0,
fields : "$$ROOT",
matched : {
$eq : ["$_id", "$docs.sub_users._id"]
}
}
}, {
$match : {
matched : true
}
}
])
that gives output:
{
"fields" : {
"_id" : ObjectId("56be1b51a0f4c8591f37f62a"),
"name" : "Bob",
"sub_users" : [
{
"_id" : ObjectId("56be1b51a0f4c8591f37f62a")
}
],
"docs" : {
"_id" : ObjectId("56be1b51a0f4c8591f37f62a"),
"name" : "Bob",
"sub_users" : {
"_id" : ObjectId("56be1b51a0f4c8591f37f62a")
}
}
},
"matched" : true
}

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