Mongodb - Array indexed by string in $addToSet operator - mongodb

Suppose we have these two documents:
{
"_id" : ObjectId("5f3cdd1d0fefeba343ff3093"),
"country" : "C1",
"time" : "1994",
"value" : NumberInt(100),
"type" : "type1",
"origin" : "O1"
}
{
"_id" : ObjectId("5f3cdd1d0fefeba343ff3094"),
"country" : "C1",
"time" : "1994",
"value" : NumberInt(200),
"type" : "type1",
"origin" : "O2"
}
I want to retrieve the aggregation with the origin array indexed by strings (the value of "type"); expected output:
{
"_id" : {
"country" : "C1",
"time" : "1994"
},
"TOT" : NumberInt(300),
"count" : 2.0,
"origin" : [
"O1": NumberInt(100),
"O2": NumberInt(200)
]
}
Here we have the type of the "origin" array as {[key: string]: number}.
With the following query, the origin array is instead indexed by numbers:
use local;
db.getCollection("test_collection").aggregate(
[
{
"$match" : {
"type" : {
"$in" : [
"type1"
]
}
}
},
{
"$group" : {
"_id" : {
"country" : "$country",
"time" : "$time"
},
"TOT" : {
"$sum" : "$value"
},
"count" : {
"$sum" : 1.0
},
"origin" : {
"$addToSet" : "$value"
}
}
}
],
{
"allowDiskUse" : false
}
);

You can try $arrayToObject after adding in origin,
use local;
db.getCollection("test_collection").aggregate([
{ "$match": { "type": { "$in": ["type1"] } } },
{
"$group": {
"_id": {
"country": "$country",
"time": "$time"
},
"TOT": { "$sum": "$value" },
"count": { "$sum": 1.0 },
// add object like this
"origin": {
"$addToSet": {
k: "$origin",
v: "$value"
}
}
}
},
// add this
{ $addFields: { origin: { $arrayToObject: "$origin" } } }
],
{ "allowDiskUse": false }
)
Playground

Related

How to get percentage total of data with group by date in MongoDB

How to get percentage total of data with group by date in MongoDB ?
Link example : https://mongoplayground.net/p/aNND4EPQhcb
I have some collection structure like this
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4b"),
"date" : "2019-05-03T10:39:53.108Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4c"),
"date" : "2019-05-03T10:39:53.133Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4d"),
"date" : "2019-05-03T10:39:53.180Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4e"),
"date" : "2019-05-03T10:39:53.218Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
And I have query in mongodb to get data of collection, how to get percentage of total data. in bellow example query to get data :
db.name_collection.aggregate(
[
{ "$match": {
"update_at": { "$gte": "2019-11-04T00:00:00.0Z", "$lt": "2019-11-06T00:00:00.0Z"},
"id": { "$in": [166] }
} },
{
"$group" : {
"_id": {
$substr: [ '$update_at', 0, 10 ]
},
"count" : {
"$sum" : 1
}
}
},
{
"$project" : {
"_id" : 0,
"date" : "$_id",
"count" : "$count"
}
},
{
"$sort" : {
"date" : 1
}
}
]
)
and this response :
{
"date" : "2019-11-04",
"count" : 39
},
{
"date" : "2019-11-05",
"count" : 135
}
how to get percentage data total from key count ? example response to this :
{
"date" : "2019-11-04",
"count" : 39,
"percentage" : "22%"
},
{
"date" : "2019-11-05",
"count" : 135,
"percentage" : "78%"
}
You have to group by null to get total count and then use $map to calculate the percentage. $round will be a useful operator in such case. Finally you can $unwind and $replaceRoot to get back the same number of documents:
db.collection.aggregate([
// previous aggregation steps
{
$group: {
_id: null,
total: { $sum: "$count" },
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: "$docs",
in: {
date: "$$this.date",
count: "$$this.count",
percentage: { $concat: [ { $toString: { $round: { $multiply: [ { $divide: [ "$$this.count", "$total" ] }, 100 ] } } }, '%' ] }
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: { newRoot: "$docs" }
}
])
Mongo Playground

Group and Merge array of objects

I am struggling around with the aggregation pipeline feature from MongoDB.
So far the output for one result looks like this:
{
"type": "inbound",
"sender": "postAG",
"receiver": "maxMusterMan",
"datetime": "20191125",
"info": [
{
"q": "A",
"value": "5",
"name": null,
"plz": 1234
},
{
"q": "B",
"value": "AS",
"name": "ABS",
"plz": null
},
{
"q": "A",
"value": "5",
"name": "aa",
"plz": null
},
... more objects
]
}
The final result should look like:
{
"type": "inbound",
"sender": "postAG",
"receiver": "maxMusterMan",
"datetime": "20191125",
"info": [
{
"q": "A",
"value": "0",
"name": "aa",
"plz": 1234
},
{
"q": "B",
"value": "AS",
"name": "ABS"
}
]
}
So in a nutshell, I want to group the values from the array field info by the "q" field and merge the objects (newer one overwrites the old value).
Further I would like to remove all the values with value "" or null;
There are more fields in the real payload, so I would like to avoid to add a $cond for each field of the object.
Some approaches so far from my side:
for the cleanup, use a UDF, but this is not possible in the pipeline.
use map-reduce for the group and merge, not available in the pipeline.
Please consider that the input file is the output from the several pipeline steps.
So I can not just use map-reduce alone, first I need the pipeline too.
My idea was to create two views, first will do the pipeline stuff and second map-reduce, is this a good solution?
Thx
Andreas
I didn't really understand from your explanation if you can or cannot use map-reduce.
However assuming you can't and you have to 'concat' the pipelines there is no 'generic' workaround for multiple fields - you have to create a condition for each in the pipeline.
With that said here is a working pipeline:
db.collection.aggregate(
[
{
"$unwind" : "$info"
},
{
"$group" : {
"_id" : "$info.q",
"type" : {
"$first" : "$type"
},
"sender" : {
"$first" : "$sender"
},
"receiver" : {
"$first" : "$receiver"
},
"datetime" : {
"$first" : "$datetime"
},
"values" : {
"$push" : "$info.value"
},
"names" : {
"$push" : "$info.name"
},
"plz" : {
"$push" : "$info.plz"
}
}
},
{
"$project" : {
"_id" : 1.0,
"type" : 1.0,
"sender" : 1.0,
"receiver" : 1.0,
"datetime" : 1.0,
"values" : {
"$filter" : {
"input" : "$values",
"as" : "curr",
"cond" : {
"$or" : [
{
"$ne" : [
"$$curr",
null
]
},
{
"$ne" : [
"$$curr",
""
]
}
]
}
}
},
"names" : {
"$filter" : {
"input" : "$names",
"as" : "curr",
"cond" : {
"$or" : [
{
"$ne" : [
"$$curr",
null
]
},
{
"$ne" : [
"$$curr",
""
]
}
]
}
}
},
"plz" : {
"$filter" : {
"input" : "$plz",
"as" : "curr",
"cond" : {
"$or" : [
{
"$ne" : [
"$$curr",
null
]
},
{
"$ne" : [
"$$curr",
""
]
}
]
}
}
}
}
},
{
"$project" : {
"sender" : 1.0,
"receiver" : 1.0,
"datetime" : 1.0,
"type" : 1.0,
"_id" : 1.0,
"value" : {
"$cond" : {
"if" : {
"$gt" : [
{
"$size" : "$values"
},
0.0
]
},
"then" : {
"$arrayElemAt" : [
"$values",
-1.0
]
},
"else" : null
}
},
"name" : {
"$cond" : {
"if" : {
"$gt" : [
{
"$size" : "$names"
},
0.0
]
},
"then" : {
"$arrayElemAt" : [
"$names",
-1.0
]
},
"else" : null
}
},
"plz" : {
"$cond" : {
"if" : {
"$gt" : [
{
"$size" : "$plz"
},
0.0
]
},
"then" : {
"$arrayElemAt" : [
"$plz",
-1.0
]
},
"else" : null
}
}
}
},
{
"$addFields" : {
"infoArray" : [
{
"k" : "type",
"v" : "$_id"
},
{
"k" : "value",
"v" : "$value"
},
{
"k" : "name",
"v" : "$name"
},
{
"k" : "plz",
"v" : "$plz"
}
]
}
},
{
"$addFields" : {
"info" : {
"$arrayToObject" : {
"$filter" : {
"input" : "$infoArray",
"as" : "curr",
"cond" : {
"$ne" : [
"$$curr.v",
null
]
}
}
}
}
}
},
{
"$group" : {
"_id" : null,
"type" : {
"$first" : "$type"
},
"sender" : {
"$first" : "$sender"
},
"receiver" : {
"$first" : "$receiver"
},
"datetime" : {
"$first" : "$datetime"
},
"info" : {
"$push" : "$info"
}
}
}
]
)

MongoDB Keep path where a criteria is met

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)

mongodb aggregation $group and then $push a object

this is my data :
> db.bookmarks.find({"userId" : "56b9b74bf976ab70ff6b9999"}).pretty()
{
"_id" : ObjectId("56c2210fee4a33579f4202dd"),
"userId" : "56b9b74bf976ab70ff6b9999",
"items" : [
{
"itemId" : "28",
"timestamp" : "2016-02-12T18:07:28Z"
},
{
"itemId" : "29",
"timestamp" : "2016-02-12T18:07:29Z"
},
{
"itemId" : "30",
"timestamp" : "2016-02-12T18:07:30Z"
},
{
"itemId" : "31",
"timestamp" : "2016-02-12T18:07:31Z"
},
{
"itemId" : "32",
"timestamp" : "2016-02-12T18:07:32Z"
},
{
"itemId" : "33",
"timestamp" : "2016-02-12T18:07:33Z"
},
{
"itemId" : "34",
"timestamp" : "2016-02-12T18:07:34Z"
}
]
}
I want to have something like (actually i hope the _id can become userId too) :
{
"_id" : "56b9b74bf976ab70ff6b9999",
"items" : [
{ "itemId": "32", "timestamp": "2016-02-12T18:07:32Z" },
{ "itemId": "31", "timestamp": "2016-02-12T18:07:31Z" },
{ "itemId": "30", "timestamp": "2016-02-12T18:07:30Z" }
]
}
What I have now :
> db.bookmarks.aggregate(
... { $match: { "userId" : "56b9b74bf976ab70ff6b9999" } },
... { $unwind: '$items' },
... { $sort: { 'items.timestamp': -1} },
... { $skip: 2 },
... { $limit: 3},
... { $group: { '_id': '$userId' , items: { $push: '$items.itemId' } } }
... ).pretty()
{ "_id" : "56b9b74bf976ab70ff6b9999", "items" : [ "32", "31", "30" ] }
i tried to read the document in mongo and find out i can $push, but somehow i cannot find a way to push such object, which is not defined anywhere in the whole object. I want to have the timestamp also.. but i don't know how should i modified the $group (or others??) to do so. thanks for helping!
This code, which I tested in the MongoDB 3.2.1 shell, should give you the output format that you want:
> db.bookmarks.aggregate(
{ "$match" : { "userId" : "Ursula" } },
{ "$unwind" : "$items" },
{ "$sort" : { "items.timestamp" : -1 } },
{ "$skip" : 2 },
{ "$limit" : 3 },
{ "$group" : { "_id" : "$userId", items: { "$push" : { "myPlace" : "$items.itemId", "myStamp" : "$items.timestamp" } } } } ).pretty()
Running the above will produce this output:
{
"_id" : "Ursula",
"items" : [
{
"myPlace" : "52",
"myStamp" : ISODate("2016-02-13T18:07:32Z")
},
{
"myPlace" : "51",
"myStamp" : ISODate("2016-02-13T18:07:31Z")
},
{
"myPlace" : "50",
"myStamp" : ISODate("2016-02-13T18:07:30Z")
}
]
}
In MongoDB version 3.2.x, you can also use the $out operator in the very last stage of the aggregation pipeline, and have the output of the aggregation query written to a collection. Here is the code I used:
> db.bookmarks.aggregate(
{ "$match" : { "userId" : "Ursula" } },
{ "$unwind" : "$items" },
{ "$sort" : { "items.timestamp" : -1 } },
{ "$skip" : 2 },
{ "$limit" : 3 },
{ "$group" : { "_id" : "$userId", items: { "$push" : { "myPlace" : "$items.itemId", "myStamp" : "$items.timestamp" } } } },
{ "$out" : "ursula" } )
This gives me a collection named "ursula":
> show collections
ursula
and I can query that collection:
> db.ursula.find().pretty()
{
"_id" : "Ursula",
"items" : [
{
"myPlace" : "52",
"myStamp" : ISODate("2016-02-13T18:07:32Z")
},
{
"myPlace" : "51",
"myStamp" : ISODate("2016-02-13T18:07:31Z")
},
{
"myPlace" : "50",
"myStamp" : ISODate("2016-02-13T18:07:30Z")
}
]
}
>
Last of all, this is the input document I used in the aggregation query. You can compare this document to how I coded the aggregation query to see how I built the new items array.
> db.bookmarks.find( { "userId" : "Ursula" } ).pretty()
{
"_id" : ObjectId("56c240ed55f2f6004dc3b25c"),
"userId" : "Ursula",
"items" : [
{
"itemId" : "48",
"timestamp" : ISODate("2016-02-13T18:07:28Z")
},
{
"itemId" : "49",
"timestamp" : ISODate("2016-02-13T18:07:29Z")
},
{
"itemId" : "50",
"timestamp" : ISODate("2016-02-13T18:07:30Z")
},
{
"itemId" : "51",
"timestamp" : ISODate("2016-02-13T18:07:31Z")
},
{
"itemId" : "52",
"timestamp" : ISODate("2016-02-13T18:07:32Z")
},
{
"itemId" : "53",
"timestamp" : ISODate("2016-02-13T18:07:33Z")
},
{
"itemId" : "54",
"timestamp" : ISODate("2016-02-13T18:07:34Z")
}
]
}

mongodb aggregation match multiple $and on the same field

i have a document like this :
{
"ExtraFields" : [
{
"value" : "print",
"fieldID" : ObjectId("5535627631efa0843554b0ea")
},
{
"value" : "14",
"fieldID" : ObjectId("5535627631efa0843554b0eb")
},
{
"value" : "POLYE",
"fieldID" : ObjectId("5535627631efa0843554b0ec")
},
{
"value" : "30",
"fieldID" : ObjectId("5535627631efa0843554b0ed")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627631efa0843554b0ee")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627731efa0843554b0ef")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627831efa0843554b0f0")
},
{
"value" : "42",
"fieldID" : ObjectId("5535627831efa0843554b0f1")
},
{
"value" : "30",
"fieldID" : ObjectId("5535627831efa0843554b0f2")
},
{
"value" : "14",
"fieldID" : ObjectId("5535627831efa0843554b0f3")
},
{
"value" : "19",
"fieldID" : ObjectId("5535627831efa0843554b0f4")
}
],
"id" : ObjectId("55369e60733e4914550832d0"), "title" : "A product"
}
what i want is to match one or more sets from the ExtraFields array. For example, all the products that contain the values print and 30. Since a value may be found in more than one fieldID (like 0 or true) we need to create a set like
WHERE (fieldID : ObjectId("5535627631efa0843554b0ea"), value : "print")
Where i'm having problems is when querying more than one fields. The pipeline i came up with is :
db.products.aggregate([
{'$unwind': '$ExtraFields'},
{
'$match': {
'$and': [{
'$and': [{'ExtraFields.value': {'$in': ["A52A2A"]}}, {
'ExtraFields.fieldID': ObjectId("5535627631efa0843554b0ea")
}]
}
,
{
'$and': [{'ExtraFields.value': '14'}, {'ExtraFields.fieldID': ObjectId("5535627631efa0843554b0eb")}]
}
]
}
},
]);
This returns zero results, but this is what i want to do in theory. Match all items that contain set 1 AND all that contain set 2.
The end result should look like a faceted search output :
[
{
"_id" : {
"values" : "18",
"fieldID" : ObjectId("5535627831efa0843554b0f3")
},
"count" : 2
},
{
"_id" : {
"values" : "33",
"fieldID" : ObjectId("5535627831efa0843554b0f2")
},
"count" : 1
}
]
Any ideas?
You could try the following aggregation pipeline
db.products.aggregate([
{
"$match": {
"ExtraFields.value": { "$in": ["A52A2A", "14"] },
"ExtraFields.fieldID": {
"$in": [
ObjectId("5535627631efa0843554b0ea"),
ObjectId("5535627631efa0843554b0eb")
]
}
}
},
{
"$unwind": "$ExtraFields"
},
{
"$match": {
"ExtraFields.value": { "$in": ["A52A2A", "14"] },
"ExtraFields.fieldID": {
"$in": [
ObjectId("5535627631efa0843554b0ea"),
ObjectId("5535627631efa0843554b0eb")
]
}
}
},
{
"$group": {
"_id": {
"value": "$ExtraFields.value",
"fieldID": "$ExtraFields.fieldID"
},
"count": {
"$sum": 1
}
}
}
])
With the sample document provided, this gives the output:
/* 1 */
{
"result" : [
{
"_id" : {
"value" : "14",
"fieldID" : ObjectId("5535627631efa0843554b0eb")
},
"count" : 1
}
],
"ok" : 1
}