Related
Hello I have the following data structure :
[
{
"name": "a name",
"project": [
{
companyName: "a name",
contactPerson: [
{
work_email: "test#test.com"
}
]
},
{
companyName: "a name1",
contactPerson: [
{
work_email: "test1#test.com"
}
]
},
{
companyName: "a name2",
contactPerson: [
{
work_email: "test2#test.com"
}
]
},
{
companyName: "a name3",
contactPerson: [
{
work_email: "test#test.com"
}
]
},
]
}
]
With this query i want to find all projects that have the email test#test.com :
db.collection.find({
"project.contactPerson.work_email": "test#test.com"
},
{
"project.$": 1
})
It only returns the first result it finds and then it just stops. but in my data i have two projects with that email and i want to find both. here's a playground you can use to further help me if you can. Thanks in advance and much appreciated : https://mongoplayground.net/p/4Mpp7kHi98u
The positional $ operator limits the contents of an to return either:
The first element that matches the query condition on the array.
The first element if no query condition is specified for the array
(Starting in MongoDB 4.4). Ref
You can do something like following,
[
{
"$unwind": "$project"
},
{
$addFields: {
"project.contactPerson": {
$filter: {
input: "$project.contactPerson",
cond: {
$eq: [
"$$this.work_email",
"test#test.com"
]
}
}
}
}
},
{
$match: {
$expr: {
$ne: [
"$project.contactPerson",
[]
]
}
}
},
{
$group: {
_id: "$_id",
name: {
$first: "$name"
},
project: {
"$addToSet": "$project"
}
}
}
]
Working Mongo playground
db.collection.aggregate([
{
$unwind: "$project"
},
{
$match: {
"project.contactPerson.work_email": "test#test.com"
}
},
{
"$group": {
"_id": "$_id",
"name": {
"$first": "$name"
},
"project": {
"$push": {
"companyName": "$project.companyName",
"contactPersion": "$project.contactPerson"
}
}
}
}
])
//step1(problem statement related find):, find shows all projects even one of the array element of contact is matched, use an aggregate function to display specific email ids
> db.test3.find({ "project.contact.email": "abc2#email.com" }).pretty();
{
"_id" : ObjectId("5f43fdc153e34ac6967fe8ce"),
"name" : "Pega Contractors",
"project" : [
{
"pname" : "pname1",
"contact" : [
{
"email" : "xyz1#email.com"
}
]
},
{
"pname" : "pname2",
"contact" : [
{
"email" : "abc2#email.com"
}
]
},
{
"pname" : "pname3",
"contact" : [
{
"email" : "xyz1#email.com"
}
]
}
]
}
--
//aggregate option:
//Step1: data preparation
> db.test3.find().pretty();
{
"_id" : ObjectId("5f43fdc153e34ac6967fe8ce"),
"name" : "Pega Contractors",
"project" : [
{
"pname" : "pname1",
"contact" : [
{
"email" : "xyz1#email.com"
}
]
},
{
"pname" : "pname2",
"contact" : [
{
"email" : "abc2#email.com"
}
]
},
{
"pname" : "pname3",
"contact" : [
{
"email" : "xyz1#email.com"
}
]
}
]
}
>
//step2: aggregate and unwind project for the next step pipeline input
> db.test3.aggregate([ {$unwind: "$project"}]);
{ "_id" : ObjectId("5f43fdc153e34ac6967fe8ce"), "name" : "Pega Contractors", "project" : { "pname" : "pname1", "contact" : [ { "email" : "xyz1#email.com" } ] } }
{ "_id" : ObjectId("5f43fdc153e34ac6967fe8ce"), "name" : "Pega Contractors", "project" : { "pname" : "pname2", "contact" : [ { "email" : "abc2#email.com" } ] } }
{ "_id" : ObjectId("5f43fdc153e34ac6967fe8ce"), "name" : "Pega Contractors", "project" : { "pname" : "pname3", "contact" : [ { "email" : "xyz1#email.com" } ] } }
//step3: Desired outcome, i.e display data specific to email
> db.test3.aggregate([
... {$unwind: "$project"},
... {$match: {"project.contact.email":"xyz1#email.com"}}
... ]);
{ "_id" : ObjectId("5f43fdc153e34ac6967fe8ce"), "name" : "Pega Contractors", "project" : { "pname" : "pname1", "contact" : [ { "email" : "xyz1#email.com" } ] } }
{ "_id" : ObjectId("5f43fdc153e34ac6967fe8ce"), "name" : "Pega Contractors", "project" : { "pname" : "pname3", "contact" : [ { "email" : "xyz1#email.com" } ] } }
> db.test3.aggregate([ {$unwind: "$project"}, {$match: {"project.contact.email":"acb2#email.com"}} ]);
> db.test3.aggregate([ {$unwind: "$project"}, {$match: {"project.contact.email":"abc2#email.com"}} ]);
{ "_id" : ObjectId("5f43fdc153e34ac6967fe8ce"), "name" : "Pega Contractors", "project" : { "pname" : "pname2", "contact" : [ { "email" : "abc2#email.com" } ] } }
>
I'm new to MongoDB.
In the find query I'm using the following structure:
db.report.find({'accountList.transactionList.description': /.*aear.*/i})
However, accountList contains multiple values, and so does transaction list, the exact query would be:
db.report.find({'accountList[0].transactionList[4].description': /.*aear.*/i})
The problem is that accountList has multiple accounts, and only one of them has the value 'aear' in the description. When I'm executing the query it returns me both accounts, and I'd like to keep only the account where aear is in its description. Also, this MUST be iterable over many files, since it file has different transactionLists, therefore in some documents aear will not appear at all, and in others it might appear multiple types, always in different positions. I believe something must be done in projection, but setting it like this doesn't work:
.projection({"accountList.id":1,"accountList.transactionList.description":1})
Here's the output:
"accountList" : [
{
"id" : "1",
"type" : "xD",
"currency" : "EUR",
"transactionList" : [
{
"onDate" : ISODate("2019-08-25T21:00:00.000-03:00"),
"description" : "aear"
},
{
"onDate" : ISODate("2019-08-25T21:00:00.000-03:00"),
"description" : "bb"
},
{
"onDate" : ISODate("2019-08-25T21:00:00.000-03:00"),
"description" : "cc"
}
]
},
{
"id" : "2",
"type" : "xD",
"currency" : "USD",
"transactionList" : [
{
"onDate" : ISODate("2019-08-15T21:00:00.000-03:00"),
"description" : "aa",
},
{
"onDate" : ISODate("2019-08-14T21:00:00.000-03:00"),
"description" : "ee"
}
]
}
]
And I'd like something like this, where I''m only getting the path to where the condition is met:
"accountList" : [
{
"id" : "1",
"type" : "xD",
"currency" : "EUR",
"transactionList" : [
{
"onDate" : ISODate("2019-08-25T21:00:00.000-03:00"),
"description" : "aear"
},
To accomplish that you need to use aggregate. I believe this code will work in your case:
db.report.aggregate([
{ "$match": { "accountList.transactionList.description": { $regex: "aear", $options: "i"} } },
{ "$unwind": "$accountList" },
{ "$unwind": "$accountList.transactionList" },
{ "$match": { "accountList.transactionList.description": { $regex: "aear", $options: "i"} } },
{ "$group": {
"_id": {
"_id": "$_id",
"accountListId": "$accountList.id",
"accountListType": "$accountList.type",
"accountListCurrency": "$accountList.currency",
},
"transactionList": { "$push": "$accountList.transactionList" }
}},
{ "$group": {
"_id": "$_id._id",
"accountList": {
"$push": {
"id": "$_id.accountListId",
"type": "$_id.accountListType",
"currency": "$_id.accountListCurrency",
"transactionList": "$transactionList"
}
}
}}
])
Updating my answer as this question got updated with new required o/p :
Answer for New Question :
If you've only one transaction matching to given criteria /.*aear.*/i, let's say description is unique across accountList array of report document(exact for provided sample):
db.report.aggregate([{
$match: {
'accountList.transactionList.description': /.*aear.*/i
}
},{ $unwind: '$accountList' },{ $unwind: '$accountList.transactionList' },{$match :{ 'accountList.transactionList.description': /.*aear.*/i}}, { $project: { 'accountList': 1, _id: 0 } }])
But, if you've multiple descriptions (across multiple objects in accountsList array of a report document) matches to given criteria in accountList :
db.report.aggregate([{
$match: {
'accountList.transactionList.description': /.*aear.*/i
}
}, { $unwind: '$accountList' }, { $unwind: '$accountList.transactionList' }, { $match: { 'accountList.transactionList.description': /.*aear.*/i } },
{ $group: { _id: '$_id', accountList: { $push: '$accountList' }, data: { $first: '$$ROOT' } } }
, { $addFields: { 'data.accountList': '$accountList' } }, { $replaceRoot: { 'newRoot': '$data' } }, { $project: { 'accountList': 1, _id: 0 } }
])
Output :
/* 1 */
{
"accountList" : [
{
"id" : "1100",
"type" : "xD",
"currency" : "EUR",
"transactionList" : {
"onDate" : ISODate("2019-08-26T00:00:00.000Z"),
"description" : "aear"
}
},
{
"id" : "1200",
"type" : "xD",
"currency" : "USD",
"transactionList" : {
"onDate" : ISODate("2019-08-16T00:00:00.000Z"),
"description" : "aear"
}
}
]
}
/* 2 */
{
"accountList" : [
{
"id" : "1",
"type" : "xD",
"currency" : "EUR",
"transactionList" : {
"onDate" : ISODate("2019-08-26T00:00:00.000Z"),
"description" : "aear"
}
}
]
}
If in case you've multiple matching descriptions in transaction array & also in other objects of accounts array (this will work for all above scenarios as well but it might not be needed as per requirement, it can be bulky, Check document#3 in Output for clarification) :
db.report.aggregate([
{ "$match": { "accountList.transactionList.description": /.*aear.*/i } },
{ "$unwind": "$accountList" },
{ "$unwind": "$accountList.transactionList" },
{ "$match": { "accountList.transactionList.description": /.*aear.*/i } },
{
"$group": {
"_id": {
"docId": "$_id",
"accountsListObjId": "$accountList.id"
},
"transactionList": { "$push": "$accountList.transactionList" },
"accountList": { "$first": '$accountList' }
}
}
, { $addFields: { 'accountList.transactionList': '$transactionList' } },
{
"$group": {
"_id": "$_id.docId",
"accountList": { $push: '$accountList' }
}
}, { $project: { 'accountList': 1, _id: 0 } }
])
Output :
/* 1 */
{
"accountList" : [
{
"id" : "1100",
"type" : "xD",
"currency" : "EUR",
"transactionList" : [
{
"onDate" : ISODate("2019-08-26T00:00:00.000Z"),
"description" : "aear"
}
]
},
{
"id" : "1200",
"type" : "xD",
"currency" : "USD",
"transactionList" : [
{
"onDate" : ISODate("2019-08-16T00:00:00.000Z"),
"description" : "aear"
}
]
}
]
}
/* 2 */
{
"accountList" : [
{
"id" : "1",
"type" : "xD",
"currency" : "EUR",
"transactionList" : [
{
"onDate" : ISODate("2019-08-26T00:00:00.000Z"),
"description" : "aear"
}
]
}
]
}
/* 3 */
{
"accountList" : [
{
"id" : "00",
"type" : "xD",
"currency" : "EUR",
"transactionList" : [
{
"onDate" : ISODate("2019-08-26T00:00:00.000Z"),
"description" : "aear"
},
{
"onDate" : ISODate("2019-08-26T00:00:00.000Z"),
"description" : "aear"
}
]
},
{
"id" : "100",
"type" : "xD",
"currency" : "USD",
"transactionList" : [
{
"onDate" : ISODate("2019-08-16T00:00:00.000Z"),
"description" : "aear"
}
]
}
]
}
If you're looking for exact text, you can do this as well(cause regex is not allowed in cond) :
db.report.aggregate([
{
$match: {
'accountList.transactionList.description': 'aear'
}
}, { $unwind: '$accountList' }, {
$addFields: {
'accountList.transactionList': {
$filter: {
input: '$accountList.transactionList',
as: 'eachTransaction',
cond: { $eq: ["$$eachTransaction.description", 'aear'] }
}
}
}
}, { $match: { 'accountList.transactionList': { $ne: [] } } }, { $group: { _id: '$_id', accountList: { $push: '$accountList' }, data: { $first: '$$ROOT' } } }
, { $addFields: { 'data.accountList': '$accountList' } }, { $replaceRoot: { 'newRoot': '$data' } }, { $project: { 'accountList': 1, _id: 0 } }])
Output : Same as above.
Answer for Old Question :
Ok you've two options here, Please try these :
If you've only one object in accountList which does matches with the given filter then you can simply do this:
db.report.find({'accountList.transactionList.description': /.*aear.*/i}, {'accountList.$': 1})
Output :
/* 1 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df0c7"),
"accountList" : [
{
"id" : "4474",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
/* 2 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df0d7"),
"accountList" : [
{
"id" : "4400",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
/* 3 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df077"),
"accountList" : [
{
"id" : "0000",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
/* 4 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df1c7"),
"accountList" : [
{
"id" : "0101",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
Downside of above .find () query is it would get only first matching object in accountList, If you've multiple matching objects for given filter in accountList then you need to use aggregation (this aggregation query can be used for earlier scenario as well, Please check output for diff) :
db.report.aggregate([
{
$match: {
"accountList.transactionList.description": /.*aear.*/i
}
},
{ $unwind: "$accountList" },
{
$match: {
"accountList.transactionList.description": /.*aear.*/i
}
}, { $group: { _id: '$_id', accountList: { $push: '$accountList' }, doc: { $first: '$$ROOT' } } }, { $addFields: { 'doc.accountList': '$accountList' } },
{ $replaceRoot: { 'newRoot': '$doc' } }
])
Output :
// This first object is best example where you need aggregation
/* 1 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df1c7"),
"accountList" : [
{
"id" : "0101",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
},
{
"id" : "1111",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
/* 2 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df0d7"),
"accountList" : [
{
"id" : "4400",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
/* 3 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df077"),
"accountList" : [
{
"id" : "0000",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
/* 4 */
{
"_id" : ObjectId("5d6435145a0d22d3c86df0c7"),
"accountList" : [
{
"id" : "4474",
"transactionList" : [
{
"description" : "aear"
},
{
"description" : "koe"
}
]
}
]
}
Try this query:
db.report.find({'accountList[0].transactionList[4].description': { $regex: /.*aear.*/i} })
OR - Which will return only the first matching document:
db.report.find({'accountList[0].transactionList[4].description': /.*aear.*/i}).limit(1)
I have a collection like below
{
relatedProperties: [ //Array
{
locations: [ //Array
{
value: "Brazil"
},
{
value: "Germany"
}
]
},
{
locations: []
},
{
locations: null
}
]
}
How do I write an aggregation to make only the empty or null arrays to have a default value like;
locations: [
{
value: "India"
}
]
You can use $mergeObjects to keep other fields whatever they are:
db.collection.aggregate([
{
$project: {
relatedProperties: {
$map: {
input: "$relatedProperties",
as: "rp",
in: {
$cond: {
if: {
$eq: [
{
$ifNull: [
"$$rp.locations",
[]
]
},
[]
]
},
then: {
$mergeObjects: [
"$$rp",
{
locations: [
{
value: "India"
}
]
}
]
},
else: "$$rp"
}
}
}
}
}
}
])
The processing can also be done using $map operator. The following query can get us the expected output:
db.collection.aggregate([
{
$addFields:{
"relatedProperties":{
$map:{
"input":"$relatedProperties",
"as":"relatedProperty",
"in":{
"name":"$$relatedProperty.name",
"age":"$$relatedProperty.age",
"org":"$$relatedProperty.org",
"locations":{
$cond:[
{
$in:["$$relatedProperty.locations",[null,[]]]
},
[
{
"value":"India"
}
],
"$$relatedProperty.locations"
]
}
}
}
}
}
}
]).pretty()
Data set:
{
"_id" : ObjectId("5d666236986fb04b2aeabe2a"),
"relatedProperties" : [
{
"locations" : [
{
"value" : "Brazil"
},
{
"value" : "Germany"
}
],
"name" : "ABC",
"age" : "12",
"org" : {
"value" : "org1"
}
},
{
"locations" : [ ],
"name" : "CDE",
"age" : "30",
"org" : {
"value" : "org2"
}
},
{
"locations" : null,
"name" : "EFG",
"age" : "20",
"org" : {
"value" : "org3"
}
}
]
}
Output:
{
"_id" : ObjectId("5d666236986fb04b2aeabe2a"),
"relatedProperties" : [
{
"name" : "ABC",
"age" : "12",
"org" : {
"value" : "org1"
},
"locations" : [
{
"value" : "Brazil"
},
{
"value" : "Germany"
}
]
},
{
"name" : "CDE",
"age" : "30",
"org" : {
"value" : "org2"
},
"locations" : [
{
"value" : "India"
}
]
},
{
"name" : "EFG",
"age" : "20",
"org" : {
"value" : "org3"
},
"locations" : [
{
"value" : "India"
}
]
}
]
}
I have a collection as below what I want is to fetch the items that has exact match of Tag="dolore", I tried different ways but I am getting all the elements if any of the embedded element has tag as dolore
{
"_id" : 123,
"vendor" : "ut",
"boxes" : [
{
"boxRef" : 321,
"items" : [
{
"Tag" : "dolore",
},
{
"Tag" : "irure",
},
{
"Tag" : "labore",
}
]
},
{
"boxRef" : 789,
"items" : [
{
"Tag" : "incididunt",
},
{
"Tag" : "magna",
},
{
"Tag" : "laboris",
}
]
},
{
"boxRef" : 456,
"items" : [
{
"Tag" : "reprehenderit",
},
{
"Tag" : "reprehenderit",
},
{
"Tag" : "enim",
}
]
}
]
}
If you are expecting to get only the matching embedded documents you have $unwind, $match and then $group to reverse the $unwind. Like this:
db.getCollection('collectionName').aggregate([
{
$unwind:"$boxes"
},
{
$unwind:"$boxes.items"
},
{
$match:{
"boxes.items.Tag":"dolore"
}
},
{
$group:{
_id:{
boxRef:"$boxes.boxRef",
_id:"$_id"
},
vendor:{
"$first":"$vendor"
},
boxRef:{
"$first":"$boxes.boxRef"
},
items:{
$push:"$boxes.items"
}
}
},
{
$group:{
_id:"$_id._id",
vendor:{
"$first":"$vendor"
},
boxes:{
$push:{
boxRef:"$boxRef",
items:"$items"
}
}
}
},
])
Output:
{
"_id" : 123.0,
"vendor" : "ut",
"boxes" : [
{
"boxRef" : 321.0,
"items" : [
{
"Tag" : "dolore"
}
]
}
]
}
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/