MongoDB: projection to match multiple - javascript

I am having a bit of trouble here. So I want to show a user's profile. The user belongs to groups. The logged in user can see details of any groups they have in common. Here is some example data
{
_id: "1234",
battletag: "Fake#1234",
guilds: [{
name: "Lok'Narosh!",
rank: 4,
roles: ['casual']
}, {
name: "Warlords of Draenor",
rank: 2,
roles: ['PvP', 'raider']
}, {
name: "Lok'Tar Ogar!",
rank: 3,
roles: ['raider']
}],
}
I can get the current user's groups and reduce it to ['Lok'Narosh!', 'Warlords of Draenor'], meaning that Lok'tar Ogar should be omitted from the results.
The main problem I am coming across is that most operations I know only return the first result. For example, with $elemMatch:
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
Is there a way that I can filter this list to contain all matching elements against a list of elements?

You can use aggregate:
$unwind operator to deconstruct 'guilds' field.
Apply criteria with $match
Reconstruct array.
db.getCollection('yourColl').aggregate({$unwind:"$guilds"},{$match:{"guilds.rank":{$gte:2.0}}},{$group:{ "_id":"$_id", "battletag":{$first:"$battletag"},"guilds":{$addToSet:"$guilds"}}})

Related

Why is $elemMatch return the first document, instead of the all matches documents?

I'm trying to execute a query that returns all the documents that match based on query parameters.
I have the following schema:
_id: ObjectId('631b875491b16c38eecfa4e9')
brandName: "Nick"
categories: Array
products: Array
0: Object
productName: "Vans Shoes dsds Old Skool"
description: "amazing Shoes."
categoryId: ObjectId('62f3eaff3ded19dcce71081e')
price: 240
numberOfBuyers: 0
_id: ObjectId(631b875491b16c38eecfa4ec)
1: Object
2: Object
3: Object
__v: 0
The following query should give me all the documents that match, but it returns only the first document:
const products = await Brand.find(
{
_id: brandId
},
{
products: {
$elemMatch: {
categoryId: categoryId,
price: {
$gte: minPrice,
$lte: maxPrice
}
}
}
})
What is wrong?
You are querying on "Brand" documents. This means your query tells Mongoose: if one of the products is matching (categoryId and price), return the (whole) Brand document.
In order to retrieve only specific elements of this array, you should include your $elemMatch object in the projection step of your find call:
const products = await Brand.find({
_id: brandId
}, {
//include other properties you want to include in your output document
products: {
$elemMatch: {
categoryId: "62f3eaff3ded19dcce71081e",
price: 240
}
}
}
})
Update after comment
Your products array will only contain the first element that was matched. This is intended behaviour (as described here: cs/manual/reference/operator/projection/elemMatch/):
Definition
$elemMatch
The
$elemMatch
operator limits the contents of an field from the query results to contain only the first element matching the
$elemMatch
condition.
In order to get several results you should probably use an aggregation pipeline using $unwind and $group.

Check if two property of a nested document are equal in mongodb

I have a user.expenses collection like this
{
userId: ObjectId("62f332b93753ac926ff6ac62"),
expenses: [
{
name: 'electricity',
assigned: 400,
given: 400,
},
{
name: 'restaurant',
assigned: 2100,
given: 0,
}
]
}
I will get userId and expenses.name(electricity) from the request. I need to check if the user.expenses collection has any expenses document whose name is electricity and assigned is not equal to given.
I used $elemMatch and could check if there are any embedded documents whose name is electricity.
db.user.expenses.find({
userId: ObjectId('62f332b93753ac926ff6ac62'),
expenses: {
$elemMatch: {
name: 'electricity',
},
},
});
EDIT
I also tried to use $where. But it only can be applied to the top-level document.
Query
you cant do it with query operators because you want to reference a field but you can do it with aggregate operators and $filter
filter the array and keep only if electricity and assigned!=given
keep the document if at least one sub-document was electricity with assigned!=given
Playmongo
aggregate(
[{"$match":
{"$expr":
{"$ne":
[{"$filter":
{"input": "$expenses",
"cond":
{"$and":
[{"$eq": ["$$this.name", "electricity"]},
{"$ne": ["$$this.assigned", "$$this.given"]}]}}}, []]}}}])

How can I populate a nested related collection with SailsJS?

I have 3 collections,
Users,
Clan, which has many Clanmembers
Clanmembers, which has many Users
So when I do
Clan.find().populate('clanmembers');
It works fine and returns the result but then I get something like:
{ clanmembers:
[ { role: 'Entry',
owner: 2,
member: 2,
id: 1,
createdAt: null,
updatedAt: null } ],
owner:
{ username: 'tester',
email: 'tester',
about: 'This is\n\na test hello\n\n\n\n\n\n\n\n\nhiiii',
profileurl: '',
avatar: 'asd.jpg',
rank: '1',
id: 2,
createdAt: '2015-10-25T10:15:20.000Z',
updatedAt: '2015-10-25T21:31:47.000Z' },
clanname: 'testset',
clantag: 'testin',
id: 2,
createdAt: '2015-10-26T01:07:02.000Z',
updatedAt: '2015-10-26T01:07:02.000Z' }
What I'd like is also to be able to populate the clanmembers array's owners so that I can get usernames and what not of the clanmembers, how can I populate this nested array? Or is it not possible and I'll have to loop/find each one by their ID. Any information would be great thank you.
This can't be done as of now, although it has been in the talks for quite some time.
As you mentioned, you'll have to do the population manually. But you can avoid making separate calls for each subdocument.
What I usually do is collect the required ids in an array and run a single query to the database, then maintain a map of ids to documents which makes it pretty convenient to use in multiple places.

Array of values

For example I have n doc in MongoDB collection
Films = new Mongo.Collection('films');
Film.insert({name: 'name n', actor: 'John'}); // *n
And I want to show array with only name values
var names = ['name 1', 'name 2',..,'name n'];
Any idea how to do it ?
And guys , ols write in comments correct title value of my question, to help other guys to find it, thx :)
You didn't provided any criteria for grouping name as an array.
You can use following query to get all names:
db.collection.distinct("name")
OR you can use MongoDB's aggregation to get all name by grouping them with some condition, if required. The query will be like following:
db.collection.aggregate({
$group: {
_id: null, //add condition if require
name: {
$push: "$name"
}
}
}, {
$project: {
name: 1,
_id: 0
}
})
If you want only distinct name then replace $push with $addToSet.

MongoDB aggregate merge two different fields as one and get count

I have following data in MongoDB:
[{id:3132, home:'NSH', away:'BOS'}, {id:3112, home:'ANA', away:'CGY'}, {id:3232, home:'MIN', away:'NSH'}]
Is it possible to get total game count for each team with aggregate pipeline?
desired result:
[{team: 'NSH', totalGames: 2}, {team:'MIN', totalGames: 1}, ...}]
i can get each on seperately to their own arrays with two aggregate calls:
[{$group: {_id: "$home", gamesLeft: {$sum: 1}}}]
and
[{$group: {_id: "$away", gamesLeft: {$sum: 1}}}]
resulting
var homeGames = [ { _id: 'NSH', totalGames: 1 }, { _id: 'SJS', totalGames: 2 }, ...]
var awayGames = [ { _id: 'NSH', totalGames: 1 }, { _id: 'SJS', totalGames: 4 }, ...]
But i really want to get it working with just one query. If not possible what would be the best way to combine these two results in to one using javascript?
After some puzzling, I found a way to get it done using an aggregate pipeline. Here is the result:
db.games.aggregate([{
$project: {
isHome: { $literal: [true, false] },
home: true,
away: true
}
}, {
$unwind: '$isHome'
}, {
$group: {
_id: { $cond: { if: '$isHome', then: '$home', else: '$away' } },
totalGames: { $sum: 1 }
}
}
]);
As you can see it consists of three stages. The first two are meant to duplicate each document into one for the home team and one for the away team. To do this, the project stage first creates a new isHome field on each document containing a true and a false value, which the unwind stage then splits into separate documents containing either the true or the false value.
Then in the group phase, we let the isHome field decide whether to group on the home or the away field.
It would be nicer if we could create a team field in the project step, containing the array [$home, $away], but mongo only supports adding array literals here, hence the workaround.

Categories

Resources