Search Exact Array Values In Multiple Fields - mongodb

I have a collection which has 3 documents like below:
Collection:
{
name: "A",
arr: [1, 2, 3],
arr1: [4, 5, 6]
},
{
name: "B",
arr: [3, 7, 11],
arr1: [5, 6, 9]
},
{
name: "C",
arr: [3, 4, 5],
arr1: [7, 9, 12]
}
I want to search array below in the collection.
But all array values must be matched in fields "arr" or "arr1".
I mean array values can be in either fields but all values must be in the document.
So when I search array in the collection only second which has name:"B" and third which has name:"C" documents should be the result.
Because in the second document; first array value( 3 ) in the "arr" field and second and third array values(5 and 9) in the "arr1" field. In the third document first and second (3, 5) array values in the "arr" field and third array value (9) in the "arr1" field.
Array : [3, 5, 9]
Can you help me?

The best way to do this is using the $redact operator.
db.collection.aggregate([
{ "$redact": {
"$cond": [
{ "$setIsSubset": [ [3,5,9], { "$setUnion": [ "$arr", "$arr1" ] } ] },
"$$KEEP",
"$$PRUNE"
]}
}
])
You can also use $project with the $setUnion operator
and $match.
db.collection.aggregate([
{ "$project": { "name": 1, "arr": 1, "arr1": 1, "allvalues": { "$setUnion": [ "$arr", "$arr1" ]}}},
{ "$match": { "allvalues": { "$all": [3, 5, 9] }}}
])
Output:
{ "_id" : ObjectId("55d48fd2939d0f7d372d6dbe"), "name" : "B", "arr" : [ 3, 7, 11 ], "arr1" : [ 5, 6, 9 ] }
{ "_id" : ObjectId("55d48fd2939d0f7d372d6dbf"), "name" : "C", "arr" : [ 3, 4, 5 ], "arr1" : [ 7, 9, 12 ] }

Related

Transform data on mongo query

I have collection that has an array field and string date field. How do i transform my mongo data that looks like:
{"d" : [ 1, 2, 3, 4, 5, 6, 7 ], "date" : "21-10-2020" }
to
{"21-10-2020" : [ 1, 2, 3, 4, 5, 6, 7 ] }
using query?
Is there a way to do this transform?
You can try,
$arrayToObject convert array format of k(key) and v(value) and it will convert to object
$replaceWith replace object to root
db.collection.aggregate([
{
$replaceWith: {
$arrayToObject: [
[{ k: "$date", v: "$d" }]
]
}
}
])
Playground

MonogDB document structure: Map vs. Array for element-wise aggregations

We want to store ratings of a metric (say sales, profit) for some category (say city) in MondoDB. Example rating scale: [RED, YELLOW, GREEN], the length will be fixed. We are considering the following two document structures:
Structure 1: Ratings as an array
{
"_id": 1,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : [1, 2, 3], // frequency of RED, YELLOW, GREEN ratings, fixed length array
"profit": [4, 5, 6],
},
}
{
"_id": 2,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : [1, 2, 3], // frequency of RED, YELLOW, GREEN ratings, fixed length array
"profit": [4, 5, 6],
},
}
Structure 2: Ratings as a map
{
"_id": 1,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : { // map will always have "RED", "YELLOW", "GREEN" keys
"RED": 1,
"YELLOW": 2,
"GREEN": 3
},
"profit" : {
"RED":4,
"YELLOW": 5,
"GREEN": 6
},
},
}
{
"_id": 2,
"city": "X",
"metrics": ["sales", "profit"],
"ratings" : {
"sales" : { // map will always have "RED", "YELLOW", "GREEN" keys
"RED": 1,
"YELLOW": 2,
"GREEN": 3
},
"profit" : {
"RED":4,
"YELLOW": 5,
"GREEN": 6
},
},
}
Our use case:
aggregate ratings grouped by city and metric
we do not intend to index on the "ratings" field
So for structure 1, to aggregate ratings, I need element-wise aggregations and it seems it will likely involve unwind steps or maybe map-reduce and the resulting document would look something like this:
{
"city": "X",
"sales": [2, 4, 6]
"profit": [8, 10, 12]
}
For structure 2, I think aggregation would be relatively straightforward using the aggregation pipeline, ex (aggregating just sales):
db.getCollection('Collection').aggregate([
{
$group: {
"_id": {"city": "$city" },
"sales_RED": {$sum: "$ratings.sales.RED"},
"sales_YELLOW": {$sum: "$ratings.sales.YELLOW"},
"sales_GREEN": {$sum: "$ratings.sales.GREEN"}
}
},
{
$project: {"_id": 0, "city": "$_id.city", "sales": ["$sales_RED", "$sales_YELLOW", "$sales_GREEN"]}
}
])
Would give the following result:
{
"city": "X",
"sales": [2, 4, 6]
}
Query:
I am tending towards the second structure mainly because I am not clear on how to achieve element-wise array aggregation in MOngoDB. From what I have seen it will probably involve unwinding. The second document structure will have a larger document size because of the repeated field names for the ratings but the aggregation itself is simple. Can you please point out, based on our use case, how would they compare in terms of computational efficiency, and if I am missing any points worth considering?
I was able to achieve the aggregation with the array structure using $arrayElemAt. (However, this still involves having to specify aggregations for individual array elements, which is the same as the case for document structure 2)
db.getCollection('Collection').aggregate([
{
$group: {
"_id": {"city": "$city" },
"sales_RED": {$sum: { $arrayElemAt: [ "$ratings.sales", 0] }},
"sales_YELLOW": {$sum: { $arrayElemAt: [ "$ratings.sales", 1] }},
"sales_GREEN": {$sum: { $arrayElemAt: [ "$ratings.sales", 2] }},
}
},
{
$project: {"_id": 0, "city": "$_id.city", "sales": ["$sales_RED", "$sales_YELLOW", "$sales_GREEN"]}
}
])

How to Merge Array and Document Field in Mongo DB

I have this document in MongoDB
[
{
"locations": [5, 5],
"id": "fff"
},
{
"locations": [7, 7],
"id": "aaa"
},
{
"locations": [9, 9],
"id": "ccc"
}
]
And I want to merge the array field and string field into a field that contains a combination of them like this
{
"device": [
["fff", 5, 5],
["aaa", 7, 7],
["ccc", 9, 9]
]
}
Is it possible to do this with aggregation? Thank you.
You can use $concatArrays to merge two fields and then $group to make it two dimensional array
db.collection.aggregate([
{ "$group": {
"_id": null,
"devices": { "$push": { "$concatArrays": [["$id"], "$locations"] } }
}}
])
Output
[
{
"devices": [
["fff", 5, 5],
["aaa", 7, 7],
["ccc", 9, 9]
]
}
]

Get the set of all unique values in array field

Given the following documents:
{ "_id" : ObjectId("585901b7875bab86885cf54f"), "foo" : 24, "bar" : [ 1, 2, 5, 6 ] }
{ "_id" : ObjectId("585901be875bab86885cf550"), "foo" : 42, "bar" : [ 3, 4 ] }
I want to get all the unique values in the bar field, something like:
{"_id": "something", "bar": [1, 2, 3, 4, 5, 6]}
This is what I tried:
db.stuff.aggregate([{
$group: {
_id: null,
bar: {
$addToSet: {$each: "$bar"}
}
}
}])
But complains that $each is not a recognized operator.
This does work:
db.stuff.aggregate([{
$group: {
_id: null,
bar: {
$addToSet: "$bar"
}
}
}])
But obviously produces a wrong result:
{ "_id" : null, "bar" : [ [ 3, 4 ], [ 1, 2, 5, 6 ] ] }
EDIT
I managed to have the result I want by adding a first $unwind stage:
db.stuff.aggregate([{
$unwind: { "$bar" },
$group: {
_id: null,
bar: {
$addToSet: "$bar"
}
}
}])
=> { "_id" : null, "bar" : [ 4, 3, 5, 2, 6, 1 ] }
Is it possible at all to make it in one single pipeline stage?
The distinct() works with array field as well so will beautifully do this.
db.stuff.distinct('bar')
The aggregation framework is overkill for this and will not perform well

Find document with array containing the maximum occurence of a specific value

I have documents like the this
{ "_id" : ObjectId("5755d81e2935fe65f5d167aa"), "prices" : [ 23, 11, 2, 3, 4, 1, 232 ] },
{ "_id" : ObjectId("5755d81e2935fe65f5d167ab"), "prices" : [ 99, 3, 23, 23, 12 ] },
{ "_id" : ObjectId("5755d81e2935fe65f5d167ac"), "prices" : [ 999, 12, 3, 4, 4, 4, 4, 4, 123 ] },
{ "_id" : ObjectId("5755d81e2935fe65f5d167ad"), "prices" : [ 24, 3, 4, 5, 6, 7, 723 ] }
and I want to find the document with array 'prices' containing the highest amount of digit 4, which in my case is the third document. Is there any way to query it?
Starting from MongoDB 3.2, we can $project our documents and use the $size and the $filter operator to return the "count" of the number 4 in each array. From there we need to $group using that "value" and use the $push accumulator operator to return an array of the documents that have same "maximum". Next you $sort your documents by _id and use $limit to return the documents with the maximum occurrence of 4.
db.collection.aggregate(
[
{ "$project": {
"prices": 1,
"counter": {
"$size": {
"$filter": {
"input": "$prices",
"as": "p",
"cond": { "$eq": [ "$$p", 4 ] }
}
}
}
}},
{ "$group": {
"_id": "$counter",
"docs": { "$push": "$$ROOT" }
}},
{ "$sort": { "_id": -1 } },
{ "$limit": 1 }
]
)