Join Same Collection in Mongo - mongodb

Below is the sample collection document record that i want to join the same collection with different child array elements.
Sample Collection Record :
{
"_id": "052dc2aa-043b-4cd7-a3f2-f3fe6540ae52",
"Details": [
{
"Id": "104b0bb1-d4a5-469b-b1fd-b4822e96dcb0",
"Number": "12345",
"Percentages": [
{
"Code": "55555",
"Percentage": "45"
},
{
"Code": "55333",
"Percentage": "50"
}
]
},
{
"Id": "104b0bb1-d4a5-469b-b1fd-b4822e96dcb0",
"Number": "55555",
"Percentages": [
{
"Code": "55555",
"Percentage": "45"
}
]
}
],
"Payments": [
{
"Id": "61ee1a6f-3334-4f33-ab6c-51c646b75c41",
"Number": "12345"
}
]
}
The mongo Pipeline query which i would like to fetch the Percentages Array with matched conditions whose Details.Number and Payment.Number should be same
Result:
"Percentages": [
{
"Code": "55555",
"Percentage": "45"
},
{
"Code": "55333",
"Percentage": "50"
}]
How to bring the result by joining the same collections child elements using aggregate ?

Following query does what you want:
db.collection.aggregate([
{$unwind : "$Details"},
{$unwind : "$Details.Percentages"},
{$unwind : "$Payments"}, // $unwind all your arrays
{
$addFields : { //This include new `isMatch` field, which is gonna be true, only if Details.Number = Payment.Number
"isMatch" : {$cond: { if: { $eq: [ "$Details.Number", "$Payments.Number" ] }, then: true, else: false }}
}
},
{
$match : { // This ignores all others, for which Details.Number != Payment.Number
"isMatch" : true
}
},
{
$group : { // This will return only the Percentage objects
_id : null,
"Percentages" : {$push : "$Details.Percentages"}
}
},
{
$project : { // To ignore "_id" field
_id : 0,
"Percentages" : 1
}
}
])
Result:
{
"Percentages" : [
{
"Code" : "55555",
"Percentage" : "45"
},
{
"Code" : "55333",
"Percentage" : "50"
}
]
}
Hope this helps!

Related

MongoDB Aggregation function

I have the following JSON Documents in Mongo collection named "Movies"
{
"_id": "5ed0c9700b9e8b0e2c542054",
"movie_name": "Jake 123",
"score": 20,
"director": "Jake"
},
{
"_id": "5ed0a9840b9e8b0e2c542053",
"movie_name": "Avatar",
"director": "James Cameroon",
"score": 50,
"boxoffice": [
{
"territory": "US",
"gross": 2000
},
{
"territory": "UK",
"gross": 1000
}
]
},
{
"_id": "5ed0a9630b9e8b0e2c542052",
"movie_name": "Titanic",
"score": 100,
"director": "James Cameroon",
"boxoffice": [
{
"territory": "US",
"gross": 1000
},
{
"territory": "UK",
"gross": 500
}
],
"actors": [
"Kate Winselet",
"Leonardo De Caprio",
"Rajinikanth",
"Kamalhaasan"
]
}
I run the below query which finds the maximum collection of a country of various movies. My intention is to find the maximum collection and the corresponding territory.
db.movies.aggregate([
{$match: {"boxoffice" : { $exists: true, $ne : []}}},
{$project: {
"title":"$movie_name", "max_boxoffice": {$max : "$boxoffice.gross"},
"territory" : "$boxoffice.territory" } }
])
I get the result as follows. How do I get the correct territory that corresponds to the collection?
{
"_id" : ObjectId("5ed0a9630b9e8b0e2c542052"),
"title" : "Titanic",
"max_boxoffice" : 1000,
"territory" : [
"US",
"UK"
]
},
{
"_id" : ObjectId("5ed0a9840b9e8b0e2c542053"),
"title" : "Avatar",
"max_boxoffice" : 2000,
"territory" : [
"US",
"UK"
]
}
Expected output:
Avatar and Titanic has collected more money in US. I wanted territories to display the values of them
{
"_id" : ObjectId("5ed0a9630b9e8b0e2c542052"),
"title" : "Titanic",
"max_boxoffice" : 1000,
"territory" : "US"
},
{
"_id" : ObjectId("5ed0a9840b9e8b0e2c542053"),
"title" : "Avatar",
"max_boxoffice" : 2000,
"territory" : "US"
}
For this specific requirement, you can use $set (aggregation). $set appends new fields to existing documents. and we can include one or more $set stages in an aggregation operation to achieve this like:
db.movies.aggregate([
{
$match: { "boxoffice": { $exists: true, $ne: [] } }
},
{
$set: {
boxoffice: {
$filter: {
input: "$boxoffice",
cond: { $eq: ["$$this.gross", { $max: "$boxoffice.gross" }]}
}
}
}
},
{
$set: {
boxoffice: { $arrayElemAt: ["$boxoffice", 0] }
}
},
{
$project: {
"title": "$movie_name",
"max_boxoffice": "$boxoffice.gross",
"territory": "$boxoffice.territory"
}
}
])
Mongo Playground

Project embedded document key value, based on condition in mongoDB aggregation

I have a mongo collection called tickets and we are storing ticket details in similar structure documents like this:
[
{
"status": "PAUSED",
"lifecycle_dates": {
"OPEN": "d1",
"CLOSED": "d2",
"PAUSED": "d3"
}
},
{
"status": "OPEN",
"lifecycle_dates": {
"OPEN": "d1",
"PAUSED": "d3"
}
},
{
"status": "CLOSED",
"lifecycle_dates": {
"OPEN": "d1",
"CLOSED": "d2"
}
}
]
I need to fetch the data which says current status of ticket and status date on.
and I want to project data like :
[
{
"status": "PAUSED",
"lifecycle_date": "d3"
},
{
"status": "OPEN",
"lifecycle_date": "d1"
},
{
"status": "CLOSED",
"lifecycle_date": "d2"
}
]
How can I project single lifecycle date based on current status in mongo aggregation pipeline?
something like this:
{
$project : {
"status" : 1,
"lifecycle_date" : $lifecycle_dates[$status]
}
}
couldn't find any reference or similar problem in mongo reference document here
current mongo version : 3.2
Updated Answer :
Since you need to fetch the date as per the status, you can use this aggregate query :
db.test.aggregate([
{
$project : {
_id : 0,
status : 1,
lifecycle_date : { $cond: [ {$eq : ["$status","OPEN"]}, "$lifecycle_dates.OPEN", { $cond: [ {$eq : ["$status","CLOSED"]}, "$lifecycle_dates.CLOSED", { $cond: [ {$eq : ["$status","PAUSED"]}, "$lifecycle_dates.PAUSED", "-1" ]} ]} ]}
}
}])
This is compatible with Mongo 3.2 as well.
Output :
{ "status" : "PAUSED", "lifecycle_date" : "d3" }
{ "status" : "OPEN", "lifecycle_date" : "d1" }
{ "status" : "CLOSED", "lifecycle_date" : "d2" }
=========================================================================
This answer was for the previous question -
Use this aggregate :
db.test.aggregate([
{
$project : {
_id : 0,
status : 1,
lifecycle_date : "$lifecycle_dates.PAUSED"
}
}
])
Output :
{ "status" : "PAUSED", "lifecycle_date" : "d3" }
You can try below aggregation
db.collection.aggregate([
{ "$project": {
"status": 1,
"lifecycle_date": {
"$arrayElemAt": [
{ "$filter": {
"input": { "$objectToArray": "$lifecycle_dates" },
"as": "life",
"cond": { "$eq": ["$$life.k", "$status"] }
}},
0
]
}
}},
{ "$project": {
"status": 1,
"lifecycle_date": "$lifecycle_date.v"
}}
])
db.tickets.aggregate(
// Pipeline
[
// Stage 1
{
$project: {
"status": 1,
_id: 0,
"lifecycle_dates": {
$switch: {
branches: [{
case: {
$eq: ["$status", "PAUSED"]
},
then: "$lifecycle_dates.PAUSED"
},
{
case: {
$eq: ["$status", "OPEN"]
},
then: "$lifecycle_dates.OPEN"
},
{
case: {
$eq: ["$status", "CLOSED"]
},
then: "$lifecycle_dates.OPEN"
}
],
}
}
}
},
])

$lookup and get count under certain conditions MongoDB

Have 2 collections for handling chat
For chat rooms
For chat Messages
Sample data for chatRooms is as follows
{
"data": [
{
"_id": "5a606ab0116e2c164b25ef33",
"topic": "akhil Ben chat",
"topicDesc": "question 1",
"roomName": "benakhil777akhil",
"createdOn": "2018-01-18T09:36:48.231Z",
"participants": [
"ben",
"akhil777"
],
"__v": 0
},
{
"_id": "5a4dbdaab46b426863e7ead3",
"topic": "test",
"topicDesc": "test123",
"roomName": "benakhil777test",
"createdOn": "2018-01-04T05:37:46.088Z",
"participants": [
"ben",
"akhil777"
],
"__v": 0
}
]}
Sample Data for chatMessages is as follows
{"data": [
{
"_id": "5a62281ea0652120a6668bae",
"topic": "akhil Ben chat",
"roomName": "benakhil777akhil",
"message": "test 1",
"__v": 0,
"readStatus": [
{
"recipient": "ben",
"_id": "5a62281ea0652120a6668bb0",
"status": true
},
{
"recipient": "akhil777",
"_id": "5a62281ea0652120a6668baf",
"status": true
}
],
"createdOn": "2018-01-19T17:17:18.456Z"
},
{
"_id": "5a622866a0652120a6668bb1",
"topic": "akhil Ben chat",
"roomName": "benakhil777akhil",
"message": "Test 2",
"__v": 0,
"readStatus": [
{
"recipient": "ben",
"_id": "5a622866a0652120a6668bb3",
"status": false
},
{
"recipient": "akhil777",
"_id": "5a622866a0652120a6668bb2",
"status": true
}
],
"createdOn": "2018-01-19T17:18:30.396Z"
},
{
"_id": "5a62287ca0652120a6668bb4",
"topic": "akhil Ben chat",
"roomName": "benakhil777akhil",
"message": "test 3",
"__v": 0,
"readStatus": [
{
"recipient": "ben",
"_id": "5a62287ca0652120a6668bb6",
"status": false
},
{
"recipient": "akhil777",
"_id": "5a62287ca0652120a6668bb5",
"status": true
}
],
"createdOn": "2018-01-19T17:18:52.018Z"
}
]}
In the above JSON readStatus store the status, which the user read the message or not. so that i can count the unread messages by a user for each chat room.
The status inside the readStatus holds the read status of message, true for message is read.
There are two rooms benakhil777akhil and benakhil777test.
What i want to get is the number of unread messages for each room by a user say ben
Also there is userDetails collection
say,
[{
"_id": "59e6d6ba02e11e1814481022",
"username": "ben",
"name": "Ben S",
"email": "qwerty#123.com",
},{
"_id": "59e6d6ba02e11e1814481022",
"username": "akhil777",
"name": "Akhil Clement",
"email": "qwerty#123.com",
}]
this will be the user details collection
and output JSON i need is like.
{
"data": [
{
"_id": "5a606ab0116e2c164b25ef33",
"topic": "akhil Ben chat",
"topicDesc": "question 1",
"roomName": "benakhil777akhil",
"createdOn": "2018-01-18T09:36:48.231Z",
"participants": [
"ben",
"akhil777"
],
"participantDetails":[{
"_id": "59e6d6ba02e11e1814481022",
"username": "ben",
"name": "Ben S",
"email": "qwerty#123.com",
},{
"_id": "59e6d6ba02e11e1814481022",
"username": "akhil777",
"name": "Akhil Clement",
"email": "qwerty#123.com",
}],
"unreadCount": 2,
"__v": 0
},
{
"_id": "5a4dbdaab46b426863e7ead3",
"topic": "test",
"topicDesc": "test123",
"roomName": "benakhil777test",
"createdOn": "2018-01-04T05:37:46.088Z",
"participants": [
"ben",
"akhil777"
],
"participantDetails":[{
"_id": "59e6d6ba02e11e1814481022",
"username": "ben",
"name": "Ben S",
"email": "qwerty#123.com",
},{
"_id": "59e6d6ba02e11e1814481022",
"username": "akhil777",
"name": "Akhil Clement",
"email": "qwerty#123.com",
}],
"unreadCount": 0,
"__v": 0
}
]}
Please try this aggregation pipeline
db.rooms.aggregate(
[
{$match : {participants : 'ben'}},
{$lookup : {
from : "chats",
localField : "roomName",
foreignField:"roomName",
as :"out"
}
},
{$unwind : {
path: "$out",
preserveNullAndEmptyArrays: true
}
},
{$unwind : {
path: "$out.readStatus",
preserveNullAndEmptyArrays: true
}
},
{$addFields : {
isMatch : { $and : [
{ $eq : ["$out.readStatus.recipient" , "ben" ] } , { $eq : [ "$out.readStatus.status" , false ] } ]
}
}
},
{$group : {
_id : {
_id : "$_id" ,
topic : "$topic",
topicDesc : "$topicDesc",
createdOn : "$createdOn",
participants : "$participants",
roomName : "$roomName"
},
unreadCount : { $sum : { $cond : [ "$isMatch" , 1, 0 ] } }
}
},
{$sort : {unreadCount : -1}}
]
).pretty()
result
{
"_id" : {
"_id" : "5a606ab0116e2c164b25ef33",
"topic" : "akhil Ben chat",
"topicDesc" : "question 1",
"createdOn" : "2018-01-18T09:36:48.231Z",
"participants" : [
"ben",
"akhil777"
],
"roomName" : "benakhil777akhil"
},
"unreadCount" : 2
}
{
"_id" : {
"_id" : "5a4dbdaab46b426863e7ead3",
"topic" : "test",
"topicDesc" : "test123",
"createdOn" : "2018-01-04T05:37:46.088Z",
"participants" : [
"ben",
"akhil777"
],
"roomName" : "benakhil777test"
},
"unreadCount" : 0
}
EDIT since addFields is not available in 3.2.17
{$group : {
_id : {
_id : "$_id" ,
topic : "$topic",
topicDesc : "$topicDesc",
createdOn : "$createdOn",
participants : "$participants",
roomName : "$roomName"
},
unreadCount : { $sum : { $cond : [ { $and : [
{ $eq : ["$out.readStatus.recipient" , "ben" ] } , { $eq : [ "$out.readStatus.status" , false ] } ]
} , 1, 0 ] } }
}
}
EDIT-2 added $project
{$project :
{
"_id" : "$_id._id",
"topic" : "$_id.topic",
"topicDesc" : "$_id.topicDesc",
"createdOn" : "$_id.createdOn",
"participants" : "$_id.participants",
"roomName" : "$_id.roomName",
"unreadCount" : "$unreadCount"
}
}
You can simplify your code to use below aggregation.
$cond with input criteria to check for read status flag, output 1 when false 0 when true.
inner $sum to count unread values in each chat message with outer $sum to sum the unread values across all matching chat messages.
db.chatRooms.aggregate(
[{
"$match":{"participants":"ben"}},
{"$lookup":{
"from":"chatMessages",
"localField":"roomName",
"foreignField":"roomName",
"as":"chatMessages"
}},
{"$project":{
"topic":1,
"topicDesc":1,
"roomName":1,
"createdOn":1,
"participants":1,
"unreadCount":{
"$sum":{
"$map":{
"input":"$chatMessages",
"as":"chatMessage",
"in":{
"$sum":{
"$map":{
"input":"$$chatMessage.readStatus",
"as":"mChatMessage",
"in":{"$cond":[{"$eq":["$$mChatMessage.status",false]},1,0]}
}
}
}
}
}
}
}}
])
result JSON with user details.
db.chatRooms.aggregate(
[
{$match : {participants : 'ben'}},
{ $unwind : {
path: "$participants",
preserveNullAndEmptyArrays: true
}
},
{ $lookup: {
from:"users",
localField:"participants",
foreignField:"username",
as:"userData"
}
},
{ $lookup: {
from:"chatmessages",
localField:"roomName",
foreignField:"roomName",
as:"out"
}
},
{ $unwind : {
path: "$out",
preserveNullAndEmptyArrays: true
}
},
{ $unwind : {
path: "$out.readStatus",
preserveNullAndEmptyArrays: true
}
},
{ $group : {
_id : {
_id : "$_id" ,
topic : "$topic",
topicDesc : "$topicDesc",
createdOn : "$createdOn",
roomName : "$roomName"
},
participants : {$addToSet : "$participants" } ,
participantDetails : {$addToSet : {$arrayElemAt : ["$userData", 0]}},
unreadCount : {
$sum : {
$cond : [ {
$and : [
{ $eq : ["$out.readStatus.recipient" , "ben" ] } ,
{ $eq : [ "$out.readStatus.status" , false ] }
]
} , 1, 0
]
}
}
}
}
,
{ $project :
{
_id : "$_id._id",
topic : "$_id.topic",
topicDesc : "$_id.topicDesc",
createdOn : "$_id.createdOn",
participants : "$_id.participants",
roomName : "$_id.roomName",
unreadCount : "$unreadCount",
participants : 1 ,
participantDetails : 1
}
}
])

Upsert KV pair in subdocument for specific rules

How to update a document and insert key-value in subdocument for specific rules?
MongoDB version: 3.4
Use this CLI to insert simulation data
db.country.insertMany([{"_id":"us","groups":[{"group":"1"},{"group":"2"} ]},{"_id":"eu","groups":[{"group":"1"},{"group":"2"}]}, {"_id":"jp","groups":[{"group":"2"}]}])
original data
db.country.find()
{
"_id": "us", "groups": [ { "group" : "1" }, { "group": "2" } ]
}
{
"_id": "eu", "groups": [ { "group" : "1" }, { "group" : "2" } ]
}
{
"_id": "jp", "groups": [ { "group" : "2" } ]
}
How to get this result? ( just add status: happy to group 1 )
{
"_id": "us", "groups": [ { "group" : "1", "status": "happy" }, { "group": "2" } ]
}
{
"_id": "eu", "groups": [ { "group" : "1", "status": "happy" }, { "group" : "2" } ]
}
{
"_id": "jp", "groups": [ { "group" : "2" } ]
}
I know how to select all groups that match group=1
db.country.aggregate([
{'$unwind': '$groups'},
{'$match': {'groups.group': '1'}} ,
{'$project': {'group': '$groups.group', _id:0 }}
])
{ "group" : "1" }
{ "group" : "1" }
and also know how to use update + $set like this
// { "_id": 1, "people": {"name": "tony" } }
db.test.update({_id: 1}, { $set: {'people.country': 'taiwan'}})
// { "_id": 1, "people": {"name": "tony" , "country": "taiwan" } }
but how to merge update + $set and aggregate function? Please help me.
pymongo is OK for me.
How to get this result? ( just add status: happy to group 1 )
Use $ to refer the position of the matched sub-document in array.
db.coll.update_many({'groups.group':'1'}, {'$set': {'groups.$.status': 'happy'}})
see more here

RESTHeart filtering and sorting by sub documents property

I m working with mongodb and restheart.
In my nosql db i have a unique document with this structure:
{
"_id": "docID",
"users": [
{
"userID": "12",
"elements": [
{
"elementID": "1492446877599",
"events": [
{
"id": 1,
"date": 356
},
{
"id": 2,
"date": 123
}
]
}
]
},
{
"userID": "11",
"elements": [
{
"elementID": "14924",
"events": [
{
"id": 1,
"date": 123
},
{
"id": 2,
"date": 356
}
]
},
{
"elementID": "14925",
"events": [
{
"id": 1,
"date": 12
},
{
"id": 2,
"date": 36
}
]
}
]
}
i need to filter the user with userID = 11 and i need to order his events by ascending date.
i was trying with:
http://myhost:port/myCollection?keys={"users":{"$elemMatch":{"userID":"11"}}}&sort_by={"users.elements.events.date":-1}
but it doesn t work.
db.v.aggregate([
{ $unwind : '$users'},
{ $match : { 'users.userID' : '11' }} ,
{ $unwind : '$users.elements'},
{ $unwind : '$users.elements.events'},
{ $sort : {'users.elements.events.date': 1}},
{ $group : {
_id : '$_id',
elementID : { $first : '$users.elements.elementID' },
userID : { $first : '$users.userID' },
events : { $push : '$users.elements.events'}
}
},
{ $project : {
_id : 1,
userID : 1,
'elements.elementID' : '$elementID',
'elements.events' : '$events'
}
}
]);
This will give you following :
{
"_id" : ObjectId("5911ba55f0d9c285c561ea33"),
"userID" : "11",
"elements" : {
"elementID" : "14924",
"events" : [
{
"id" : 1,
"date" : 123
},
{
"id" : 2,
"date" : 356
}
]
}
}