How to retrieve element present inside array in Mongo DB? - mongodb

I have structure shown below:
{
field1: "somevalue",
name:"xtz",
nested_documents: [
{
x:1,
y:2,
info:[
{name:"sachin",value:"test"},
{name:"sachin", value:"test"}
]
},
{
x:1,
y:3,
info:[
{name:"sachin1",value:"test"},
{name:"sachin2", value:"test"}
]
},
{
x:4,
y:3,
info:[
{name:"sachin",value:"test"},
{name:"sachin", value:"test"}
]
}
]
}
I know that I can retrieve element present inside 1st array using below code:
db.test.find({"nested_documents.x": 1},{_id: 0, nested_documents: {$elemMatch: {x: 1}}}
But, I want to apply same logic for name attribute.
I want to retrieve only document that has name `sachin'.
What I have tries is shown below:
db.test.find({"nested_documents.info.name": "sachin"},
{_id: 0, 'nested_documents.info': {$elemMatch: {name: "sachin"}}});
But Mongo db says it does not support '.' operator inside projection :(. Is there any other way to do this?(Using command prompt or code)
Command to insert document is shown below:
db.test.insert( {
field1: "somevalue",
name:"xtz",
nested_documents: [
{
x:1,
y:2,
info:[
{name:"sachin",value:"test"},
{name:"sachin", value:"test"}
]
},
{
x:1,
y:3,
info:[
{name:"sachin1",value:"test"},
{name:"sachin2", value:"test"}
]
},
{
x:4,
y:3,
info:[
{name:"sachin",value:"test"},
{name:"sachin", value:"test"}
]
}
]
}
)
I am expecting output as:
{ "_id" : ObjectId("5142e0f153cd2aab3a3bae5b"),
"nested_documents" : [
{ "x" : 1, "y" : 2,
"info" : [
{ "name" : "sachin", "value" : "test" },
{ "name" : "sachin", "value" : "test" }
]
},
{ "x" : 4, "y" : 3,
"info" : [ { "name" : "sachin", "value" : "test" },
{ "name" : "sachin", "value" : "test" }
]
}
]
}

You would have to use aggregate() with a double $unwind, like this:
db.test.aggregate([
// filter for documents with x=1
// note: this will use an index, if defined on "nested_documents.x"
//{ $match: { "nested_documents.x": 1 } },
// reduce data to nested_documents, as other fields are not considered
{ $project: { nested_documents: 1 } },
// flatten the outer array
{ $unwind: "$nested_documents" },
// filter for nested_documents with x=1
// note that at this point nested_documents is no longer an array
//{ $match: { "nested_documents.x": 1 } },
// flatten the inner array
{ $unwind: "$nested_documents.info" },
// filter for nested_documents.info.name = "sachin"
// note that at this point nested_documents.info is no longer an array
{ $match: { "nested_documents.info.name": "sachin" } },
// format output: re-create inner array
{ $group: { _id: { id: "$_id",
nested_documents: {
x: "$nested_documents.x",
y: "$nested_documents.y"
}
},
info: { $push: "$nested_documents.info" } } },
{ $project: { "nested_documents.x": "$_id.nested_documents.x",
"nested_documents.y": "$_id.nested_documents.y",
"nested_documents.info": "$info" } },
// format output: re-create outer array
{ $group: { _id: "$_id.id", nested_documents: { $push: "$nested_documents" } } },
])
note: I put in as //comments the logic to filter for x=1 as you had in a previous example
and the result is:
{
"result" : [
{
"_id" : ObjectId("515d873457a0887a97cc8d19"),
"nested_documents" : [
{
"x" : 4,
"y" : 3,
"info" : [
{
"name" : "sachin",
"value" : "test"
},
{
"name" : "sachin",
"value" : "test"
}
]
},
{
"x" : 1,
"y" : 2,
"info" : [
{
"name" : "sachin",
"value" : "test"
},
{
"name" : "sachin",
"value" : "test"
}
]
}
]
}
],
"ok" : 1
}
For more information on aggregate, refer to http://docs.mongodb.org/manual/applications/aggregation/

Related

MongoDB - Group by and count value, but treat per record as one

I want to group by and count follow_user.tags.tag_id per record, so no matter how many times the same tag_id show up on the same record, it only counts as 1.
My database structure looks like this:
{
"external_userid" : "EXID1",
"follow_user" : [
{
"userid" : "USERID1",
"tags" : [
{
"tag_id" : "TAG1"
}
]
},
{
"userid" : "USERID2",
"tags" : [
{
"tag_id" : "TAG1"
},
{
"tag_id" : "TAG2"
}
]
}
]
},
{
"external_userid" : "EXID2",
"follow_user" : [
{
"userid" : "USERID1",
"tags" : [
{
"tag_id" : "TAG2"
}
]
}
]
}
Here's my query:
[
{ "$unwind": "$follow_user" }, { "$unwind": "$follow_user.tags" },
{ "$group" : { "_id" : { "follow_user᎐tags᎐tag_id" : "$follow_user.tags.tag_id" }, "COUNT(_id)" : { "$sum" : 1 } } },
{ "$project" : { "total" : "$COUNT(_id)", "tagId" : "$_id.follow_user᎐tags᎐tag_id", "_id" : 0 } }
]
What I expected:
{
"total" : 1,
"tagId" : "TAG1"
},
{
"total" : 2,
"tagId" : "TAG2"
}
What I get:
{
"total" : 2,
"tagId" : "TAG1"
},
{
"total" : 2,
"tagId" : "TAG2"
}
$set - Create a new field follow_user_tags.
1.1. $setUnion - To distinct the value from the Result 1.1.1.
1.1.1. $reduce - Add the value of follow_user.tags.tag_id into array.
$unwind - Deconstruct follow_user_tags array field to multiple documents.
$group - Group by follow_user_tags and perform total count via $sum.
$project - Decorate output document.
db.collection.aggregate([
{
$set: {
follow_user_tags: {
$setUnion: {
"$reduce": {
"input": "$follow_user.tags",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this.tag_id"
]
}
}
}
}
}
},
{
$unwind: "$follow_user_tags"
},
{
$group: {
_id: "$follow_user_tags",
total: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
tagId: "$_id",
total: 1
}
}
])
Sample Mongo Playground

Fetch values x,y from field in 3x nested arrays

please, help , I have following type of documents:
db.g.find({_id:ObjectId("605929e0122984ad3c4c537a") }).pretty()
{
"_id" : ObjectId("605929e0122984ad3c4c537a"),
"a" : [
{
"p" : [
{
"pid" : 1,
"c" : {
"t" : [
{
"x" : 1,
"y" : 2
},
{
"z" : 1,
"x" : 5
},
{
"h" : 1
}
]
}
},
{
"d" : 1
}
]
},
{
"p" : [
{
"pid" : 2,
"c" : {
"t" : [
{
"x" : 4
}
]
}
},
{
"pid" : 3,
"c" : {
"t" : [
{
"y" : 4
}
]
}
}
]
}
]
}
And I need to fetch only the values from the fields:"a.p.pid" , and all "x" or "y" if they exist , so the final result to look like:
{pid:1,x:1,y:2}
{pid:1,x:5}
{pid:2,x:4}
{pid:3,y:4}
Collection is pretty big and doing 3x$unwind take alot of time ...
Attempting with $redact, $map / $filter but no success ... , any help will be highly appreciated ...
Demo - https://mongoplayground.net/p/w0wWJRdBP-J
Use $unwind twice to seperate each pid element
Use $filter to get an array of objects where x or y is present.
db.collection.aggregate([
{ "$unwind": "$a" },
{ "$unwind": "$a.p" },
{ "$project": {
_id: 0, pid: "$a.p.pid",
t: {
$filter: { input: "$a.p.c.t", as: "item",
cond: { $or: [
{ $ne: [ { $type: "$$item.x" }, "missing" ] },
{ $ne: [ { $type: "$$item.y" }, "missing" ]}
]}
}
}}
},
{ "$unwind": "$t" },
{ "$project": { pid: 1, x: "$t.x", y: "$t.y" } }
])

Merge two documents and reshape with arrays

Input data
[
{
"_id" : ObjectId("xxx"),
"ParentNumber" : "12345",
"ChildNumber" : "A123"
},
{
"_id" : ObjectId("yyy"),
"ParentNumber" : "12345",
"ChildNumber" : "B123"
},
{
"_id" : ObjectId("zzz"),
"ParentNumber" : "6789",
"ChildNumber" : "C123"
}
]
Output Needed
[
{
"_id" : ObjectId("aaa"),
"ParentNumber" : "12345",
"Children":[
{ "ChildNumber" : "A123"},
{ "ChildNumber" : "B123"}
]
},
{
"_id" : ObjectId("bbb"),
"ParentNumber" : "6789",
"Children":[
{ "ChildNumber" : "C123"}
]
}
]
I tried the following but can't figure out how to group the parent numbers with children.
db.test.aggregate
(
[
{
$project:
{
"_id" : ObjectId(),
"ParentNumber" : "$ParentNumber",
"Children" : [
{
"ChildNumber" : "$ChildNumber"
}
]
}
}
]
)
I referred to the merge function in mongodb but I can't figure out how to compare one document to another based on a condition and return a result with array.
Thank you
This gives the desired result. Try this:
[{$group: {
_id: "$ParentNumber",
children: {
$push:{'ChildNumber':'$ChildNumber'}
},
}}, {$project: {
"ParentNumber":"$_id",
children:1
}}]
Check this PS: it will not show the objectId of the parent
db.collection.aggregate([
{
$group: {
_id: "$ParentNumber",
Children: {
$push: "$$ROOT"
},
},
},
{
"$project": {
_id: 0,
ParentNumber: "$_id",
Children: {
"ChildNumber": "$Children.ChildNumber"
}
}
}
])

How to get the last element in a sub document in mongodb

This is a sample mongo document i have.
{
"_id" : ObjectId("someid"),
"doc": {
subDoc: [{
content: [{name: 'aaa'}, {name: 'bbb'}, {name: 'ccc'}
},
{
content: [{name: 'aaa'}, {name: 'bbb'}, {name: 'ccc'}
},
{
content: [{name: 'aaa'}, {name: 'bbb'}, {name: 'ccc'}
}]
}
}
I need to get the last element of the content which is nested array as mentioned in the above sample. I tried using aggregate with $project, still i was not able to get the result.
Expected result:
{
"_id" : ObjectId("someid"),
"doc": {
subDoc: [{
content: {name: 'ccc'}
},
{
content: {name: 'ccc'}
},
{
content: {name: 'ccc'}
}]
}
}
Please try this :
db.yourCollectionName.aggregate([{
"$project": {
"doc.subDoc": {
$map:
{
input: "$doc.subDoc",
as: "each",
in: { content: { "$arrayElemAt": ["$$each.content", -1] } }
}
}
}
}])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e0e6d1f400289966ece90d8"),
"doc" : {
"subDoc" : [
{
"content" : [
{
"name" : "aaa"
},
{
"name" : "bbb"
},
{
"name" : "ccc"
}
]
},
{
"content" : [
{
"name" : "aaa"
},
{
"name" : "bbb"
},
{
"name" : "ccc"
}
]
},
{
"content" : [
{
"name" : "aaa"
},
{
"name" : "bbb"
},
{
"name" : "ccc"
}
]
}
]
}
}
Result :
/* 1 */
{
"_id" : ObjectId("5e0e6d1f400289966ece90d8"),
"doc" : {
"subDoc" : [
{
"content" : {
"name" : "ccc"
}
},
{
"content" : {
"name" : "ccc"
}
},
{
"content" : {
"name" : "ccc"
}
}
]
}
}
Use $slice operator to get the last item from an array in the collection.
{
"$project" : {
"_id" : 1,
"subDoc": { "$slice": [ "$doc.subDoc", -1 ] }
}
}

How to $setDifference in array & Object using Mongo DB

UserDetails
{
"_id" : "5c23536f807caa1bec00e79b",
"UID" : "1",
"name" : "A",
},
{
"_id" : "5c23536f807caa1bec00e78b",
"UID" : "2",
"name" : "B",
},
{
"_id" : "5c23536f807caa1bec00e90",
"UID" : "3",
"name" : "C"
}
UserProducts
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "100",
"UID" : "1",
"status" : "A"
},
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "200",
"UID" : "2",
"status" : "A"
},
{
"_id" : "5c23536f807caa1bec00e52c",
"UPID" : "300",
"UID" : "3",
"status" : "A"
}
Groups
{
"_id" : "5bb20d7556db6915846da55f",
"members" : {
"regularStudent" : [
"200" // UPID
],
}
},
{
"_id" : "5bb20d7556db69158468878",
"members" : {
"regularStudent" : {
"0" : "100" // UPID
}
}
}
Step 1
I have to take UID from UserDetails check with UserProducts then take UPID from UserProducts
Step 2
we have to check this UPID mapped to Groups collection or not ?.
members.regularStudent we are mapped UPID
Step 3
Suppose UPID not mapped means i want to print the UPID from from UserProducts
I have tried but couldn't complete this, kindly help me out on this.
Expected Output:
["300"]
Note: Expected Output is ["300"] , because UserProducts having UPID 100 & 200 but Groups collection mapped only 100& 200.
My Code
var queryResult = db.UserDetails.aggregate(
{
$lookup: {
from: "UserProducts",
localField: "UID",
foreignField: "UID",
as: "userProduct"
}
},
{ $unwind: "$userProduct" },
{ "$match": { "userProduct.status": "A" } },
{
"$project": { "_id" : 0, "userProduct.UPID" : 1 }
},
{
$group: {
_id: null,
userProductUPIDs: { $addToSet: "$userProduct.UPID" }
}
});
let userProductUPIDs = queryResult.toArray()[0].userProductUPIDs;
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
},
{
$project: {
members: {
$setDifference: [ userProductUPIDs , "$UPIDs" ]
},
_id : 0
}
}
])
My Output
{
"members" : [
"300",
"100"
]
}
You need to fix that second aggregation and get all UPIDs as an array. To achieve that you can use $cond and based on $type either return an array or use $objectToArray to run the conversion, try:
db.Groups.aggregate([
{
$project: {
students: {
$cond: [
{ $eq: [ { $type: "$members.regularStudent" }, "array" ] },
"$members.regularStudent",
{ $map: { input: { "$objectToArray": "$members.regularStudent" }, as: "x", in: "$$x.v" } }
]
}
}
},
{
$unwind: "$students"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$students" }
}
},
{
$project: {
members: {
$setDifference: [ userProductUPIDs , "$UPIDs" ]
},
_id : 0
}
}
])