Comparing two object arrays and check if they have common elements - mongodb

How can I execute a query in MongoDB that returns _id if FirstArray and SecondArray has elements in common in "Name" field?
This is the collection structure:
{
"_id" : ObjectId("58b8d9e3b2b4e07bff8feed5"),
"FirstArray" : [
{
"Name" : "A",
"Something" : "200 ",
},
{
"Name" : "GF",
"Something" : "100 ",
}
],
"SecondArray" : [
{
"Name" : "BC",
"Something" : "200 ",
},
{
"Name" : "A",
"Something" : "100 ",
}
]
}

3.6 Update:
Use $match with $expr. $expr allows use of aggregation expressions inside $match stage.
db.collection.aggregate([
{"$match":{
"$expr":{
"$eq":[
{"$size":{"$setIntersection":["$FirstArray.Name","$SecondArray.Name"]}},
0
]
}
}},
{"$project":{"_id":1}}
])
Old version:
You can try $redact with $setIntersection for your query.
$setIntersection to compare the FirstArrays Names with SecondArrays Names and return array of common names documents followed by $size and $redact and compare result with 0 to keep and else remove the document.
db.collection.aggregate(
[{
$redact: {
$cond: {
if: {
$eq: [{
$size: {
$setIntersection: ["$FirstArray.Name", "$SecondArray.Name"]
}
}, 0]
},
then: "$$KEEP",
else: "$$PRUNE"
}
}
}, {
$project: {
_id: 1
}
}]
)

Related

MongoDB $cond with embedded document array

I am trying to generate a new collection with a field 'desc' having into account a condition in field in a documment array. To do so, I am using $cond statement
The origin collection example is the next one:
{
"_id" : ObjectId("5e8ef9a23e4f255bb41b9b40"),
"Brand" : {
"models" : [
{
"name" : "AA"
},
{
"name" : "BB"
}
]
}
}
{
"_id" : ObjectId("5e8ef9a83e4f255bb41b9b41"),
"Brand" : {
"models" : [
{
"name" : "AG"
},
{
"name" : "AA"
}
]
}
}
The query is the next:
db.runCommand({
aggregate: 'cars',
'pipeline': [
{
'$project': {
'desc': {
'$cond': {
if: {
$in: ['$Brand.models.name',['BB','TC','TS']]
},
then: 'Good',
else: 'Bad'
}
}
}
},
{
'$project': {
'desc': 1
}
},
{
$out: 'cars_stg'
}
],
'allowDiskUse': true,
})
The problem is that the $cond statement is always returning the "else" value. I also have tried $or statement with $eq or the $and with $ne, but is always returning "else".
What am I doing wrong, or how should I fix this?
Thanks
Since $Brand.models.name returns an array, we cannot use $in operator.
Instead, we can use $setIntersection which returns an array that contains the elements that appear in every input array
db.cars.aggregate([
{
"$project": {
"desc": {
"$cond": [
{
$gt: [
{
$size: {
$setIntersection: [
"$Brand.models.name",
[
"BB",
"TC",
"TS"
]
]
}
},
0
]
},
"Good",
"Bad"
]
}
}
},
{
"$project": {
"desc": 1
}
},
{
$out: 'cars_stg'
}
])
MongoPlayground | Alternative $reduce

Check duplicates of certain field for documents array with inner array

I have 2 objects,
{
_id: ObjectId("5cd9010310b80b3e38cd3f88")
subGroup: [
bookList: [
{
title: "A good book",
id: "abc123"
}
]
]
}
{
_id: ObjectId("5cd9010710b80b3e38cd3f89")
subGroup: [
bookList: [
{
title: "A good book",
id: "abc123"
}
]
These are 2 different objects. I would like to detect the occurence of these 2 objects where the title is duplicated (eg the same).
I tried this query
db.scope.aggregate({"$unwind": "$subGroup.bookList"}, {"$group" : { "_id": "$title", "count": { "$sum": 1 } } }, {"$match": {"id" :{ "$ne" : null } , "count" : {"$gt": 1} } })
which i looked at other threads on stackoverflow. However, it does not return me anything. How can i solve this?
There are few issues here:
$unwind should be run on subGroup and on subGroup.bookList separately
when specifying _id for $group stage you should use full path (subGroup.bookList.title)
in your $match stage you want to check if _id (not id) is $ne null
Try:
db.col.aggregate([
{"$unwind": "$subGroup"},
{"$unwind": "$subGroup.bookList"},
{"$group" : { "_id": "$subGroup.bookList.title", "count": { "$sum": 1 } } },
{"$match": { "_id" :{ "$ne" : null } , "count" : { "$gt": 1} } }
])
Mongo playground

Count and apply condition to slice the mongodb array document

My document structure looks like this:
{
"_id" : ObjectId("5aeeda07f3a664c55e830a08"),
"profileId" : ObjectId("5ad84c8c0e71892058b6a543"),
"list" : [
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
{
"content" : "answered your post",
"createdBy" : ObjectId("5ad84c8c0e71892058b6a540")
},
],
}
I want to count array of
list field. And apply condition before slicing that
if the list<=10 then slice all the elements of list
else 10 elements.
P.S I used this query but is returning null.
db.getCollection('post').aggregate([
{
$match:{
profileId:ObjectId("5ada84c8c0e718s9258b6a543")}
},
{$project:{notifs:{$size:"$list"}}},
{$project:{notifications:
{$cond:[
{$gte:["$notifs",10]},
{$slice:["$list",10]},
{$slice:["$list","$notifs"]}
]}
}}
])
Your first $project stage effectively wipes out all result fields but the one(s) that it explicitly projects (only notifs in your case). That's why the second $project stage cannot $slice the list field anymore (it has been removed by the first $project stage).
Also, I think your $cond/$slice combination can be more elegantly expressed using the $min operator. So there's at least the following two fixes for your problem:
Using $addFields:
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $addFields: { notifs: { $size: "$list" } } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ "$notifs", 10 ] } ]
}
}}
])
Using a calculation inside the $project - this avoids a stage so should be preferable.
db.getCollection('post').aggregate([
{ $match: { profileId: ObjectId("5ad84c8c0e71892058b6a543") } },
{ $project: {
notifications: {
$slice: [ "$list", { $min: [ { $size: "$list" }, 10 ] } ]
}
}}
])

How to filter specific array size using project aggregation in MongoDB

I have this lab test in the mongodb course i am currently taking, the movies collection have a title field and the instruction says:
Using only $project aggregation.
find the movie titles composed of only 1 word like "Cinderella" and "3-25" should count where as "Cast Away" would not.
Use $split String expression and $size Array expression.
Here's a sample document from movies collection:
{
"_id" : ObjectId("573a1390f29313caabcd4192"),
"title" : "The Conjuring of a Woman at the House of Robert Houdin",
"year" : 1896,
"runtime" : 1,
"cast" : [
"Jeanne d'Alcy",
"Georges M�li�s"
]
}
And here's my code:
var pipeline = [
{ $project: {
"title": { $split: ["$title"," "] }
} },
{ $project: {
"_id": 0,
"title_size": {$eq: [{$size: "$title"}, 1]},
"Movie": "$title"
} }
]
db.movies.aggregate(pipeline)
The $eq returns boolean values true and false, not what i expected, then i tried the $literal: 1as the second expression of $eq but i get the same boolean values
What i wanted to achieved is this:
{ "title_size" : 1, "Movie" : [ "Cinderella" ] }
But how?
[
{
$project: {
splitedTitles: {$split: ["$title", " "]}
}
},
{
$match : { splitedTitles : { $size: 1 } }
}
]

mongodb aggregate nested nested array

how i can find the document with $match on position 3 (only last item in array "ndr"). It is necessary that the aggreation search only in the last array-item of ndr.
{
"_id" : ObjectId("58bd5c63a3d24b4a2e4cde03"),
"name" : "great document",
"country" : "us_us",
"cdate" : ISODate("2017-03-06T12:56:03.405Z"),
"nodes" : [
{
"node" : 3244343,
"name" : "Best Node ever",
"ndr" : [
{
"position" : 7,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
},
{
"position" : 3,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
}
]
}
],
}
I need this result after aggregation
{
"name" : "great document",
"country" : "us_us",
"cdate" : ISODate("2017-03-06T12:56:03.405Z"),
"nodes" : [
{
"node" : 3244343,
"name" : "Best Node ever",
"ndr" : [
{
"position" : 3,
"cdate" : ISODate("2017-03-06T10:55:20.000Z")
}
]
}
]
}
I hope anyone can help me.
You can try below aggregation with Mongo 3.4 version.
The below query finds the last item (-1) using $arrayElemAt operator in the ndr array and stores the variable in last using $let operator for each nodes and compare the last variable position value using $$ notation to 3 and wraps the nbr element within array [] if entry found and else returns empty array.
$map operator to reach nbr array inside the nodes array and project the updated nbr array while mapping the rest of nodes fields.
$addFields stage will overwrite the existing nodes with new nodes while keeping the all the other fields.
db.collection.aggregate([{
$addFields: {
nodes: {
$map: {
input: "$nodes",
as: "value",
in: {
node: "$$value.node",
name: "$$value.name",
ndr: {
$let: {
vars: {
last: {
$arrayElemAt: ["$$value.ndr", -1]
}
},
in: {
$cond: [{
$eq: ["$$last.position", 3]
},
["$$last"],
[]
]
}
}
}
}
}
}
}
}]);
Update:
You can try $redact which will keep the whole document if it finds the matching position from with the given filter.
$map to project the true, false values based on the filter for each of the nodes nbr position value and $anyElementTrue will inspect the previous boolean values for each doc and return a true or false value and $redact will use the booelan value from above comparison; true value to keep and false value to remove the document.
db.collection.aggregate([{
$redact: {
$cond: [{
$anyElementTrue: {
$map: {
input: "$nodes",
as: "value",
in: {
$let: {
vars: {
last: {
$arrayElemAt: ["$$value.ndr", -1]
}
},
in: {
$cond: [{
$eq: ["$$last.position", 3]
},
true,
false
]
}
}
}
}
}
}, "$$KEEP", "$$PRUNE"]
}
}]);
you will need to unwind both nested arrays.
db.<collection>.aggregate([
{ $unwind: '$nodes' },
{ $unwind: '$nodes.ndr'},
{ $group: {'_id':{'_id':'$_id', 'nodeID', '$nodes.node' },
'name':{'$last':'$name'},
'country':{'$last':'$country'},
'cdate':{'$last':'$cdate'},
'nodes':{'$last':'$nodes'}
}
},
{ $match : { nodes.ndr.position: 3 } }
]);
From here you can reassemble the aggregate results with a $group on the and do a projection. I'm not sure what your ultimate end result should be.