Mongodb Version 2.6.9
I'm attempting to count the total a company has been involved in a messaging interaction. I'm able to get one side of the interaction using the aggregate $group, but I've come up empty on essentially looking at the two fields and aggregating those together for each unique company ID.
The sender_id and receiver_id relate to the same company Id's.
{ "_id" : a, "sender_id" : 1, "receiver_id" : 2, payload: "data" }
{ "_id" : b, "sender_id" : 2, "receiver_id" : 5, payload: "data" }
{ "_id" : c, "sender_id" : 2, "receiver_id" : 4, payload: "data" }
{ "_id" : d, "sender_id" : 3, "receiver_id" : 2, payload: "data" }
{ "_id" : e, "sender_id" : 4, "receiver_id" : 1, payload: "data" }
Using the above data structure, I attempting to produce a result set similar to
{ "_id" : 1, count: 2}
{ "_id" : 2, count: 4}
{ "_id" : 3, count: 1}
{ "_id" : 4, count: 2}
{ "_id" : 5, count: 1}
where for example Company 2 was involved in messages a, b, c, d.
Your options are limited in 2.6 pipeline. You can try below pipeline.
$group with $push to create single value array for both sender_id and receiver_id.
$project with $setUnion to merge ids into single array.
$unwind and $group to count the occurrences.
db.collection.aggregate({
"$group": {
"_id": "$_id",
"sender_id": {
"$push": "$sender_id"
},
"receiver_id": {
"$push": "$receiver_id"
}
}
}, {
"$project": {
"id": {
"$setUnion": ["$sender_id", "$receiver_id"]
}
}
}, {
"$unwind": "$id"
}, {
"$group": {
"_id": "$id",
"count": {
"$sum": 1
}
}
})
You can use below pipeline for newer versions. Use [] to create array.
db.collection.aggregate({
$project: {
id: ["$sender_id", "$receiver_id"]
}
}, {
$unwind: "$id"
}, {
$group: {
_id: "$id",
count: {
$sum: 1
}
}
})
Related
I have a MongoDB collection as below. And I want to get the min\max\avg\count of the xxx field inside all documents which $match: { "Parsed.FileId": "421462559", "Parsed.MessageId": "123" }
Note that Fields of each document only contains one single field with SchemaName = xxx
Is it possible with MongoDB aggregation (or other feature) and how?
{
"_id" : NumberLong(409),
"Parsed" : {
"FileId" : "421462559",
"MessageId": "123",
"Fields" : [
{
"SchemaName" : "xxx",
"Type" : 0,
"Value" : 6
},
{
"SchemaName" : "yyy",
"Type" : 0,
"Value" : 5
}
]
}
},
{
"_id" : NumberLong(510),
"Parsed" : {
"FileId" : "421462559",
"MessageId": "123",
"Fields" : [
{
"SchemaName" : "xxx",
"Type" : 0,
"Value" : 10
},
{
"SchemaName" : "yyy",
"Type" : 0,
"Value" : 20
}
]
}
}
For example collection above, I expect to get the result for field xxx as:
{
count: 2,
min: 6,
max: 10,
avg: 8
}
You can use below aggregation
Basically you need to first $unwind the nested array and then have to use $group with the corresponding accumulator i.e. min
max
$sum
$avg
db.collection.aggregate([
{ "$match": { "Parsed.Fields.SchemaName": "xxx" }},
{ "$unwind": "$Parsed.Fields" },
{ "$match": { "Parsed.Fields.SchemaName": "xxx" }},
{ "$group": {
"_id": null,
"count": { "$sum": 1 },
"max": { "$max": "$Parsed.Fields.Value" },
"min": { "$min": "$Parsed.Fields.Value" },
"avg": { "$avg": "$Parsed.Fields.Value" }
}}
])
Can someone help me with the query for sorting an array by date in ascending order?
I have tried the below query but the sorting is not happening as expected,
db.getCollection(xyz).aggregate([{
$match: {
"_id":{$in:[{"a" : "NA","b" : "HXYZ","c" : "12345","d" : "AA"}]}
}
},{
$sort: {'bal.date': 1}
},
{ $project: {
balances: { $slice: ["$bal",2]}
}
}
])
My collection:
/* 1 */
{
"_id" : {
"a" : "NA",
"b" : "HXYZ",
"c" : "12345",
"d" : "AA"
},
"bal" : [
{
"type" : "E",
"date" : "2015-08-02"
},
{
"type" : "E",
"date" : "2015-08-01"
},
{
"type" : "E",
"date" : "2015-07-07"
}
]
}
Please help me what is the problem in the above query.
Thanks in advance.
You are mixing $match with $sort stage
Correct syntax to used aggregation pipeline stages
db.collection.aggregate([
{ "$match": {
"_id": {
"$eq": {
"a": "NA",
"b": "HXYZ",
"c": "12345",
"d": "AA"
}
}
}},
{ "$unwind": "$bal" },
{ "$sort": { "bal.date": 1 }},
{ "$group": {
"_id": "$_id",
"bal": {
"$push": "$bal"
}
}}
])
From the looks of it, you're saving the date as String, the sort() function will sort the dates as Strings which will not give you the order you're expecting.
You can either run a script that will convert the bal.date field to Date() format and then sort() will work automatically, or you can do the converting + sorting server side.
Suppose my collection consists of items that looks like this:
{
"items" : [
{
"item_id": 1,
"item_field": 10
},
{
"item_id": 2,
"item_field": 15
},
{
"item_id": 3,
"item_field": 3
},
]
}
Can I somehow select the entry of items with the lowest value of item_field, in this case the one with item_id 3?
I'm ok with using the aggregation framework. Bonus point if you can give me the code for the C# driver.
You can use $reduce expression in the following way.
The below query will set the initialValue to the first element of $items.item_field and followed by $lt comparison on the item_field and if true set $$this to $$value, if false keep the previous value and $reduce all the values to find the minimum element and $project to output min item.
db.collection.aggregate([
{
$project: {
items: {
$reduce: {
input: "$items",
initialValue:{
item_field:{
$let: {
vars: { obj: { $arrayElemAt: ["$items", 0] } },
in: "$$obj.item_field"
}
}
},
in: {
$cond: [{ $lt: ["$$this.item_field", "$$value.item_field"] }, "$$this", "$$value" ]
}
}
}
}
}
])
You can use $unwind to seperate items entries.
Then $sort by item_field asc and then $group.
db.coll.find().pretty()
{
"_id" : ObjectId("58edec875748bae2cc391722"),
"items" : [
{
"item_id" : 1,
"item_field" : 10
},
{
"item_id" : 2,
"item_field" : 15
},
{
"item_id" : 3,
"item_field" : 3
}
]
}
db.coll.aggregate([
{$unwind: {path: '$items', includeArrayIndex: 'index'}},
{$sort: { 'items.item_field': 1}},
{$group: {_id: '$_id', item: {$first: '$items'}}}
])
{ "_id" : ObjectId("58edec875748bae2cc391722"), "item" : { "item_id" : 3, "item_field" : 3 } }
We can get expected result using following query
db.testing.aggregate([{$unwind:"$items"}, {$sort: { 'items.item_field': 1}},{$group: {_id: "$_id", minItem: {$first: '$items'}}}])
Result is
{ "_id" : ObjectId("58edf28c73fed29f4b741731"), "minItem" : { "item_id" : 3, "item_field" : 3 } }
{ "_id" : ObjectId("58edec3373fed29f4b741730"), "minItem" : { "item_id" : 3, "item_field" : 3 } }
Say for every document of a collection, it has an string array. how could I count the repetitive time of every element of the array in all this collection? Right now I can find all the distinct element, but then Map Reduce function is a little tricky that I haven't fully understood.
Doc A
{
_id:
name:
actors: ["a", "b", "c"]
}
Doc B
{
_id:
name:
actors: ["a", "d"]
}
Doc C
{
_id:
name:
actors: ["a", "c", "f"]
}
I wanne get a statistic result with a:3 b:1 c:2 d:1 f:1.
An alternative route that you could take is the aggregation framework. Considering the above collection as an example
Populate test collection:
db.collection.insert([
{ "_id" : 1, "name" : "ABC1", "actors": ["a", "b", "c"] },
{ "_id" : 2, "name" : "ABC2", "actors" : ["a", "d"] },
{ "_id" : 3, "name" : "XYZ1", "actors" : ["a", "c", "f"] }
])
Using MongoDB 3.4.4 or newer:
db.collection.aggregate([
{ "$unwind" : "$actors" },
{ "$group": { "_id": "$actors", "count": { "$sum": 1} } },
{ "$group": {
"_id": null,
"counts": {
"$push": {
"k": "$_id",
"v": "$count"
}
}
} },
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$counts" }
} }
])
Output
{
a: 3,
b: 1,
c: 2,
d: 1,
f: 1
}
Using MongoDB 3.2 and below:
The following aggregation pipeline operation uses the $unwind stage to output a document for each element in the actors array and the $group stage to group the documents by the value in the actors array then
counts the number of documents per each group (which gives the occurrence of the array elements as a group) by way of the $sum operator:
db.collection.aggregate([
{ "$unwind" : "$actors" },
{ "$group": { "_id": "$actors", "count": { "$sum": 1} } }
])
The operation returns the following results which would be a close match to your expectations but won't give you the documents as key/value pair:
/* 0 */
{
"result" : [
{
"_id" : "f",
"count" : 1
},
{
"_id" : "d",
"count" : 1
},
{
"_id" : "c",
"count" : 2
},
{
"_id" : "b",
"count" : 1
},
{
"_id" : "a",
"count" : 3
}
],
"ok" : 1
}
This is my database/document.
Running:
db.Students.find().pretty()
Result is:
{
"_id" : 1,
"scores" : [
{
"attempt" : 1,
"score" : 5
},
{
"attempt" : 2,
"score" : 10
},
{
"attempt" : 3,
"score" : 7
},
{
"attempt" : 4,
"score" : 9
}
]
}
How to display the scores in descending order using $sort ?
Well you cannot do that using .find() as any .sort() modifier there is actually sorting the documents and not the contents of your array. But you can do that using .aggregate():
db.Students.aggregate([
// Unwind the array to de-normalize
{ "$unwind": "$scores" },
// Sort the documents with the scores descending
{ "$sort": { "_id": 1, "scores.score": -1 } },
// Group back to an array
{ "$group": {
"_id": "$_id",
"scores": { "$push": "$scores" }
}}
])
So once all the elements are "de-normalized" into individual documents, the $sort pipeline stage takes care of re-arranging the order.