Query nested arrays of subdocuments - arrays

EDIT: The query should not user the '&" characters. Those or for updates.
ANSWER: Courtesy of Gibbs
correct:
const result : any = await mongoose.model('Events').update(
{
_id: eventId,
"groups._id": groupId,
"groups.users._id": userId
},
incorrect:
const result: any = await mongoose.model('Events').findOne(
{
_id: eventId,
"groups._id": "5f270416e7964b20d6f8953e",
"groups.$.users": { _id: '5f270877b4942d2528885dbd' }
})
// OR:
{
_id: eventId,
"groups._id": "5f270416e7964b20d6f8953e",
"groups.$.users._id" : '5f270877b4942d2528885dbd',
})
With only the first two predicates,(eventId and groups [$_id]) it returns the event. But the third breaks.
Does anybody know why this returns null (object ids are copied directly from the db)
Thanks!
{
"_id": ObjectId("5f270416e7964b20d6f8953d"),
"lastRounds": [],
"name": "EpicFest",
"start": 1596392488896.0,
"end": 1596392500896.0,
"groups": [
{
"_id": ObjectId("5f270416e7964b20d6f8953e"),
"name": "Vossius",
"users": [
{
"created": 1596393590778.0,
"sessionId": null,
"feedBack": {
"messagesSent": 0,
"messagesSentLiked": 0,
"messagesReceived": 0,
"messagesReceivedLiked": 0,
"userFeedbackReceived": [],
"chatFeedbackReceived": [],
"_id": ObjectId("5f270877b4942d2528885dbe")
},
"_id": ObjectId("5f270877b4942d2528885dbd"),
"image": "someAvatr",
"name": "hans",
"groupId": "5f270416e7964b20d6f8953e",
"results": []
},
]
},
{
"_id": ObjectId("5f270416e7964b20d6f8953f"),
"users": [],
"name": "Ignatius"
}
],
"results": [],
"theses": [],
"rounds": 10,
"schedule": [],
"__v": 4
}

Use the aggregate pipeline to fetch based on the inner sub-document.
In the first match stage, filter by eventId and do a $elemMatch on the inner groupId.
Next, unwind the group and groups.user.
Post unwinding you will have a flat object structure that you can again apply filter upon and, just apply a match stage on groups.user._id now.
const pipeline = [
{
$match: {
_id: eventId,
groups: {
$elemMatch: mongoose.Types.ObjectId('5f270416e7964b20d6f8953e')
}
}
},
{
$unwind: '$groups'
},
{
$unwind: '$groups.users'
},
{
$match: {
'$groups.users._id': mongoose.Types.ObjectId('5f270877b4942d2528885dbd')
}
}
];
const result: any = await mongoose.model('Events').aggregate(pipeline).exec();

Mongo Play
db.collection.find({
_id: ObjectId("5f270416e7964b20d6f8953d"),
"groups._id": ObjectId("5f270416e7964b20d6f8953e"),
"groups.users._id": ObjectId("5f270877b4942d2528885dbd")
})
You tried to use String "5f270416e7964b20d6f8953e". It should be ObjectId
You can use . notation to access a nested element or $elemMatch

Related

MongoDB using skip and distinct in a query based on values inside an array

So I have document that is structure like this
_id: ObjectId('62bbe17d8fececa06b91873d')
clubName: 'test'
staff:[
'62bbe47f8fececa06b9187d8'
'624f4b56ab4f5170570cdba3' //IDS of staff members
]
A single staff can be assigned to multiple clubs so what I'm trying to achieve is to get all staff that has been assigned to at least one club and display them on a table on the front end, I followed this solution since distinct and skip can't be used on a single query but it just returned this:
[
{ _id: [ '624f5054ab4f5170570cdd16', '624f5054ab4f5170570cdd16' ] } //staff from club 1,
{ _id: [ '624f5054ab4f5170570cdd16', '624f9194ab4f5170570cded1' ] } //staff from club 2,
{ _id: [ '624f4b56ab4f5170570cdba3' ]} //staff from club 3
]
my desired outcome would be like this:
[ _id : ['624f5054ab4f5170570cdd16', '624f9194ab4f5170570cded1', '624f4b56ab4f5170570cdba3'] ]
here's my query:
const query = this.clubModel.aggregate(
[{ $group: { _id: '$staff' } }, { $skip: 0}, { $limit: 10}],
(err, results) => {
console.log(results);
},
);
the values returned are not distinct at all, is there an operation that can evaluate the value inside an array and make them distinct?
Here's my new query after adding the 'createdAt' field in my document structure:
const query = this.clubModel.aggregate([
{ $sort: { createdAt: -1 } },
{
$unwind: '$drivers',
},
{
$project: {
isActive: true,
},
},
{
$group: {
_id: 'null',
ids: {
$addToSet: '$drivers',
},
},
},
{
$project: {
_id: 0,
},
},
{
$skip: skip,
},
{
$limit: limit,
},
]);
Does this works for you, first UNWIND the staff array, and then group on "_id" as null and add staff values using $addToSet:
db.collection.aggregate([
{
"$unwind": "$staff"
},
{
"$group": {
"_id": "null",
"ids": {
"$addToSet": "$staff"
}
}
},
{
"$project": {
"_id": 0,
}
},
{
$skip: 0
},
{
$limit: 10
}
])
Here's the working link.

(Mongodb) Getting an error (BadValue) $in needs an array , when using $map aggregate function

I am trying to convert array of strings to array of ObjectId using map function, map will return me array of ObjectIds that I am using in $match to check if _id matches with any of objectids present in array using $in operator.
Database configuration is
db={
"students": [
{
"_id": ObjectId("5c1a79f7c98da061141475b7"),
"firstName": "Ibrahim",
"kelasID": ObjectId("5c429f9906f2a805bc6cd494"),
"lastName": "Ali",
"schoolID": ObjectId("5c1a735fc98da061141475a1"),
"year": 2018,
"__v": 0,
"addedOn": ISODate("2018-12-25T04:27:47.909Z"),
"checkIn": false,
"checkInStatus": 1,
"contactNo1": "012225656",
"father": "Ali",
"fatherID": "8852245",
"idType": 0,
"lastModified": ISODate("2018-12-25T04:27:47.909Z"),
"mother": "",
"motherID": ""
},
{
"_id": ObjectId("5c3bfea37774fb0b55000cb5"),
"idType": 0,
"checkIn": false,
"checkInStatus": 1,
"year": 2019,
"schoolID": ObjectId("5c1a735fc98da061141475a1"),
"kelasID": ObjectId("5c1a7534c98da061141475a3"),
"firstName": "Umar",
"lastName": "Bin Al-Khattab",
"contactNo1": "601222",
"status": 1,
"addedOn": ISODate("2019-01-14T03:14:43.597Z"),
"lastModified": ISODate("2019-01-14T03:14:43.597Z"),
"__v": 0
},
{
"_id": ObjectId("5c1a7c69c98da061141475bb"),
"idType": 0,
"checkIn": false,
"checkInStatus": 1,
"year": 2018,
"schoolID": ObjectId("5c1a735fc98da061141475a1"),
"kelasID": ObjectId("5c1a7540c98da061141475a5"),
"firstName": "Abdul Rahman",
"lastName": "Affan",
"father": "Affan",
"fatherID": "54321",
"contactNo1": "602288",
"status": 1,
"addedOn": ISODate("2018-12-25T04:30:16.130Z"),
"lastModified": ISODate("2018-12-25T04:30:16.130Z"),
"__v": 0
}
]
}
and the query is
db.students.aggregate([
{
"$match": {
"_id": {
$in: {
"stud": {
"$map": {
"input": [
"5c1a79f7c98da061141475b7",
"5c3bfea37774fb0b55000cb5",
"5c1a7c69c98da061141475bb",
"5c3bfea37774fb0b55000cb4",
"5c1a7d32c98da061141475be",
"5c3bfea37774fb0b55000cb7"
],
"in": {
"$toObjectId": "$$this"
}
}
}
}
}
}
}
])
You can also check this out here- https://mongoplayground.net/p/8tQFHxtrttW
The above doesn't work but if I change the query like below it works.
db.students.aggregate([
{
"$match": {
"_id": {
$in: [
ObjectId("5c1a79f7c98da061141475b7"),
ObjectId("5c3bfea37774fb0b55000cb5")
]
}
}
}
])
Link to above query- https://mongoplayground.net/p/cjq77KIAcPE
Error i'm getting - query failed: (BadValue) $in needs an array
Can anybody help with the reason to the error , as according to docs map does return an array , so what am I doing wrong?
Not sure why it doesn't work it might have to do with $in operator and $in aggregation. When you use an array that is defined by a variable you can use $in operator but when you are using aggregation result ( in this case $map ) you have to use $in aggregation. But to use $in aggregation you need to work with $expr for $match.
You can try the below code which works fine
db.students.aggregate([
{
$addFields: {
studentIds: {
$map: {
input: [
"5c1a79f7c98da061141475b7",
"5c3bfea37774fb0b55000cb5",
"5c1a7c69c98da061141475bb",
"5c3bfea37774fb0b55000cb4",
"5c1a7d32c98da061141475be",
"5c3bfea37774fb0b55000cb7",
],
in: {
$toObjectId: "$$this",
},
},
},
},
},
{
$match: {
$expr: {
$in: ["$_id", "$studentIds"],
},
},
},
]);
or if you want to combine the two of the stages you can do it like this
db.students.aggregate([
{
$match: {
$expr: {
$in: [
"$_id",
{
$map: {
input: [
"5c1a79f7c98da061141475b7",
"5c3bfea37774fb0b55000cb5",
"5c1a7c69c98da061141475bb",
"5c3bfea37774fb0b55000cb4",
"5c1a7d32c98da061141475be",
"5c3bfea37774fb0b55000cb7",
],
in: {
$toObjectId: "$$this",
},
},
},
],
},
},
},
]);

Remove object from array with mongoose 5.12 ($pull not working)

I have a problem with mongoose 5.12.
{
"_id": {
"$oid": "60db70c9956a6c4d0645d447"
},
"articles": [
{
"_id": {
"$oid": "60db8764da322a23e787ca3d"
},
"type": "Déssert",
"name": "dfd",
"description": "",
"price": 0,
"tag": "",
"picture": "noarticle.jpg"
},
],
"editor": 27,
}
My restaurant document contains articles (object array)
And I want to remove an article from it.
I'm using this:
const deletedArticle = await this.restaurantModel.findOneAndUpdate(
{ editor: userId },
{ $pull: { articles: articleId } },
{ multi: true, new: true, useFindAndModify: true },
);
// userId -> 27 and articleId --> "60db8764da322a23e787ca3d"
But nothing changes.
Is this an _id type problem? Or anything else?
(The $push option work)
You did wrong in the line:
{ $pull: { articles: articleId } }
It should be:
{ $pull: { articles: { _id : articleId } } }
Or:
{ $pull: {"articles._id" : articleId } }

How to project only matched array item in mongodb aggregation?

I have a collection called shows, with documents as:
{
"url": "http://www.tvmaze.com/shows/167/24",
"name": "24",
"genres": [
"Drama",
"Action"
],
"runtime": 60
},
{
"url": "http://www.tvmaze.com/shows/4/arrow",
"name": "Arrow",
"genres": [
"Drama",
"Action",
"Science-Fiction"
],
"runtime": 60
}
I wanted to search shows with genre 'Action' and project the result array as
{
"url": "http://www.tvmaze.com/shows/167/24",
"name": "24",
"genres": [
"Action" // I want only the matched item in
//my result array
],
"runtime": 60
} , //same for the second doc as well
If I use
db.shows.find({genres:'Action'}, {'genres.$': 1});
It works but the same does not work in aggregate method with $project
Shows.aggregate([
{
$match: { 'genres': 'Action'}
},
{
$project: {
_id: 0,
url: 1,
name: 1,
runtime: 1,
'genres.$': 1
}
}
]);
this is the error I get on this aggregate query
Invalid $project :: caused by :: FieldPath field names may not start with '$'."
db.collection.aggregate([
{
$match: {
"genres": {
$regex: "/^action/",
$options: "im"
}
}
},
{
$project: {
_id: 0,
url: 1,
name: 1,
runtime: 1,
genres: {
$filter: {
input: "$genres",
as: "genre",
cond: {
$regexMatch: {
input: "$$genre",
regex: "/^action/",
options: "im"
}
}
}
}
}
}
])
Here is how I solved it, Thanks #turivishal for the help

Query only for numbers in nested array

I am trying to get an average number of an key in a nested array inside a document, but not sure how to accomplish this.
Here is how my document looks like:
{
"_id": {
"$oid": "XXXXXXXXXXXXXXXXX"
},
"data": {
"type": "PlayerRoundData",
"playerId": "XXXXXXXXXXXXX",
"groupId": "XXXXXXXXXXXXXX",
"holeScores": [
{
"type": "RoundHoleData",
"points": 2
},
{
"type": "RoundHoleData",
"points": 13
},
{
"type": "RoundHoleData",
"points": 3
},
{
"type": "RoundHoleData",
"points": 1
},
{
"type": "RoundHoleData",
"points": 21
}
]
}
}
Now, the tricky part of this is that I only want the average of points for holeScores[0] of all documents with this playerid and this groupid.
Actually, the best solution would be collecting all documents with playerid and groupid and create a new array with the average of holeScores[0], holeScores[1], holeScores[2]... But if I only can get one array key at the time, that would be OK to :-)
Here is what I am thinking but not quit sure how to put it together:
var allScores = dbCollection('scores').aggregate(
{$match: {"data.groupId": groupId, "playerId": playerId}},
{$group: {
_id: playerId,
rounds: { $sum: 1 }
result: { $sum: "$data.scoreTotals.points" }
}}
);
Really hoping for help with this issue and thanks in advance :-)
You can use $unwind with includeArrayIndex to get index and then use $group to group by that index
dbCollection('scores').aggregate(
{
$match: { "data.playerId": "XXXXXXXXXXXXX", "data.groupId": "XXXXXXXXXXXXXX" }
},
{
$unwind: {
path: "$data.holeScores",
includeArrayIndex: "index"
}
},
{
$group: {
_id: "$index",
playerId: { $first: "data.playerId" },
avg: { $avg: "$data.holeScores.points" }
}
}
)
You can try below aggregation
db.collection.aggregate(
{ "$match": { "data.groupId": groupId, "data.playerId": playerId }},
{ "$group": {
"_id": null,
"result": {
"$sum": {
"$arrayElemAt": [
"$data.holeScores.points",
0
]
}
}
}}
)

Resources