I have a collection called constructora that has the following structure:
{
"_id" : A,
"edificio" : [
{
"_id": X,
"a" : 48,
"b" : 59
},
{
"_id": Y,
"a" : 8,
"b" : 5
},
{
"_id": Z,
"a" : 0,
"b" : -1
},
...
]
}
So, I want to make a query that returns, for each sub document (edificio) his parent's _id. An example:
{
"_id" : X,
"a" : 48,
"b" : 59
"id_constructora" : A
}
{
"_id" : Y,
"a" : 8,
"b" : 5
"id_constructora" : A
}
{
"_id" : Z,
"a" : 0,
"b" : -1
"id_constructora" : A
}
How can I do that?
EDIT
Now I'm trying using aggregate, and grouping my query by "edificio_id", so for each document in edificio I can get my desired output:
db.constructora.aggregate(
[
{ $project : { "_id" : 1, "edificio._id" : 1 } },
{ $group : { _id : "$edificio._id" } }
]
).pretty();
But it doesn't work. The output is:
...
{
"_id" : [
ObjectId("613339376430333562373466"),
ObjectId("663736363935393066656236"),
ObjectId("313933613036363364633832"),
ObjectId("653135313831633638336436")
]
}
{
"_id" : [
ObjectId("643531326231663739626465"),
ObjectId("343231386237333365356461"),
ObjectId("373461303864636138393263"),
ObjectId("386433623966653737343962"),
ObjectId("303863633366376431363335"),
ObjectId("663833343161643639376161"),
ObjectId("383833363836663532633733"),
ObjectId("396330313961353137333166"),
ObjectId("646535366662363364613837"),
ObjectId("633937613032656436653965")
]
}
You can use $unwind to break the embedded array into embedded docs, $addFields to rename and add the _id into the embedded doc followed by $replaceRoot to promote the embedded document to the top level in 3.4 mongo server.
db.constructora.aggregate([
{$unwind:"$edificio"},
{$addFields:{"edificio.id_constructora":"$_id"}},
{$replaceRoot: {newRoot: "$edificio"}}
])
More info here https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/#replaceroot-with-an-array-element
Related
I am storing messages between users in a collection with this schema:
{
"_id" : ObjectId("5b23c455e3fce278f9e8d05f"), //message id
"f" : ObjectId("5ad13aaa1ba073601cc16bca"), //sender userid
"c" : "Hi", //contents
"t" : ObjectId("5ad2de5c691a4008cf6923b4"), //reciever userid
}
I am trying to query db to generate a list of current user conversations just like whatsapp list with the last message embedded using this aggregation:
db.getCollection('message').aggregate(
[
{ $match: { $or: [ { f: ObjectId("5ad13aaa1ba073601cc16bca") }, {t:ObjectId("5ad13aaa1ba073601cc16bca")} ] } },
{
$group : {
_id :{f:"$f",t:"$t"},
c: { $push: "$$ROOT" }
}
}
]
)
Result is:
{
"_id" : {
"f" : ObjectId("5ad13aaa1ba073601cc16bca"),
"t" : ObjectId("5ad2de5c691a4008cf6923b4")
},
"c" : [
{
"_id" : ObjectId("5b23c455e3fce278f9e8d05f"),
"f" : ObjectId("5ad13aaa1ba073601cc16bca"),
"c" : "Hi",
"t" : ObjectId("5ad2de5c691a4008cf6923b4"),
"d" : ISODate("2018-06-15T13:48:34.000Z"),
}
]
},
{
"_id" : {
"f" : ObjectId("5ad2de5c691a4008cf6923b4"),
"t" : ObjectId("5ad13aaa1ba073601cc16bca")
},
"c" : [
{
"_id" : ObjectId("5b235fea43966a767d2d9604"),
"f" : ObjectId("5ad2de5c691a4008cf6923b4"),
"c" : "Hello",
"t" : ObjectId("5ad13aaa1ba073601cc16bca"),
"d" : ISODate("2018-06-15T06:40:07.000Z"),
}
]
}
As you can see, there is a conversation between 5ad13aaa1ba073601cc16bca and 5ad2de5c691a4008cf6923b4. The group acts on f and t with their order. But we do just need to find conversations regardless of the order of f and t. Thus, the result document should be just like this with the latest message embedded:
{
"_id" : {
"x" : ObjectId("5ad13aaa1ba073601cc16bca"),
"y" : ObjectId("5ad2de5c691a4008cf6923b4")
},
"c" : [
{
"_id" : ObjectId("5b23c455e3fce278f9e8d05f"),
"f" : ObjectId("5ad13aaa1ba073601cc16bca"),
"c" : "Hi",
"t" : ObjectId("5ad2de5c691a4008cf6923b4"),
"d" : ISODate("2018-06-15T13:48:34.000Z"),
}
]
}
How can I handle this with aggregation? Any suggestions? Thanks.
Of course, you can handle it with an aggregation! As your _ids are ObjectIds, you can compare them with relational operators, therefore you can use $max and $min on them:
db.getCollection('message').aggregate([
{$match: {$or: [{f: _id}, {t: _id}]}},
{$group: {
_id: {
x: {$max: ['$f', '$t']},
y: {$min: ['$f', '$t']}
},
c: {$push: '$$ROOT'}}
}
])
The document represents one users having images. Each image can have N images related to it. I would like to be able to update the matches list only if:
The match does exist yet.
There is less then N elements in the matches array
If they are already N element, only push if "c" parameter is higher than the lower present.
{
"user_id" : 1,
"imgs" : [
{
"img_id" : 1,
"matches" : [
{
"c" : 0.3,
"img_id" : 2
},
{
"c" : 0.2,
"img_id" : 3
}
]
},
{
"img_id" : 5,
"matches" : [
{
"c" : 0.4,
"img_id" : 6
}
]
}
]
}
Basically, "matches" is a set, but $addToSet does not provide $slice and $sort, so I am trying to use $push instead.
db.stack.updateOne(
{ "user_id" : 1, "imgs.img_id" : 1, "imgs.matches.img_id" : { "$ne" : 2 } },
{ "$push" : { "imgs.$.matches" : { "$each" : [ { "c" : 0.7, "img_id" : 2} ], "$sort" : { "c" : -1 }, "$slice" : 3 } } }
);
Does not work, since my document get inserted several times.
Your issue is with the filter part of the updateOne. You should use $elemMatch to make sure that the filter is applied to only one element of the "matches" list.
{"user_id": 1, "imgs": {"$elemMatch": {"img_id" : 1, "matches.img_id": {"$ne": 2}}}},
{ "$push" : { "imgs.$.matches" : { "$each" : [ { "c" : 0.7, "img_id" : 2} ], "$sort" : { "c" : -1 }, "$slice" : 3 } } })
Document looks like this:
{
"_id" : ObjectId("361de42f1938e89b179dda42"),
"user_id" : "u1",
"evaluator_id" : "e1",
"candidate_id" : ObjectId("54f65356294160421ead3ca1"),
"OVERALL_SCORE" : 150,
"SCORES" : [
{ "NAME" : "asd", "OBTAINED_SCORE" : 30}, { "NAME" : "acd", "OBTAINED_SCORE" : 36}
]
}
Aggregation function:
db.coll.aggregate([ {$unwind:"$SCORES"}, {$group : { _id : { user_id : "$user_id", evaluator_id : "$evaluator_id"}, AVG_SCORE : { $avg : "$SCORES.OBTAINED_SCORE" }}} ])
Suppose if there are two documents with same "user_id" (say u1) and different "evaluator_id" (say e1 and e2).
For example:
1) Average will work like this ((30 + 20) / 2 = 25). This is working for me.
2) But for { evaluator_id : "e1" } document, score is 30 for { "NAME" : "asd" } and { evaluator_id : "e2" } document, score is 0 for { "NAME" : "asd" }. In this case, I want the AVG_SCORE to be 30 only (not (30 + 0) / 2 = 15).
Is it possible through aggregation??
Could any one help me out.
It's possible by placing a $match between the $unwind and $group aggregation pipelines to first filter the arrays which match the specified condition to include in the average computation and that is, score array where the obtained score is not equal to 0 "SCORES.OBTAINED_SCORE" : { $ne : 0 }
db.coll.aggregate([
{
$unwind: "$SCORES"
},
{
$match : {
"SCORES.OBTAINED_SCORE" : { $ne : 0 }
}
},
{
$group : {
_id : {
user_id : "$user_id",
evaluator_id : "$evaluator_id"
},
AVG_SCORE : {
$avg : "$SCORES.OBTAINED_SCORE"
}
}
}
])
For example, the aggregation result for this document:
{
"_id" : ObjectId("5500aaeaa7ef65c7460fa3d9"),
"user_id" : "u1",
"evaluator_id" : "e1",
"candidate_id" : ObjectId("54f65356294160421ead3ca1"),
"OVERALL_SCORE" : 150,
"SCORES" : [
{
"NAME" : "asd",
"OBTAINED_SCORE" : 0
},
{
"NAME" : "acd",
"OBTAINED_SCORE" : 36
}
]
}
will yield:
{
"result" : [
{
"_id" : {
"user_id" : "u1",
"evaluator_id" : "e1"
},
"AVG_SCORE" : 36
}
],
"ok" : 1
}
{
"_id" : ObjectId("53692eb238ed04c824679f18"),
"firstUserId" : 1,
"secondUserId" : 17,
"messages" : [
{
"_id" : ObjectId("5369338997b964b81d579fc6"),
"read" : true,
"dateTime" : 1399403401,
"message" : "d",
"userId" : 1
},
{
"_id" : ObjectId("536933c797b964b81d579fc7"),
"read" : false,
"dateTime" : 1399403463,
"message" : "asdf",
"userId" : 17
}
]
}
I'm trying to select all documents that have firstUserId = 1 and also have sub documents
that have userId differnet ($ne) to 1 and read = false.
I tried:
db.usermessages.find({firstUserId: 1, "messages.userId": {$ne: 1}, "messages.read": false})
But it returns empty cause messages have both 1 and 17.
And also how to count subdocuments that have given case?
Are you trying to get the count of all the documents which are returned after your match criteria? If Yes, then you might consider using aggregation framework. http://docs.mongodb.org/manual/aggregation/
Something like below could be done to get you the counts:
db.usermessages.aggregate(
{ "$unwind": "$messages" },
{ "$match":
{ "firstUserId": 1,
"messages.userId": { "$ne" : 1},
"messages.read": false
}
},
{ "$group": { "_id" :null, "count" : { "$sum": 1 } } }
)
Hope this helps.
PS: I have not tried this on my system.
I'm want use mongodb to achieve simple query like mysql "select a-b from table", but aggregation framework query result is not right.
data:
{ "_id" : ObjectId("511223348a88785127a0d13f"), "a" : 1, "b" : 1, "name" : "xxxxx0" }
{ "_id" : ObjectId("511223348a88785127a0d13f"), "a" : 2, "b" : 2, "name" : "xxxxx1" }
mongodb cmd:
db.site.aggregate([
{ $match: {
"a" : {$exists:true},
"b" : {$exists:true},
}
},
{ $project: { _id : 0,name : 1,
r1: {$subtract:["$a", "$b"]} }
},
{ $limit: 100 },
]);
"result" : [
{
"name" : "xxxx1",
"r1" : -1
},
{
"name" : "xxxx0",
"r1" : -2
},
]
I cannot replicate your behaviour:
> db.tg.find()
{ "_id" : ObjectId("511223348a88785127a0d13f"), "a" : 1, "b" : 1, "name" : "xxxxx0" }
> db.tg.aggregate([{ $match: { "a" : {$exists:true}, "b" : {$exists:true} } }, { $project: { _id : 0,name : 1, r1: {$subtract:["$a", "$b"]} }}, { $limit: 100 }])
{ "result" : [ { "name" : "xxxxx0", "r1" : 0 } ], "ok" : 1 }
Can you give us a little more info like your MongoDB version?