MongoDB conditional aggregation with unwind - mongodb

I have db collection like:
{
"_id" : "af5c00e4-d3a8-419d-8793-c0cf328802ec",
"collaborators" : [
{
"_id" : "9bd2eee8-bf6c-4c6f-bab7-d2d175aed807",
"origin" : [
{
"originId" : "123"
}
],
"firstName" : "Parveen",
"lastName" : "Vendor",
"email" : "pk#gmail.com"
},
{
"_id" : "234324-bf6c-4c6f-bab7-d2d175aed807",
"origin" : [
{
"originId" : "1234"
}
],
"firstName" : "Parveen123",
"lastName" : "34",
"email" : "abc#gmail.com"
}
],
"orders" : [
{
"totalAmount" : 10,
"collaborators" : [
{
"origin" : [
{
"originId" : "123",
}
],
"type" : "Supplier"
},
{
"origin" : [
{
"originId" : "1233",
}
],
"type" : "Supplier"
}
]
}
]
}
Want to replace data in orders (array) collaborators(array) with
**collaborators(array) ** data if matches originId of both
Expected output
{
"_id" : "af5c00e4-d3a8-419d-8793-c0cf328802ec",
"collaborators" : [
{
"_id" : "9bd2eee8-bf6c-4c6f-bab7-d2d175aed807",
"origin" : [
{
"originId" : "123"
}
],
"firstName" : "Parveen",
"lastName" : "Vendor",
"email" : "pk#gmail.com"
},
{
"_id" : "234324-bf6c-4c6f-bab7-d2d175aed807",
"origin" : [
{
"originId" : "1234"
}
],
"firstName" : "Parveen123",
"lastName" : "34",
"email" : "abc#gmail.com"
}
],
"orders" : [
{
"totalAmount" : 10,
"collaborators" : [
{
"_id" : "9bd2eee8-bf6c-4c6f-bab7-d2d175aed807",
"origin" : [
{
"originId" : "123"
}
],
"firstName" : "Parveen",
"lastName" : "Vendor",
"email" : "pk#gmail.com"
},
{
"origin" : [
{
"originId" : "1233",
}
],
"type" : "Supplier"
}
]
}
]
}
One collection record can have multiple collaborators , same as order can have multiple collaborators.
Need to replace only where originId matches

One option is:
Use $map, $mergeObjects and $filter to add a new key to each item of orders.collaborators with the matching item from collaborators if exists.
choose the new key if it contains data or the original key, if not.
db.collection.aggregate([
{$set: {
ordersCollaborators: {
$map: {
input: {$first: "$orders.collaborators"},
in: {$mergeObjects: [
{original: "$$this"},
{new: {
$filter: {
input: "$collaborators",
as: "i",
cond: {
$eq: [
{$first: "$$i.origin.originId"},
{$first: "$$this.origin.originId"}
]
}
}
}
}
]
}
}
}
}
},
{$set: {
orders: [
{totalAmount: {$first: "$orders.totalAmount"},
collaborators: {
$map: {
input: "$ordersCollaborators",
in: {
$cond: [
{$eq: [{$size: "$$this.new"}, 0]},
"$$this.original",
"$$this.new"
]
}
}
}
}
],
ordersCollaborators: "$$REMOVE"
}
}
])
See how it works on the playground example

Related

Mongo find query only returns one result

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

What wrong with my mongo query to get a specific by nest document?

{
"_id" : ObjectId("5dbdacc28cffef0b94580dbd"),
"owner" : {
"image" : "https://lh3.googleusercontent.com/a-/AAuE7mCpG2jzbEdffPgdeVWnkBKwyzCCwEB1HMbU1LAVAg=s50",
"fullname" : "soeng kanel",
"userID" : "5da85558886aee13e4e7f044"
},
"image" : "soeng kanel-1572711618984.png",
"body" : "sdadadasdsadadas sds",
"date" : ISODate("2019-11-02T16:20:05.558Z"),
"comments" : [
{
"user" : "5da85558886aee13e4e7f044",
"fullname" : "soeng kanel",
"username" : "",
"comment" : "sdsfdsfdsfds",
"_id" : ObjectId("5dbdacc78cffef0b94580dbf"),
"replies" : [
{
"likes" : [
"5da85558886aee13e4e7f044"
],
"date" : ISODate("2019-11-02T16:20:05.558Z"),
"_id" : ObjectId("5dbdacd78cffef0b94580dc0"),
"reply" : "r1111111",
"username" : "",
"fullname" : "soeng kanel",
"user" : "5da85558886aee13e4e7f044"
},
{
"likes" : [],
"date" : ISODate("2019-11-02T16:20:05.558Z"),
"_id" : ObjectId("5dbdacdb8cffef0b94580dc1"),
"reply" : "r222222",
"username" : "",
"fullname" : "soeng kanel",
"user" : "5da85558886aee13e4e7f044"
},
{
"likes" : [],
"date" : ISODate("2019-11-03T03:04:23.528Z"),
"_id" : ObjectId("5dbe4749fa751f05afcc1bd6"),
"reply" : "33333333",
"username" : "",
"fullname" : "soeng kanel",
"user" : "5da85558886aee13e4e7f044"
}
],
"date" : ISODate("2019-11-02T16:20:05.558Z"),
"likes" : []
}
],
"likes" : [
"5da85558886aee13e4e7f044"
],
"project" : {},
"__v" : 2
}
My query is
db.getCollection("posts").aggregate([
{ $match: {_id: ObjectId("5dbdacc28cffef0b94580dbd"), "comments._id": ObjectId("5dbdacc78cffef0b94580dbf") }},
{ $unwind: "$comments"},
{ $match: { "comments._id": ObjectId("5dbdacc78cffef0b94580dbf")}},
{ $project: {"replies": "$comments.replies", _id: 0}},
{ $match: { "replies._id": ObjectId("5dbdacd78cffef0b94580dc0")}},
{ $project: {"likes": "$replies.likes", _id: 0}},
])
With this query I get 3 elements ,
{
"likes" : [
[
"5da85558886aee13e4e7f044"
],
[],
[]
]
}
That is not what I want, what I want is to get by specific
replies by this _id 5dbdacd78cffef0b94580dc0.
And My expectation
{
"likes" : [
[
"5da85558886aee13e4e7f044"
]
]
}
Try using $unwind on replies before you $match stage on replies.
db.collection.aggregate([
{
$match: {
_id: ObjectId("5dbdacc28cffef0b94580dbd"),
"comments._id": ObjectId("5dbdacc78cffef0b94580dbf")
}
},
{
$unwind: {
path: "$comments",
preserveNullAndEmptyArrays: false
}
},
{
$match: {
"comments._id": ObjectId("5dbdacc78cffef0b94580dbf")
}
},
{
$project: {
"replies": "$comments.replies",
_id: 0
}
},
{
$unwind: {
path: "$replies",
preserveNullAndEmptyArrays: false
}
},
{
$match: {
"replies._id": ObjectId("5dbdacd78cffef0b94580dc0")
}
},
{
$project: {
"likes": "$replies.likes"
}
}
])
Above query produce output in the following fashion:
[
{
"likes": [
"5da85558886aee13e4e7f044"
]
}
]
I hope that's okay.

Mongo Aggregation to replace an empty / null array field inside an array with a default array

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

MongoDB creates array of arrays in $group $push instead of flat array

I am trying to group a set of documents after an $unwind operation. My documents look like this:
{
"_id" : ObjectId("5cdb5b5acadf5100019da2f4"),
"allowedLocations" : [
{
"type" : "country",
"value" : "world",
"label" : "World"
}
],
"disallowedLocations" : [
{
"type" : "country",
"value" : "CF",
"label" : "Central African Republic"
},
{
"type" : "country",
"value" : "CN",
"label" : "China"
}
],
}
{
"_id" : ObjectId("5cdb5b5acadf5100019da2f4"),
"allowedLocations" : [
{
"type" : "country",
"value" : "US",
"label" : "United States of America"
}
],
"disallowedLocations" : [
{
"type" : "country",
"value" : "CA",
"label" : "Canada"
},
{
"type" : "country",
"value" : "MX",
"label" : "Mexico"
}
],
}
I want to group them by _id and then concatenate the allowedLocations and disallowedLocations arrays into one. The group stage in my pipeline looks like this:
{
"$group" : {
"_id" : "$_id",
"allowedLocations" : {
"$push" : "$allowedLocations"
},
"disallowedLocations" : {
"$push" : "disallowedLocations"
}
}
}
The problem is, the result I get is not a document with both arrays concatenated, but an array of arrays, each element of the array being the array of each document:
{
"_id" : ObjectId("5cdb5b5acadf5100019da2f4"),
"allowedLocations" : [
[
{
"type" : "country",
"value" : "US",
"label" : "United States of America"
}
],
[
{
"type" : "country",
"value" : "world",
"label" : "World"
}
],
],
"disallowedLocations" : [
[
{
"type" : "country",
"value" : "CF",
"label" : "Central African Republic"
},
{
"type" : "country",
"value" : "CN",
"label" : "China"
}
],
[
{
"type" : "country",
"value" : "CA",
"label" : "Canada"
},
{
"type" : "country",
"value" : "MX",
"label" : "Mexico"
}
]
}
}
Is there a way to produce a flat array with only objects as elements? I also tried with $concatArrays before the push but that creates more arrays inside the arrays.
Two solutions here. You can either run $unwind on both arrays to get single allowed and disallowed location per document and then run your $group stage:
db.col.aggregate([
{
$unwind: "$allowedLocations"
},
{
$unwind: "$disallowedLocations"
},
{
"$group" : {
"_id" : "$_id",
"allowedLocations" : {
"$addToSet" : "$allowedLocations"
},
"disallowedLocations" : {
"$addToSet" : "$disallowedLocations"
}
}
}
])
or you can run your $group first and then use $reduce to flatten allowedLocations and disallowedLocations:
db.col.aggregate([
{
"$group" : {
"_id" : "$_id",
"allowedLocations" : {
"$push" : "$allowedLocations"
},
"disallowedLocations" : {
"$push" : "$disallowedLocations"
}
}
},
{
$project: {
_id: 1,
allowedLocations: {
$reduce: {
input: "$allowedLocations",
initialValue: [],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
},
disallowedLocations: {
$reduce: {
input: "$disallowedLocations",
initialValue: [],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
}
}
}
])

Match documents with their inner array element variables in MongoDB

I can't understand how to compare a document variable to another document variable. My goal is to match all Authors who have at least one book written in their mothertongue (native language).
However, after unwinding the books array, My $match: { mothertongue: "$bookLang"}} doesn't return return anything, eventhough they're the same in the $project stage.
Can you help me without javascript?
This is my current query:
db.author.aggregate([
{
$unwind: "$books"
},
{
$project: {
books: true,
mothertongue: true,
bookLang: "$books.lang"
}
},
{
$match: { mothertongue: "$bookLang"}
}
])
And here is a sample of the dataset
{
"_id" : ObjectId("5aa7b34a338571a7470be0eb"),
"fname" : "Minna",
"lname" : "Canth",
"mothertongue" : "Finnish",
"birthdate" : ISODate("1844-03-19T00:00:00Z"),
"deathdate" : ISODate("1897-05-12T00:00:00Z"),
"books" : [
{
"title" : "Anna Liisa",
"lang" : "Finnish",
"language" : "finnish",
"edition" : 1,
"cover" : "Hard",
"year" : 1895,
"categorytags" : [
"Finland"
],
"publisher" : [
{
"name" : "Tammi",
"pubId" : ObjectId("5aa7b34a338571a7470be0e4")
}
]
},
{
"title" : "The Burglary and The House of Roinila",
"lang" : "English (UK)",
"translator" : ObjectId("5aa7b34a338571a7470be0ee"),
"cover" : "Soft",
"year" : 2010,
"categorytags" : [
"Finland"
],
"publisher" : [
{
"name" : "Jonathan Cape",
"pubId" : ObjectId("5aa7b34a338571a7470be0e7")
}
]
},
{
"title" : "Anna Liisa 2 ed.",
"lang" : "Finnish",
"language" : "finnish",
"edition" : 2,
"cover" : "hard",
"year" : 1958,
"categorytags" : [
"Finland"
],
"publisher" : [
{
"name" : "Otava",
"pubId" : ObjectId("5aa7b34a338571a7470be0e9")
}
]
}
]
}
End goal. note I'm not interested in formatting just yet, just the filtering
{
"Author" : "Charles Bukowski",
"BooksInMothertongue" : [
"Love Is a Dog from Hell"
]
}
{
"Author" : "Minna Canth",
"BooksInMothertongue" : [
"Anna Liisa",
"Anna Liisa 2 ed."
]
}
...
Try this
db.author.aggregate([{
$match: {
books: {
$ne: []
}
}
},
{
$project: {
books: {
$filter: {
input: "$books",
as: "book",
cond: {
$eq: ["$$book.lang", "$mothertongue"]
}
}
},
fname: 1
}
}, {
$unwind: "$books"
},
{
$group: {
_id: "$_id",
Author: {
$first: '$fname'
},
BooksInMothertongue: {
$push: "$books.title"
}
}
}
])