MongoDB Aggregation Framework: Getting $unwind error when using $group - mongodb

I have a document structure as follows:
{
"_id" : NumberLong("80000000012"),
[...]
"categories" : [{
"parent" : "MANUFACTURER",
"category" : "Chevrolet"
}, {
"parent" : "MISCELLANEOUS",
"category" : "Miscellaneous"
}],
[...]
}
I am trying to get a distinct list of all 'category' fields for each 'parent' field. I was trying to utilize the aggregation framework to do this with the following query:
db.posts_temp.aggregate(
{$unwind : '$categories'},
{$match : {'categories.parent' : 'MISCELLANEOUS'}},
{$project : {
'_id' : 0,
parent : '$categories.parent',
category : '$categories.category'
}
},
{
$group : {
_id : '$parent',
category : {$addToSet : '$category'}
}
}
);
Running this query returns the following error:
{
"errmsg" : "exception: $unwind: value at end of field path must be an array",
"code" : 15978,
"ok" : 0
}
This seems to be tied to the group portion of the query, because, when I remove it, the query runs correctly, but, obviously, the data is not where I want it to be.

I just tried executing the above aggregation query on my mongo instance. Here are my 3 documents each with a key of categories that has an array of two nested documents.
Here is my data:
{
"_id" : ObjectId("512d5252b748191fefbd4698"),
"categories" : [
{
"parent" : "MANUFACTURER",
"category" : "Chevrolet"
},
{
"parent" : "MISCELLANEOUS",
"category" : "Miscellaneous"
}
]
}
{
"_id" : ObjectId("512d535cb748191fefbd4699"),
"categories" : [
{
"parent" : "MANUFACTURER",
"category" : "Chevrolet"
},
{
"parent" : "MISCELLANEOUS",
"category" : "Pickup"
}
]
}
{
"_id" : ObjectId("512d536eb748191fefbd469a"),
"categories" : [
{
"parent" : "MANUFACTURER",
"category" : "Toyota"
},
{
"parent" : "MISCELLANEOUS",
"category" : "Miscellaneous"
}
]
}
Here is the aggregation query of yours that I ran:
db.posts_temp.aggregate( {$unwind:'$categories'} , {$match: {'categories.parent':'MISCELLANEOUS'}}, {$project:{'_id':0, parent: '$categories.parent', category:'$categories.category'}}, {$group:{_id:'$parent', category:{$addToSet:'$category'}}})
Here is the result:
{
"result" : [
{
"_id" : "MISCELLANEOUS",
"category" : [
"Pickup",
"Miscellaneous"
]
}
],
"ok" : 1
}
Let me know if there some discrepancies between my data and yours.
CSharpie

Related

Mongodb Query to get the nth document

I need to create a query in mongodb that needs to return the SECOND TO THE LAST document. I am planning to use $group for this query but i dont know what aggregation function to use. I only know $first and $last.
I have an example collection below and also include the expected output. Thank you!
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84be"),
"userid" : ObjectId("5ffe702d59a9205db81fcb69"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),
"userid" : ObjectId("5ffe644f9493e05db9245192"),
"action" : "EDITPROFILE"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bc"),
"userid" : ObjectId("5ffe64949493e05db9245197"),
"action" : "DELETETRANSACTION"
} ]
"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bb"),
"userid" : ObjectId("5ffe64b69493e05db924519b"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84ba"),
"userid" : ObjectId("5ffe65419493e05db92451d4"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b9"),
"userid" : ObjectId("5ffe65689493e05db92451d9"),
"action" : "CHANGEACCESS"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),
"userid" : ObjectId("5ffe65819493e05db92451dd"),
"action" : "DELETETRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b7"),
"userid" : ObjectId("5ffe65df9493e05db92451f3"),
"action" : "EDITPROFILE",
]
OUTPUT:
{"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),"userid" : ObjectId("5ffe644f9493e05db9245192"),"action" : "EDITPROFILE"},
{"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),"userid" : ObjectId("5ffe65819493e05db92451dd"),"action" : "DELETETRANSACTION"}
You can't have two _id keys in one single object.
I've made the parent object's id to _parentId you can give it's a name anything you want except _id
Aggregation:
db.collection.aggregate([
{
$unwind: "$auditlogs"
},
{
"$project": {
"_parentId": "$_id",
"_id": "$auditlogs._id",
"action": "$auditlogs.action",
"userid": "$auditlogs.userid",
}
}
])
Playground
You can slice the array by -2 to get the last two item, then by 1 to get first one. Therefore, the array will be left the second to the last. Finally, unwind auditlogs so it can be changed from array to object which is structure that you want.
db.collection.aggregate([
{
$project: { auditlogs : { $slice: [ "$auditlogs", -2 ] } }
},
{
$project: { auditlogs : { $slice: [ "$auditlogs", 1 ] } }
},
{
$unwind: "$auditlogs"
}
])

Mongodb sort by sum of keys

I have a json document
{
{
"_id" : ObjectId("5715c4bbac530eb3018b456a"),
"content_id" : "5715c4bbac530eb3018b4569",
"views" : NumberLong(200),
"likes" : NumberLong(100),
"comments" : NumberLong(0)
},
{
"_id" : ObjectId("5715c4bbac530eb3018b4568"),
"content_id" : "5715c4bbac530eb3018b4567",
"views" : NumberLong(300),
"likes" : NumberLong(200),
"comments" : NumberLong(0)
},
{
"_id" : ObjectId("5715c502ac530ee5018b4956"),
"content_id" : "5715c502ac530ee5018b4955",
"views" : NumberLong(500),
"likes" : NumberLong(0),
"comments" : NumberLong(200)
}
}
How can we sort the document order by SUM("views", "likes", "comments")
something like in mysql
SELECT SUM(key1, key2, key3) AS key
FROM document
ORDER BY key
Thanks in advance.
First do a projection to obtain the sum of all the likes, views and comments, then sort based on that sum. I am considering group by content_id if is needed in the second snippet
db.test.aggregate([
{ $project : { "_id" : "$content_id", "total" : { $add : [ "$likes", "$views", "$comments"]}}},
{ $sort : { "total" : 1 }}
])
If you need a group operation if content_id can be duplicated
db.test.aggregate([
{ $project : { "_id" : "$content_id", "total" : { $add : [ "$likes", "$views", "$comments"]}}},
{ $group : { "_id" : "$_id" , totalPerId : { $sum : "$total" }}},
{ $sort : { "total" : 1 }}
])
Based on your test data, you will get:
{ "_id" : "5715c502ac530ee5018b4955", "totalPerId" : NumberLong(700) }
{ "_id" : "5715c4bbac530eb3018b4567", "totalPerId" : NumberLong(500) }
{ "_id" : "5715c4bbac530eb3018b4569", "totalPerId" : NumberLong(300) }

MongoDB aggregation and projection issue

helpful people of StackOverflow!
I'm in the process of learning how to work with MongoDB, and am currently stuck with one particular problem.
I'm building a guitar tabs app, working only with an "artist" base document. All other data are subdocuments. Depending on the accessed functionality (e.g: search, list tabs by artist, view single tab), I aggregate and project my documents accordingly.
However, I can't get one projection to work as I want.
Given the following data:
{
"artist" : "Jeff Buckley",
"songs" : [
{
"name" : "Grace",
"tabs" : [
{
"version" : 1,
"tab" : "...",
"tuning" : "DADGBe"
},
{
"version" : 2,
"tab" : "...",
"tuning" : "DADGBe"
}
]
},
{
"name" : "Last Goodbye",
"tabs" : [
{
"version" : 1,
"tab" : "...",
"tuning" : "DGDGBD"
},
{
"version" : 2,
"tab" : "...",
"tuning" : "EADGBe"
}
]
}
]
}
I want to aggregate it the following way for a list view:
{
"artist" : "Jeff Buckley",
"tabs" : [
{
"song" : "Grace",
"version" : 1
},
{
"song" : "Grace",
"version" : 2
},
{
"song" : "Last Goodbye",
"version" : 1
},
{
"song" : "Last Goodbye",
"version" : 2
},
]
}
I tried it with the following projection:
db.tabs.aggregate(
[
{
$project : {
artist : 1,
tabs.song : "$songs.name",
tabs.version : "$songs.tabs.version"
}
}
]
)
But instead I got:
{
"artist" : "Jeff Buckley",
"tabs" : {
"version" : [[2,1],[2,1]],
"song" : ["Grace","Last Goodbye"]
}
}
Can anyone point me in the right direction?
Thanks!
your aggregation query not correct $project only affect your json document keys
your aggretion query like this
db.tabs.aggregate(
{$unwind : "$songs"},
{$unwind : "$songs.tabs"},
{$group : {
_id:"$artist",
tabs:{$push : {song : "$songs.name",version:"$songs.tabs.version"}}}},
{$project : {
tabs:"$tabs",
artist:"$_id",
_id:0}}
).pretty()

Mongodb aggregate match array item with child array item

I would like to find documents that contains specific values in a child array.
This is an example document:
{
"_id" : ObjectId("52e9658e2a13df5be22cf7dc"),
"desc" : "Something somethingson",
"imageurl" : "http://",
"tags" : [
{
"y" : 29.3,
"brand" : "52d2cecd0bd1bd844d000018",
"brandname" : "Zara",
"type" : "Bow Tie",
"x" : 20,
"color" : "52d50c19f8f8ca8448000001",
"number" : 0,
"season" : 0,
"cloth" : "52d50d57f8f8ca8448000006"
},
{
"y" : 29.3,
"brand" : "52d2cecd0bd1bd844d000018",
"brandname" : "Zara",
"type" : "Bow Tie",
"x" : 20,
"color" : "52d50c19f8f8ca8448000001",
"number" : 0,
"season" : 0,
"cloth" : "52d50d57f8f8ca8448000006"
}
],
"user_id" : "52e953942a13df5be22cf7af",
"username" : "Thompson",
"created" : 1386710259971,
"occasion" : "ID",
"sex" : 0
}
The query I would like to do should look something like this:
db.posts.aggregate([
{$match: {tags.color:"52d50c19f8f8ca8448000001", tags.brand:"52d2cecd0bd1bd844d000018", occasion: "ID"}},
{$sort:{"created":-1}},
{$skip:0},
{$limit:10}
])
my problem is that I dont know how to match anything inside an array in the document like "tags". How can I do this?
You could try to do it without aggregation framework:
db.posts.find(
{
occasion: "ID",
tags: { $elemMatch: { color:"52d50c19f8f8ca8448000001", brand:"52d2cecd0bd1bd844d000018" } }
}
).sort({created: -1}).limit(10)
And if you want to use aggregation:
db.posts.aggregate([
{$match:
{
tags: { $elemMatch: { color:"52d50c19f8f8ca8448000001", brand: "52d2cecd0bd1bd844d000018" } },
occasion: "ID"
}
},
{$sort:{"created":-1}},
{$limit:10}
])

$elemMatch dosen't work after $unwind in MongoDB Aggregation Framework

I have a collection of the following data:
{
"_id" : ObjectId("51f1fcc08188d3117c6da351"),
"cust_id" : "abc123",
"ord_date" : ISODate("2012-10-03T18:30:00Z"),
"status" : "A",
"price" : 25,
"items" : [{
"sku" : "ggg",
"qty" : 7,
"price" : 2.5
}, {
"sku" : "ppp",
"qty" : 5,
"price" : 2.5
}]
}
I am using the query:
cmd { "aggregate" : "orders" , "pipeline" : [
{ "$unwind" : "$items"} ,
{ "$match" : { "items" : { "$elemMatch" : { "qty" : { "$in" : [ 7]}}}}} ,
{ "$group" : { "price" : { "$first" : "$price"} , "items" : { "$push" : { "sku" : "$items.sku"}} , "_id" : { "items" : "$items"}}} ,
{ "$sort" : { "price" : -1}} ,
{ "$project" : { "_id" : 0 , "price" : 1 , "items" : 1}}
]}
Not able to understand what is going wrong
It's because you're doing $match after $unwind. $unwind generates a new stream of documents where items is no longer an array (see docs).
It emits each document as many times as there are items in it.
If you want to select documents with desired element in it and then process all of its documents, you should call $match first:
db.orders.aggregate(
{ "$match" : { "items" : { "$elemMatch" : { "qty" : { "$in" : [ 7]}}}}},
{ "$unwind" : "$items"},
...
);
If you want to select items to be processed after $unwind, you shoul remove $elemMatch:
db.orders.aggregate(
{ "$unwind" : "$items"},
{ "$match" : { "items.qty" : { "$in" : [7]}}},
...
);
In first case you'll get two documents:
{
"price" : 25,
"items" : [
{"sku" : "ppp"}
]
},
{
"price" : 25,
"items" : [
{"sku" : "ggg"}
]
}
and in second case you'll get one:
{
"price" : 25,
"items" : [
{"sku" : "ggg"}
]
}
Update. After $unwind your documents will look like:
{
"_id" : ObjectId("51f1fcc08188d3117c6da351"),
"cust_id" : "abc123",
"ord_date" : ISODate("2012-10-03T18:30:00Z"),
"status" : "A",
"price" : 25,
"items" : {
"sku" : "ggg",
"qty" : 7,
"price" : 2.5
}
}
For small number of documents, unwind and match is fine. But large number of documents, it better to do - match ($elemMatch), unwind, and match again.
db.orders.aggregate(
{ "$match" : { "items" : { "$elemMatch" : { "qty" : { "$in" : [ 7]}}}}},
{ "$unwind" : "$items"},
{ "$match" : { "items.qty" : { "$in" : [7]}}}
...
...
);
The first match will filter only documents that match qty criteria. Among the selected documents, the second match will remove the subdocuments not matching the qty criteria.