$lookup within the same collection is nesting docs instead of returning all docs - mongodb

Dealing with $lookup was fun until I thought of makeing a join withing the same collection.
Say I have the next collection:
{'_id': ObjectId('5a1a62026462db0032897179'),
'department': ObjectId('5a1982646462db032d58c3f9'),
'name': 'Standards and Quality Department',
'type': 'sub'}, {
'_id': ObjectId('5a1982646462db032d58c3f9'),
'department': false,
'desc': 'Operations Department',
'type': 'main'}
As clearly it says, there's backlinking within the same collection using the department key which could be false to indicate highest level department.
I'm using the next query (Python) to populate the results:
query = [{'$lookup': {'as': '__department',
'foreignField': '_id',
'from': 'departments',
'localField': 'department'}},
{'$unwind': '$__department'},
{'$group': {'__department': {'$first': '$__department'},
'_id': '$_id',
'department': {'$first': '$department'},
'name': {'$first': '$name'},
'type': {'$first': '$type'}}}]
for doc in conn.db.departments.aggregate(query): pprint(doc)
What I'm expecting to get:
{'__department': None,
'_id': ObjectId('5a1982646462db032d58c3f9'),
'department': false,
'name': 'Operations Department',
'type': 'main'},
{'__department': {'_id': ObjectId('5a1982646462db032d58c3f9'),
'department': 'false',
'name': 'Operations Department',
'type': 'main'},
'_id': ObjectId('5a1a62026462db0032897179'),
'department': ObjectId('5a1982646462db032d58c3f9'),
'name': 'Standards and Quality Department',
'type': 'sub'}
What I'm actually getting is:
{'__department': {'_id': ObjectId('5a1982646462db032d58c3f9'),
'department': 'false',
'name': 'Operations Department',
'type': 'main'},
'_id': ObjectId('5a1a62026462db0032897179'),
'department': ObjectId('5a1982646462db032d58c3f9'),
'name': 'Standards and Quality Department',
'type': 'sub'}
I'm not sure why $unwind is grouping both the docs together although before applying $unwind I do get both of them separatly.
Any suggestions?

That is because you create an empty array __department in the document that didn't find a match in the $lookup. This is how your orphan document looks like:
{
"_id" : ObjectId("5a1982646462db032d58c3f9"),
"department" : false,
"desc" : "Operations Department",
"type" : "main",
"__department" : []
}
When you are unwinding there is nothing to $unwind in this document, so it gets lost in the process. If you want to keep it you have to "normalize" your array. So you'd have to add this after your $lookup and before your $unwind:
{
$project: {
_id: 1,
department: 1,
name: 1,
type: 1,
__department: {
$cond: [{
$eq: ["$__department", []]
},
[{
_id: 0,
department: "None",
desc: "None",
type: "None"
}], '$__department'
]
}
}
}
So all together it should look like that:
[{
'$lookup': {
'as': '__department',
'foreignField': '_id',
'from': 'depart',
'localField': 'department'
}
},
{
'$project': {
_id: 1,
department: 1,
name: 1,
type: 1,
__department: {
$cond: [{
$eq: ["$__department", []]
},
[{
_id: 0,
department: "None",
desc: "None",
type: "None"
}], '$__department'
]
}
}
},
{'$unwind': "$__department"},
{'$group': {'__department': {'$first': '$__department'},
'_id': '$_id',
'department': {'$first': '$department'},
'name': {'$first': '$name'},
'type': {'$first': '$type'}}}]

Related

Group all elements with same name with their IDs Mongodb

I want to group all elements with same name and find their IDs and $push them in a list.
I have a dataset like
{
'id': 1,
'name': 'Refrigerator'
},
{
'id': 2,
'name': 'Refrigerator'
},
{
'id': 3,
'name': 'TV'
},
{
'id': 4,
'name': 'TV'
}
Expected Ouput
{
'equipment_name': 'Refrigerator',
'equipment_id': [1, 2]
},
{
'equipment_name': 'TV',
'equipment_id': [3, 4]
}
What I've tried
{'$group': {'_id': '$_id', 'equipmne_name': '$name'}}
{'$project': {'name': {'$push': {'$expr': ['$name', '$name']}}}
And a few more aggregation techniques with $cond
[
{'$group': {'_id': {'key': '$name', 'value': '$_id'}}},
{'$group': {'_id': '$_id.key', 'result': {'$push': {'$toString': '$$ROOT._id.value'}}}},
{'$project': {'_id': 0, 'equipment_name': '$_id', 'equipment_id': '$result'}}
]

MongoDB Aggregate Group Results Query by Common Fields

Looking to figure out how to perform a particular grouping of Mongo documents through a query, instead of transforming the results through code after getting the data.
Sample mock data:
[
{ 'id': 1, 'status': 'EMPTY', 'name': 'ALBERT' },
{ 'id': 2, 'status': 'EMPTY', 'name': 'ERIC' },
{ 'id': 3, 'status': 'EMPTY', 'name': 'ALBERT' },
{ 'id': 4, 'status': 'EMPTY', 'name': 'ALBERT' },
{ 'id': 5, 'status': 'EMPTY', 'name': 'JESSICA' },
{ 'id': 6, 'status': 'EMPTY', 'name': 'ERIC' },
...
]
My aggregate query:
db.results.aggregate([
{
$match: {
'status': 'EMPTY'
}
},
???
])
I would like to transform/sort the results into an object like this:
{
'ALBERT': [
{ 'id': 1, 'status': 'EMPTY', 'name': 'ALBERT' },
{ 'id': 3, 'status': 'EMPTY', 'name': 'ALBERT' },
{ 'id': 4, 'status': 'EMPTY', 'name': 'ALBERT' },
],
'ERIC': [
{ 'id': 2, 'status': 'EMPTY', 'name': 'ERIC' },
{ 'id': 6, 'status': 'EMPTY', 'name': 'ERIC' }
],
'JESSICA': [
{ 'id': 5, 'status': 'EMPTY', 'name': 'JESSICA' }
]
}
It's a bit difficult looking through documentation online since I'm not even sure what this type of transformation is called, or what aggregator I should be using in Mongo. So any point in the right direction would be much appreciated!
Thank you!!
Try this one:
db.collection.aggregate([
{ $unset: "_id" },
{
$group: {
_id: "$name",
data: { $push: "$$ROOT" }
}
},
{
$project: {
data: {
k: "$_id",
v: "$data"
}
}
},
{ $replaceRoot: { newRoot: { $arrayToObject: "$data" } } },
{
$group: {
_id: null,
data: { $push: "$$ROOT" }
}
},
{ $replaceRoot: { newRoot: { $mergeObjects: "$data" } } }
])
Mongo Playground

MongoDB finding nested sub documents with multiple conditions in same key

I have a collection where I have many items of the following format
{
'_id': ObjectId(),
'info': [{
'type': "type1",
'foo': foo
},
{
'type': "type2",
'bar': bar
},
{
'type': "type3",
'foo': foo
}]
}
I'd like to be able to find all documents that contain type1 AND type2 subdocuments, and have them projected to only include type1 and type2.
So response would look like:
[{ '_id': ObjectId(), 'info': [{'type': "type1", 'foo': foo}, {'type': "type2", 'bar': bar}]}, ...]
yourTable.find({ $or: [{ "info.type": "type1" }, { "info.type": "type2" }, { "info.type": "type3" }] })
you could try something like this.
Here is the link:
[1]: https://kb.objectrocket.com/mongo-db/use-mongoose-to-find-in-an-array-of-objects-1206

Group by several fields and custom sums with two conditions

I want to group rows with two conditions. The first one to get total (now it works), the second to get unread messages. I cannot imagine how to do it. Inserts are:
db.messages.insert({'source_user': 'test1', 'destination_user': 'test2', 'is_read': true})
db.messages.insert({'source_user': 'test1', 'destination_user': 'test2', 'is_read': false})
db.messages.insert({'source_user': 'test1', 'destination_user': 'test3', 'is_read': true})
my code:
db.messages.aggregate([
{'$match': {'source_user': user}},
{'$group': {
'_id': {
'source_user': '$source_user',
'destination_user': '$destination_user',
'is_read': '$is_read'
},
'total': {'$sum': 1}}
},
{'$project': {
'source_user': '$_id.source_user',
'destination_user': '$_id.destination_user',
#'unread': {'$sum': {'$_id.is_read': False}},
'total': '$total',
'_id': 0
}}
])
as a result I want to get:
[{
'source_user': 'test1',
'destination_user': 'test2',
'unread': 1,
'total': 2
}, {
'source_user': 'test1',
'destination_user': 'test3',
'unread': 0,
'total': 1
}
]
Should I add a new group or I can use $is_read flag in the same group?
Thank you!
You can count unread messages the same way you do it for total but you need to apply $cond to add 0 only for those that are read and 1 for other ones:
db.messages.aggregate([
{'$match': {'source_user': user}},
{'$group': {
'_id': {
'source_user': '$source_user',
'destination_user': '$destination_user'
},
'total': {'$sum': 1},
'unread': {'$sum': { '$cond': [ '$is_read', 0, 1 ] }}
}
},
{'$project': {
'source_user': '$_id.source_user',
'destination_user': '$_id.destination_user',
'total': 1,
'unread': 1,
'_id': 0
}}
])
MongoDB Playground

Mongodb aggregation query for message unread count group by group_id

Here is my problem : in my MongoDB database, I have a collection with Messages like :
{
'id': 1,
'message': 'Message 1',
'groups': [1],
'readBy': ['cust1','cust2'],
'status': '1'
}
{
'id': 2,
'message': 'Message 2',
'groups': [1],
'readBy': ['cust2'],
'status': '1'
}
{
'id': 3,
'message': 'Message 3',
'groups': [2],
'readBy': ['cust2','cust1'],
'status': '1'
}
{
'id': 4,
'message': 'Message 4',
'groups': [2],
'readBy': ['cust2'],
'status': '1'
}
{
'id': 5,
'message': 'Message 5',
'groups': [2],
'readBy': ['cust2'],
'status': '1'
}
I have another collection for customer, in that I have maintained groups ids.
'cust1' is part of group id = [1,2].
I want total group count from which messages is not read by cust1.
In above case count should be 2.
I have tried by using below query, But it returns total unread message count.
db.Messages.aggregate(
{$unwind : "$groups" },
{$match:{
"groups": {
"$in": ["1","2"]
},
"status": "1",
"readBy": {
"$ne": "cust1"
}
}},
{$group:{_id:null,count:{$sum:1}}}
).pretty()
Below Query satisfy above functionality.
db.Messages.aggregate(
{$unwind : "$groups" },
{$match:{
"groups": {
"$in": ["1","2"]
},
"status": "1",
"readBy": {
"$ne": "cust1"
}
}},
{$group:{_id:"$groups"}},
{$group:{_id:null,count:{$sum:1}}}
)