Select all distinct subdocuments - mongodb

I am have the next document collection:
{ "_id" : ObjectId("568dc77473bf57f465da61dd"), "name" : "Rama", "items" : [ { "id" : 40, "name" : "Botella" }, { "id" : 30, "name" : "Frasco" } ] }
{ "_id" : ObjectId("568dc78873bf57f465da61de"), "name" : "Pepe", "items" : [ { "id" : 40, "name" : "Botella" }, { "id" : 29, "name" : "Cigarrillo" } ] }
Each document have a collection of items. Each item is a document. So i have the list of all distinct items on the collection:
{ "id" : 40, "name" : "Botella" },
{ "id" : 29, "name" : "Cigarrillo" },
{ "id" : 30, "name" : "Frasco" }
With $unwind i obtained one item per document:
db.test.aggregate([{'$unwind': '$items'}, {'$project': {'items.id': 1, 'items.name': 1, '_id': 0}}])
{ "items" : { "id" : 40, "name" : "Botella" } }
{ "items" : { "id" : 30, "name" : "Frasco" } }
{ "items" : { "id" : 40, "name" : "Botella" } }
{ "items" : { "id" : 29, "name" : "Cigarrillo" } }
But now, i don't know how get the distinct 'items'. I am doing my first steps with MongoDB.
Any ideas ?

You can group on the items.id and items.name to get the distinct items
db.test.aggregate([
{'$unwind': '$items'},
{'$group': {'_id':{'id':'$items.id', 'name':'$items.name'}}},
{'$project': {'items': '$_id', '_id':0}}])

Related

MongoDB $push operator and count characters in a name

I've populated a database in Mongodb with the following data in a collection named people.
{ "_id" : 0, "name" : "Bernice Pope", "age" : 69, "date" : "2017-10-04T18:35:44.011Z" }
{ "_id" : 1, "name" : "Eric Malone", "age" : 57, "date" : "2017-10-04T18:35:44.014Z" }
{ "_id" : 2, "name" : "Blanche Miller", "age" : 35, "date" : "2017-10-4T18:35:44.015Z" }
{ "_id" : 3, "name" : "Sue Perez", "age" : 64, "date" : "2017-10-04T18:35:44.016Z" }
{ "_id" : 4, "name" : "Ryan White", "age" : 39, "date" : "2017-10-04T18:35:44.019Z"}
{ "_id" : 5, "name" : "Grace Payne", "age" : 56, "date" : "2017-10-04T18:35:44.020Z" }
{ "_id" : 6, "name" : "Jessie Yates", "age" : 53, "date" : "2017-10-04T18:35:44.020Z"}
{ "_id" : 7, "name" : "Herbert Mason", "age" : 37, "date" : "2017-10-4T18:35:44.020Z" }
{ "_id" : 8, "name" : "Jesse Jordan", "age" : 47, "date" : "2017-10-04T18:35:44.020Z"}
{ "_id" : 9, "name" : "Hulda Fuller", "age" : 25, "date" : "2017-10-04T18:35:44.020Z"}
Based on the people collection, I need to create a view named PeopleNames which likes below:
{ "LengthOfName": 8, "names" : [ "Sue Perez" ]}
{ "LengthOfName" : 9, "names" : [ "Ryan White" ]}
{ "LengthOfName" : 10, "names" : [ "Eric Malone", "Grace Payne" ]}
{ "LengthOfName" : 11, "names" : [ "Bernice Pope", "Jessie Yates", "Jesse Jordan", "Hulda Fuller" ]}
{ "LengthOfName" : 12, "names" : [ "Herbert Mason" ]}
{ "LengthOfName" : 13, "names" : [ "Blanche Miller"]}
LengthOfName is the total number of the characters in the last name and first name.
My approach is to first add the names into an array, the use $split operator to split the names and use $strLenCP to count the characters.
db.people.aggregate([
{
$project : {
name : 1,
name_array : [{$push : { $split : {$name : " "}}}]
}
}
,{
$unwind : "$name_array"
},{
$project : {
name : 1,
nameLength : {$strLenCP : $name_array}
}
},{
$group :{
_id : "$nameLength",
nameLength: 1
}
}])
But I am receiving error saying that my "$name_array" is undefined Any ideas?
The $push aggregation operator is only available in the $group stage.
You can group the documents by the name length (after trimming the spaces in the name using $replaceAll) and use $push to add the names to the names array. You can then add a $project stage to add the LengthOfName field to the documents and finally add a $sort stage to sort the documents by the LengthOfName field.
db.collection.aggregate([
{
$group: {
_id: {
$strLenCP: {
$replaceAll: {
input: "$name",
find: " ",
replacement: ""
}
}
},
names: {
$push: "$name"
}
}
},
{
$project: {
_id: 0,
LengthOfName: "$_id",
names: "$names",
}
},
{
$sort: {
LengthOfName: 1,
}
}
])
MongoPlayground

What is $$ROOT in MongoDB aggregate and how it works?

I am watching a tutorial I can understand how this aggregate works, What is the use of pings, $$ROOT in it.
client = pymongo.MongoClient(MY_URL)
pings = client['mflix']['watching_pings']
cursor = pings.aggregate([
{
"$sample": { "size": 50000 }
},
{
"$addFields": {
"dayOfWeek": { "$dayOfWeek": "$ts" },
"hourOfDay": { "$hour": "$ts" }
}
},
{
"$group": { "_id": "$dayOfWeek", "pings": { "$push": "$$ROOT" } }
},
{
"$sort": { "_id": 1 }
}
]);
Let's assume that our collection looks like below:
{
"_id" : ObjectId("b9"),
"key" : 1,
"value" : 20,
"history" : ISODate("2020-05-16T00:00:00Z")
},
{
"_id" : ObjectId("ba"),
"key" : 1,
"value" : 10,
"history" : ISODate("2020-05-13T00:00:00Z")
},
{
"_id" : ObjectId("bb"),
"key" : 3,
"value" : 50,
"history" : ISODate("2020-05-12T00:00:00Z")
},
{
"_id" : ObjectId("bc"),
"key" : 2,
"value" : 0,
"history" : ISODate("2020-05-13T00:00:00Z")
},
{
"_id" : ObjectId("bd"),
"key" : 2,
"value" : 10,
"history" : ISODate("2020-05-16T00:00:00Z")
}
Now based on the history field you want to group and insert the whole documents in to an array field 'items'. Here $$ROOT variable will be helpful.
So, the aggregation query to achieve the above will be:
db.collection.aggregate([{
$group: {
_id: '$history',
items: {$push: '$$ROOT'}
}
}])
It will result in following output:
{
"_id" : ISODate("2020-05-12T00:00:00Z"),
"items" : [
{
"_id" : ObjectId("bb"),
"key" : 3,
"value" : 50,
"history" : ISODate("2020-05-12T00:00:00Z")
}
]
},
{
"_id" : ISODate("2020-05-13T00:00:00Z"),
"items" : [
{
"_id" : ObjectId("ba"),
"key" : 1,
"value" : 10,
"history" : ISODate("2020-05-13T00:00:00Z")
},
{
"_id" : ObjectId("bc"),
"key" : 2,
"value" : 0,
"history" : ISODate("2020-05-13T00:00:00Z")
}
]
},
{
"_id" : ISODate("2020-05-16T00:00:00Z"),
"items" : [
{
"_id" : ObjectId("b9"),
"key" : 1,
"value" : 20,
"history" : ISODate("2020-05-16T00:00:00Z")
},
{
"_id" : ObjectId("bd"),
"key" : 2,
"value" : 10,
"history" : ISODate("2020-05-16T00:00:00Z")
}
]
}
I hope it helps.

Add user id in my profile collection using aggregation

Below is my user collection data
user collection
{
"_id" : ObjectId("584bc9ba420a6b189c510af6"),
"old_user_id" :1,
"name" :"aaa"
},
{
"_id" : ObjectId("9ba420a584bc6b189c59ba42"),
"old_user_id" : 2,
"name" :"bbb"
},
{
"_id" : ObjectId("59ba4284bc0a6b189c3323w23"),
"old_user_id" : 3,
"name" :"ccc"
}
myprofile collection
{
"old_user_id" :1,
"name" :"aaa",
"number":"123456789"
},
{
"old_user_id" : 2,
"name" :"bbb",
"number":"678912345"
},
{
"old_user_id" : 3,
"name" :"ccc",
"number":"673458912"
},
{
"old_user_id" : 2,
"name" : "bbb",
"adress" : "afsfdidhddk"
}
My expectation:
I need to match old_user_id in both collections and update the user collection '_id' in my profile collection
{
"userid":"584bc9ba420a6b189c510af6",
"old_user_id" :1,
"name" :"aaa",
"number":"123456789"
},
{
"userid":"9ba420a584bc6b189c59ba42",
"old_user_id" : 2,
"name" :"bbb",
"number":"678912345"
},
{
"userid":"59ba4284bc0a6b189c3323w23",
"old_user_id" : 3,
"name" :"ccc",
"number":"673458912"
},
{
"old_user_id" : 2,
"name" : "bbb",
"adress" : "afsfdidhddk"
"userid" : "9ba420a584bc6b189c59ba42"
}
You can use mongo aggregation. Use of $lookup, $project and $unwind will help.
I have formulated the query, hope this helps:
db.myprofile.aggregate([
{
$lookup:
{
from: "user",
localField: "old_user_id",
foreignField: "old_user_id",
as: "inventory_docs"
}
},
{ $project : { _id : "$inventory_docs._id",
old_user_id:"$old_user_id",
"name":"$name",
"number":"$number" } },
{$unwind: "$_id"}
])
Update:
db.user.find().forEach(function (user) {
var cursor = db.myprofile.find({"old_user_id": user.old_user_id});
cursor.forEach(function(myprofile) {
myprofile.userid = user._id.str;
db.myprofile.save(myprofile);
});
});
Result:
db.myprofile.find().pretty()
{
"_id" : ObjectId("584e7294678ae15db4fab039"),
"old_user_id" : 1,
"name" : "aaa",
"number" : "123456789",
"userid" : "584e7294678ae15db4fab035"
}
{
"_id" : ObjectId("584e7294678ae15db4fab03a"),
"old_user_id" : 2,
"name" : "bbb",
"number" : "678912345",
"userid" : "584e7294678ae15db4fab036"
}
{
"_id" : ObjectId("584e7294678ae15db4fab03b"),
"old_user_id" : 3,
"name" : "ccc",
"number" : "673458912",
"userid" : "584e7294678ae15db4fab037"
}
{
"_id" : ObjectId("584e7294678ae15db4fab03c"),
"old_user_id" : 2,
"name" : "bbb",
"adress" : "afsfdidhddk",
"userid" : "584e7294678ae15db4fab036"
}
Note: the _id fiels still appears in the resulting documents, but you should be able to live with that. Is a default behaviour of MongoDB. You can create a collection with no indexed _id field like in here: db.createCollection("user", { autoIndexId: false })

MongoDB: Sort in combination with Aggregation group

I have a collection called transaction with below documents,
/* 0 */
{
"_id" : ObjectId("5603fad216e90d53d6795131"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e67267",
"status" : "A",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:15:36.609Z")
}
/* 1 */
{
"_id" : ObjectId("5603fad216e90d53d6795134"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e6726d",
"status" : "B",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:14:31.609Z")
}
/* 2 */
{
"_id" : ObjectId("5603fad216e90d53d679512e"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e6726d",
"status" : "C",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:13:36.609Z")
}
/* 3 */
{
"_id" : ObjectId("5603fad216e90d53d6795132"),
"statusId" : "65c719e6727d",
"relatedWith" : "65c719e6726d",
"status" : "D",
"userId" : "100",
"createdTs" : ISODate("2015-09-24T13:16:36.609Z")
}
When I run the below Aggregation query without $group,
db.transaction.aggregate([
{
"$match": {
"userId": "100",
"statusId": "65c719e6727d"
}
},
{
"$sort": {
"createdTs": -1
}
}
])
I get the result in expected sorting order. i.e Sort createdTs in descending order (Minimal result)
/* 0 */
{
"result" : [
{
"_id" : ObjectId("5603fad216e90d53d6795132"),
"createdTs" : ISODate("2015-09-24T13:16:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795131"),
"createdTs" : ISODate("2015-09-24T13:15:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795134"),
"createdTs" : ISODate("2015-09-24T13:14:31.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d679512e"),
"createdTs" : ISODate("2015-09-24T13:13:36.609Z")
}
],
"ok" : 1
}
If I apply the below aggregation with $group, the resultant is inversely sorted(i.e Ascending sort)
db.transaction.aggregate([
{
"$match": {
"userId": "100",
"statusId": "65c719e6727d"
}
},
{
"$sort": {
"createdTs": -1
}
},
{
$group: {
"_id": {
"statusId": "$statusId",
"relatedWith": "$relatedWith",
"status": "$status"
},
"status": {$first: "$status"},
"statusId": {$first: "$statusId"},
"relatedWith": {$first: "$relatedWith"},
"createdTs": {$first: "$createdTs"}
}
}
]);
I get the result in inverse Order i.e. ** Sort createdTs in Ascending order**
/* 0 */
{
"result" : [
{
"_id" : ObjectId("5603fad216e90d53d679512e"),
"createdTs" : ISODate("2015-09-24T13:13:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795134"),
"createdTs" : ISODate("2015-09-24T13:14:31.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795131"),
"createdTs" : ISODate("2015-09-24T13:15:36.609Z")
},
{
"_id" : ObjectId("5603fad216e90d53d6795132"),
"createdTs" : ISODate("2015-09-24T13:16:36.609Z")
}
],
"ok" : 1
}
Where am I wrong ?
The $group stage doesn't insure the ordering of the results. See here the first paragraph.
If you want the results to be sorted after a $group, you need to add a $sort after the $group stage.
In your case, you should move the $sort after the $group and before you ask the question : No, the $sort won't be able to use an index after the $group like it does before the $group :-).
The internal algorithm of $group seems to keep some sort of ordering (reversed apparently), but I would not count on that and add a $sort.
You are not doing anything wrong here, Its a $group behavior in Mongodb
Lets have a look in this example
Suppose you have following doc in collection
{ "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : ISODate("2014-01-01T08:00:00Z") }
{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1, "date" : ISODate("2014-02-03T09:00:00Z") }
{ "_id" : 3, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : ISODate("2014-02-03T09:05:00Z") }
{ "_id" : 4, "item" : "abc", "price" : 10, "quantity" : 10, "date" : ISODate("2014-02-15T08:00:00Z") }
{ "_id" : 5, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : ISODate("2014-02-15T09:05:00Z") }
{ "_id" : 6, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : ISODate("2014-02-15T12:05:10Z") }
{ "_id" : 7, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : ISODate("2014-02-15T14:12:12Z") }
Now if you run this
db.collection.aggregate([{ $sort: { item: 1,date:1}} ] )
the output will be in ascending order of item and date.
Now if you add group stage in aggregation pipeline it will reverse the order.
db.collection.aggregate([{ $sort: { item: 1,date:1}},{$group:{_id:"$item"}} ] )
Output will be
{ "_id" : "xyz" }
{ "_id" : "jkl" }
{ "_id" : "abc" }
Now the solution for your problem
change "createdTs": -1 to "createdTs": 1 for group

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}
])