MongoDB aggregation $elemMatch inside $lookup stage - mongodb

I have a MongoDB collection that is looking like this:
{
players: [
{uuid: "A"},
{uuid: "B"}
]
},
{
players: [
{uuid: "A"},
{uuid: "C"}
]
},
{
players: [
{uuid: "D"},
{uuid: "E"}
]
}
I want to use results of a previous aggregation stage and now find all documents where a player shows up, using a $lookup stage:
from: "collection",
pipeline: [
{
$match: {
players: {
$elemMatch: {
uuid: "$playerId"
}
}
//using "players.uuid": "$playerId" doesn't work either
}
}
],
as: "field"
The input to my $lookup stage looks like this:
{
"playerId" : "A"
}
{
"playerId" : "B"
}
{
"playerId" : "C"
}
This query returns an empty array in field. It seems like $uuid is not getting evaluated correctly, because if I exchange $uuid with a hardcoded value (e.g. A), this query returns results.
I have also tried using the let property, this gave me the same result.
What am I doing wrong?

Using the documents you've provided. I believe this might work for you:
I've used $lookup to do a join onto the collection which holds the playerId, which creates an array called field. I then use $unwind to extract all the array elements from both field and player. Finally I use $cond to crosscheck if both values match.
db.getCollection('foo').aggregate([
{ $lookup : {
from: "bar",
localField: "players.uuid",
foreignField: "playerId",
as: "field"
} },
{ $unwind : "$players" },
{ $unwind : "$field" },
{ $project : {
"players": 1,
"field" : 1,
"isMatch": {
"$cond": [ { "$eq": ["$players.uuid", "$field.playerId"] }, 1, 0 ]
} } }
])
I've purposely left the output verbose..
/* 1 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 1.0
}
/* 2 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f539b337e8d2b97ff2fff"),
"playerId" : "B"
},
"isMatch" : 0.0
}
/* 3 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "B"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 0.0
}
/* 4 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "B"
},
"field" : {
"_id" : ObjectId("5a7f539b337e8d2b97ff2fff"),
"playerId" : "B"
},
"isMatch" : 1.0
}
/* 5 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 1.0
}
/* 6 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f53a8337e8d2b97ff3000"),
"playerId" : "C"
},
"isMatch" : 0.0
}
/* 7 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "C"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 0.0
}
/* 8 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "C"
},
"field" : {
"_id" : ObjectId("5a7f53a8337e8d2b97ff3000"),
"playerId" : "C"
},
"isMatch" : 1.0
}

Related

MongoDB - Find duplicated elements in record property

I'm struggling to identified duplicated elements in my MongoDB records, here is my problem :
I have a Mongo collection named "elements".
Example of a record in this collection :
{
"_id" : ObjectId("5d1b2204e851271e80c824b6"),
"name" : "A",
"items" : [
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d7"),
"_id" : ObjectId("5d1b2205e851271e80c82534")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d6"),
"_id" : ObjectId("5d1b2205e851271e80c82533")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d8"),
"_id" : ObjectId("5d1b2205e851271e80c82532")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d5"),
"_id" : ObjectId("5d1b3048e851271e80c826a5")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d5"),
"_id" : ObjectId("5d1b3048e851271e80c826ad")
}
]
}
I would like to identify records where the array "items" contains objects with the same "ref_id".
In my example we can see that the last two objects of the "items" array have the same "ref_id" : ObjectId("5d1b2204e851271e80c823d5").
I tried a bunch of aggregate function but unfortunately couldn't came out with a solution.
The following query can get us the expected output:
db.elements.aggregate([
{
$unwind:"$items"
},
{
$group:{
"_id":"$_id",
"root":{
$first:"$$ROOT"
},
"items":{
$push:"$items"
},
"distinctItems":{
$addToSet: "$items.ref_id"
}
}
},
{
$match:{
$expr:{
$ne:[
{
$size:"$items"
},
{
$size:"$distinctItems"
}
]
}
}
},
{
$addFields:{
"root.items":"$items"
}
},
{
$replaceRoot:{
"newRoot":"$root"
}
}
]).pretty()
Data set:
{
"_id" : ObjectId("5d1b2204e851271e80c824b6"),
"name" : "A",
"items" : [
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d7"),
"_id" : ObjectId("5d1b2205e851271e80c82534")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d6"),
"_id" : ObjectId("5d1b2205e851271e80c82533")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d8"),
"_id" : ObjectId("5d1b2205e851271e80c82532")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d5"),
"_id" : ObjectId("5d1b3048e851271e80c826a5")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d5"),
"_id" : ObjectId("5d1b3048e851271e80c826ad")
}
]
}
{
"_id" : ObjectId("5d654b9d7d0ab652c42315f2"),
"name" : "B",
"items" : [
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d7"),
"_id" : ObjectId("5d1b2205e851271e80c82534")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d6"),
"_id" : ObjectId("5d1b2205e851271e80c82533")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d8"),
"_id" : ObjectId("5d1b2205e851271e80c82532")
}
]
}
Output:
{
"_id" : ObjectId("5d1b2204e851271e80c824b6"),
"name" : "A",
"items" : [
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d7"),
"_id" : ObjectId("5d1b2205e851271e80c82534")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d6"),
"_id" : ObjectId("5d1b2205e851271e80c82533")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d8"),
"_id" : ObjectId("5d1b2205e851271e80c82532")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d5"),
"_id" : ObjectId("5d1b3048e851271e80c826a5")
},
{
"ref_id" : ObjectId("5d1b2204e851271e80c823d5"),
"_id" : ObjectId("5d1b3048e851271e80c826ad")
}
]
}
Explanation: We are populating an array of distinct ref_id from each document and matching if the size of the populated array is equal to the size of actual items array.

MongoDB 3.4 MongoDBRef dereferencing with .NET MongoDB Driver

I have following collections:
Collection A
{
"_id" : ObjectId("5aaa3b170e26ed1eba223ba9"),
"name" : "A1",
"ref" : {
"$ref" : "B",
"$id" : ObjectId("5aaa33740e26ed1eba223ba1")
}
}
{
"_id" : ObjectId("5aaa3b170e26ed1eba223baa"),
"name" : "A2",
"ref" : {
"$ref" : "C",
"$id" : ObjectId("5aaa33740e26ed1eba223ba2")
}
}
Collection B
{
"_id" : ObjectId("5aaa33740e26ed1eba223ba1"),
"name" : "B1"
}
...
Collection C
{
"_id" : ObjectId("5aaa33740e26ed1eba223ba2"),
"name" : "C1"
}
...
It is posable to get folowing result?
{
"_id" : ObjectId("5aaa3b170e26ed1eba223ba9"),
"name" : "A1",
"result" : [
{
"name" : "B1"
}
]
}
{
"_id" : ObjectId("5aaa3b170e26ed1eba223baa"),
"name" : "A2",
"result" : [
{
"name" : "C1"
}
]
}
I tried it with $Project and §Lookups, unfortunately withou success.
Hire is example:
db.A.aggregate([
{$project: {
name : 1,
refId: {$arrayElemAt: [{$objectToArray:"$ref"},1]},
refCol: {$arrayElemAt: [{$objectToArray:"$ref"},0]},
}
},
{$lookup : {
from : "refCol.v",
localField : "refId.v",
foreignField : "_id",
as : "result"
}
},
{$project : {"result._id" : 0, refId : 0, refCol : 0}}
])
In this example I can't reference the "refCol.v" field in the $lookup function.
Have someone a tip or a better solution for me?
You can achieve this by populate() method:
const collectionA = require('../models/collectionA');
collectionA.find({}).populate({
path: 'collectionB',
select: {
'name': 1,
'_id': 0
}
})
}).then((data) => {
if (data) {
res.send(data);
}
}).catch((err) => {
res.send(err);
})

how to get this query in mongoDB

I have this collection...
> db.banks.find().pretty()
{
"_id" : ObjectId("54f37cbb44aec3b01b7db8f4"),
"name" : "A",
"branches" : [
{
"branch_id" : 8561,
"name" : "X",
},
{
"branch_id" : 8576,
"name" : "Y",
}
]
}
{
"_id" : ObjectId("54f37cbb44aec3b01b7db8f5"),
"name" : "B",
"branches" : [
{
"branch_id" : 3238,
"name" : "Z",
}
]
}
with this command :
db.banks.aggregate({$project{"branches.name":1,"_id":0}});
get this result :
{ "branches" : { { "name" : "X" }, { "name" : "Y" } } }
{ "branches" : { { "name" : "Z" } } }
but; how I get this result?
(In fact, one object and without "branches".)
{{"name" : "X"}, {"name" : "Y"}, {"name" : "Z"}}
very thanks...
One way you could go about this is to do an $unwind first in the aggregation pipeline to get a deconstructed array with a document for each element and then group by the array element $branches.name:
db.banks.aggregate([
{ $unwind: '$branches'},
{
$group: {
_id: {
name: '$branches.name'
}
}
},
{
$project: {
_id: 0,
name: '$_id.name'
}
},
{ $sort : { "name" : 1 } }
])
Outputs:
{
"result" : [
{
"name" : "X"
},
{
"name" : "Y"
},
{
"name" : "Z"
}
],
"ok" : 1
}

Aggregate of different subtypes in document of a collection

abstract document in collection md given:
{
vals : [{
uid : string,
val : string|array
}]
}
the following, partially correct aggregation is given:
db.md.aggregate(
{ $unwind : "$vals" },
{ $match : { "vals.uid" : { $in : ["x", "y"] } } },
{
$group : {
_id : { uid : "$vals.uid" },
vals : { $addToSet : "$vals.val" }
}
}
);
that may lead to the following result:
"result" : [
{
"_id" : {
"uid" : "x"
},
"vals" : [
[
"24ad52bc-c414-4349-8f3a-24fd5520428e",
"e29dec2f-57d2-43dc-818a-1a6a9ec1cc64"
],
[
"5879b7a4-b564-433e-9a3e-49998dd60b67",
"24ad52bc-c414-4349-8f3a-24fd5520428e"
]
]
},
{
"_id" : {
"uid" : "y"
},
"vals" : [
"0da5fcaa-8d7e-428b-8a84-77c375acea2b",
"1721cc92-c4ee-4a19-9b2f-8247aa53cfe1",
"5ac71a9e-70bd-49d7-a596-d317b17e4491"
]
}
]
as x is the result aggregated on documents containing an array rather than a string, the vals in the result is an array of arrays. what i look for in this case is to have a flattened array (like the result for y).
for me it seems like that what i want to achieve by one aggegration call only, is currently not supported by any given operation as e.g. a type conversion cannot be done or unwind expectes in every case an array as input type.
is map reduce the only option i have? if not ... any hints?
thanks!
You can use the aggregation to do the computation you want without changing your schema (though you might consider changing your schema simply to make queries and aggregations of this field easier to write).
I broke up the pipeline into multiple steps for readability. I also simplified your document slightly, again for readability.
Sample input:
> db.md.find().pretty()
{
"_id" : ObjectId("512f65c6a31a92aae2a214a3"),
"uid" : "x",
"val" : "string"
}
{
"_id" : ObjectId("512f65c6a31a92aae2a214a4"),
"uid" : "x",
"val" : "string"
}
{
"_id" : ObjectId("512f65c6a31a92aae2a214a5"),
"uid" : "y",
"val" : "string2"
}
{
"_id" : ObjectId("512f65e8a31a92aae2a214a6"),
"uid" : "y",
"val" : [
"string3",
"string4"
]
}
{
"_id" : ObjectId("512f65e8a31a92aae2a214a7"),
"uid" : "z",
"val" : [
"string"
]
}
{
"_id" : ObjectId("512f65e8a31a92aae2a214a8"),
"uid" : "y",
"val" : [
"string1",
"string2"
]
}
Pipeline stages:
> project1 = {
"$project" : {
"uid" : 1,
"val" : 1,
"isArray" : {
"$cond" : [
{
"$eq" : [
"$val.0",
[ ]
]
},
true,
false
]
}
}
}
> project2 = {
"$project" : {
"uid" : 1,
"valA" : {
"$cond" : [
"$isArray",
"$val",
[
null
]
]
},
"valS" : {
"$cond" : [
"$isArray",
null,
"$val"
]
},
"isArray" : 1
}
}
> unwind = { "$unwind" : "$valA" }
> project3 = {
"$project" : {
"_id" : 0,
"uid" : 1,
"val" : {
"$cond" : [
"$isArray",
"$valA",
"$valS"
]
}
}
}
Final aggregation:
> db.md.aggregate(project1, project2, unwind, project3, group)
{
"result" : [
{
"_id" : "z",
"vals" : [
"string"
]
},
{
"_id" : "y",
"vals" : [
"string1",
"string4",
"string3",
"string2"
]
},
{
"_id" : "x",
"vals" : [
"string"
]
}
],
"ok" : 1
}
If you modify your schema using always "vals.val" field as an array field (even when the record contains only one element) you can do it easily as follows:
db.test_col.insert({
vals : [
{
uid : "uuid1",
val : ["value1"]
},
{
uid : "uuid2",
val : ["value2", "value3"]
}]
});
db.test_col.insert(
{
vals : [{
uid : "uuid2",
val : ["value4", "value5"]
}]
});
Using this approach you only need to use two $unwind operations: one unwinds the "parent" array and the second unwinds every "vals.val" value. So, querying like
db.test_col.aggregate(
{ $unwind : "$vals" },
{ $unwind : "$vals.val" },
{
$group : {
_id : { uid : "$vals.uid" },
vals : { $addToSet : "$vals.val" }
}
}
);
You can obtain your expected value:
{
"result" : [
{
"_id" : {
"uid" : "uuid2"
},
"vals" : [
"value5",
"value4",
"value3",
"value2"
]
},
{
"_id" : {
"uid" : "uuid1"
},
"vals" : [
"value1"
]
}
],
"ok" : 1
}
And no, you can't execute this query using your current schema, since $unwind fails when the field isn't an array field.

MongoDB aggregation framework $subtract

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?