MongoDB merge int arrays in one result - arrays

I have this aggregation result
[
{
"ids": [1,100]
},
{
"ids": [200, 100, 3]
}
]
I want to merge them as one result like this:
[
1,
200,
100,
3
]
I have tried group but the result not like I want:
{
$group: {
_id: 1,
merged: {
$push: "$ids"
}
}
}
result:
[
{
"_id": 1,
"merged": [
[1, 100],
[200, 100, 3]
]
}
]

Query
we dont have $concat accumulator
you can do it with $unwind , $group $addToSet
(null or 1 its the same, as long each member is grouped with same key, null is more common)
or you could do it with $push and $reduce $union
here is the first approach looks easier
PlayMongo
aggregate(
[{"$unwind": {"path": "$ids"}},
{"$group": {"_id": null, "ids": {"$addToSet": "$ids"}}},
{"$unset": ["_id"]}])

Related

MongoDB query with constraint on sum of attributes

I'd like a MongoDB query that returns records where the sum of certain attributes satisfies a constraint, for instance given the following documents:
[
{ _id: 1, q1a: 20, q1b: 50, q1c: 30},
{ _id: 2, q1a: 50, q1b: 30, q1c: 20},
{ _id: 3, q1a: 0, q1b: 0, q1c: 0},
]
Id like to run a query that return all and only docs where (q1a + q1b + q1c) == 100, which in this example is records 1 and 2 above.
Is there a way to express this in MongoDB without using $where and writing the sum as a Javascript function?
You can use $expr along with $sum:
db.collection.find({ $expr: { $eq: [ 100, { $sum: [ "$q1a", "$q1b", "$q1c" ] } ] } })
Mongo Playground

Mongo project child object but with fewer props

I have a simple document like this in my Collection named 'Partners'
[{
PartnerName: 'Company A',
MarketExpertese: [{Sector: {Code: 1, TotalYears: 1, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 2, TotalYears: 2, TotalMonths: 20, TotalClients: 10}},
{Sector: {Code: 3, TotalYears: 3, TotalMonths: 20, TotalClients: 10}}]
}]
The result of desired projection would be:
[{
PartnerName: 'Company A',
MarketExpertese: [{SectorCode: 1, TotalYears: 1},
{SectorCode: 2, TotalYears: 2},
{SectorCode: 3, TotalYears: 3}]
}]
I tried the projection with Map but didnĀ“t work. If I would need just an array of SectorCode (simple array, ex. {[1, 2, 3]} ) I could do, but as I need a prop with 2 values (sectorCode and TotalYears) my map fails...
The closet I could get was this Mongo Playground
we need to use $unwind to get a stream of documents regarding the array MarketExpertese, each document will have an element from that array
then use the $project stage to format the output as we need
then use $group to group the documents we have unwind in first step
the query may look something like this
db.collection.aggregate([
{
"$unwind": "$MarketExpertese" // First we need to use unwind to get a stream of documents regarding this array, each document will have only one element from that array
},
{
"$project": {
PartnerName: 1,
Taste: {
SectorCode: "$MarketExpertese.Sector.Code",
TotalYears: "$MarketExpertese.Sector.TotalYears"
}
}
},
{
"$group": { // then group the results
"_id": "$_id",
PartenerName: {
"$first": "$PartnerName"
},
Taste: {
"$addToSet": "$Taste"
}
}
}
])
and here is a working example on Mongo Playground
Update
you can use $map to do the same functionality
db.collection.aggregate([
{
$project: {
"_id": 0,
"PartnerName": 1,
"Teste": {
"$map": {
"input": "$MarketExpertese",
"as": "elem",
"in": {
SectorCode: "$$elem.Sector.Code",
TotalYears: "$$elem.Sector.TotalYears",
}
}
}
}
}
])
and you can test it here Mongo Playground 2

How to get the intersection of two arrays in MongoDB ($setIntersection does not work well)

I have a simple data structure that defines people and their friends.
{
id: 0,
name: 'a',
friends: [1,2,3]
}
I need to find the common friends of two people. I managed to use the aggregation pipleline get the friends array into an array.
{ "_id" : 0, "friends" : [ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], [ 0, 1, 2 ] ] }
So the friends field is a nested array, and I want to get the intersection of its elements.
I tried to use the $setIntersection operation, however I found it does not accept a variable of an array, it only accepts an array of variables. So I have to use something like this to get the result.
{
$project: {
commonFriendIds: {
$setIntersection: [
{ $arrayElemAt: ["$friends", 0] },
{ $arrayElemAt: ["$friends", 1] }
]
}
}
}
It looks ugly, and I have to modify the code to if I need to get the common friends of 3 or more people.
Is there's a better way to accomplish this?
You can use $reduce aggregation operator. Now It will provide you the intersection for all the nested arrays inside the friends array.
db.collection.aggregate([
{ "$project": {
"commonFriendIds": {
"$reduce": {
"input": "$friends",
"initialValue": { "$arrayElemAt": ["$friends", 0] },
"in": { "$setIntersection": ["$$this", "$$value"] }
}
}
}}
])
MongoPlayground

Using $size and $addToSet to compare multiple arrays from the same cluster

Here is an example of the kind of documents I'm querying:
INPUT:
}
"_id": ObjectId("2786872873872"),
"data_shop" : {
"records_data" : [
{
"artist_name" : [
{
"val" : "BEYONCE",
},
],
"album_name" : [
{
"val" : "COUNTDOWN",
}
],
"qty" : [
0,
1,
2,
3
]
},
{
"artist_name" : [
{
"val" : "MUSE",
},
],
"album_name" : [
{
"val" : "THE RESISTANCE",
}
],
"qty" : [
0,
1,
2,
3,
3
]
}
],
},
}
}
"_id": ObjectId("2786872855555"),
"data_shop" : {
"records_data" : [
{
"artist_name" : [
{
"val" : "MAC MILLER",
},
],
"album_name" : [
{
"val" : "SWIMMING",
}
],
"qty" : [
0,
1,
2,
3,
]
},
{
"artist_name" : [
{
"val" : "DAFT PUNK",
},
],
"album_name" : [
{
"val" : "RANDOM ACCESS MEMORIES",
}
],
"qty" : [
0,
1,
2,
3,
4,
]
}
],
},
}
What I've done so far:
I'm trying to use both $size and $addtoSet in order to return the ObjectIds that have repeated numbers in the qty field. As you can see, only the first ObjectId has a repeated number (3) in the qty field.
This is what I've done so far:
db.mycollection.aggregate(
[
{$match: {"data_shop.records_data.qty.1": {$lte: 1}}},
{
$project: {Album_Cluster:"$data_shop.records_data"}
},
{
$unwind: "$Album_Cluster"
},
{
$project: {qty: "$Album_Cluster.qty"},
},
{
$project: {qty_size: {$size: "$qty"}, qty:1}
},
{ $match: {"qty_size.1": {$exists: false}, qty_size: {$gt: 1} }},
{$group:
{_id: "$_id",
totalSize: {$push: "$qty_size"},
realSize: {$addToSet: "$qty"},
}
},
],
{allowDiskUse: true}
)
And this is the result of the query above, in order to check the functionality of the query:
{"_id":ObjectId("2786872873872"), "totalSize": [4, 5], "realSize":[[0, 1, 2, 3]]}
{"_id":ObjectId("2786872855555"), "totalSize": [4, 5], "realSize":[[0, 1, 2, 3], [0, 1, 2, 3, 4]]}
I'm a little bit stuck at this part since I want to compare the total size of each array versus the real size of the array (by real size I mean non-repeating numbers)
OUTPUT
This is how the output of the query should look like:
{"_id":ObjectId("2786872873872"), "isRepeating": true}
{"_id":ObjectId("2786872855555"), "isRepeating": false}
EDIT:
I've improved my query in order to get this output schema:
db.mycollection.aggregate(
[
{$match: {"data_shop.records_data.qty.1": {$lte: 1}}},
{
$project: {Album_Cluster:"$data_shop.records_data"}
},
{
$unwind: "$Album_Cluster"
},
{
$project: {qty: "$Album_Cluster.qty"},
},
{
$project: {qty_size: {$size: "$qty"}, qty:1}
},
{$group:
{
_id: "$_id",
totalSize: {$addToSet: "$qty_size"},
realSize: {$addToSet: "$qty"},
}
},
{$unwind: "$realSize"},
{
$project:
{
totalSize:1,
real_count: {$size: "$realSize"}
}
},
{$unwind: "$totalSize"},
{
$group: {
_id: "$_id",
total_size: {$addToSet: "$totalSize"},
real_size: {$addToSet: "$real_count"}
}
},
],
{allowDiskUse: true}
)
And now I'm getting this as my output:
{"_id":ObjectId("2786872873872"), "total_size": [4, 5], "real_size":[4]}
{"_id":ObjectId("2786872855555"), "total_size": [4, 5], "real_size":[5, 4]}
Now my question is, does $in allow me to validate that [4, 5] is valid in [5, 4] so my output will be isRepeating = false?
Altought you can achieve this with a stack og unwind / group stages, it can be very expensive in resources consumption.
Unfortunaltely, the $addToSet oerator in available only in $group stage.
But... There's a trick, with the $setUnion operator.
$setUnion performs set operation on arrays, treating arrays as sets. If an array contains duplicate entries, $setUnion ignores the duplicate entries.
Knowing this, performing a $setUnion on your qty array, without any other array, will just... remove the duplicates.
Here's a implementation of this approach, using only 2 project stages
db.collection.aggregate([
{
$project: {
"data_shop.records_data": {
$map: {
input: "$data_shop.records_data",
as: "data",
in: {
qty_size: {
$size: "$$data.qty"
},
qty_size_unique: {
$size: {
$setUnion: [
"$$data.qty"
]
}
}
}
}
}
}
},
{
$project: {
isRepeating: {
$cond: {
if: {
$eq: [
"$data_shop.records_data.qty_size",
"$data_shop.records_data.qty_size_unique"
]
},
then: false,
else: true
}
}
}
}
])
It will return the expected output :
[
{
"_id": 1,
"isRepeating": true
},
{
"_id": 2,
"isRepeating": false
}
]

How to perform a "NOT IN" from an array in another array in aggregate (MongoDB) [duplicate]

I have an array A in memory created at runtime and another array B saved in a mongo database. How can I efficiently get all the elements from A that are not in B?
You can assume that the array stored in mongodb is several orders of magnitude bigger than the array created at runtime, for that reason I think that obtaining the full array from mongo and computing the result would not be efficient, but I have not found any query operation in mongo that allows me to compute the result I want.
Note that the $nin operator does the opposite of what I want, i.e., it retrieves the elements from B that are not in A.
Example:
Array A, created in my appliction at runtime, is [2, 3, 4].
Array B, stored in mongodb, is [1, 3, 5, 6, 7, 10].
The result I expect is [2, 4].
The only things that "modify" the document in response are .aggregate() and .mapReduce(), where the former is the better option.
In that case you are asking for $setDifference which compares the "sets" and returns the "difference" between the two.
So representing a document with your array:
db.collection.insert({ "b": [1, 3, 5, 6, 7, 10] })
Run the aggregation:
db.collection.aggregate([{ "$project": { "c": { "$setDifference": [ [2,3,4], "$b" ] } } }])
Which returns:
{ "_id" : ObjectId("596005eace45be96e2cb221b"), "c" : [ 2, 4 ] }
If you do not want "sets" and instead want to supply an array like [2,3,4,4] then you can compare with $filter and $in instead, if you have MongoDB 3.4 at least:
db.collection.aggregate([
{ "$project": {
"c": {
"$filter": {
"input": [2,3,4,4],
"as": "a",
"cond": {
"$not": { "$in": [ "$$a", "$b" ] }
}
}
}
}}
])
Or with $filter and $anyElementTrue in earlier versions:
db.collection.aggregate([
{ "$project": {
"c": {
"$filter": {
"input": [2,3,4,4],
"as": "a",
"cond": {
"$not": {
"$anyElementTrue": {
"$map": {
"input": "$b",
"as": "b",
"in": {
"$eq": [ "$$a", "$$b" ]
}
}
}
}
}
}
}
}}
])
Where both would return:
{ "_id" : ObjectId("596005eace45be96e2cb221b"), "c" : [ 2, 4, 4 ] }
Which is of course "not a set" since the 4 was provided as input "twice" and is therefore returned "twice" as well.

Resources