Mongodb how to use $elemMatch to limit results - mongodb

My problem is: I have a structure similar to this:
{
id: 1,
participants: [
{ name: "joe", status: 0 },
{ name: "james", status: 2}
],
content: "mongomongo"
}
{
id: 2,
participants: [
{ name: "joe", status: 1 },
{ name: "jordan", status: 3}
],
content: "dongodongo"
}
What I want to do is run a query with almost the same effect as this:
db.find({ '_id': { $in: someArray}}, { participants: {$elemMatch: {'name': someName }}}
I would specify an array of object IDs for the $in, and then I would provide an username. What happens is that it would give me back both objects, but the participants array only has the entry that the $elemMatch found:
{
id: 1,
participants: [
{ name: "joe", status: 0 }
]
}
{
id: 2,
participants: [
{ name: "joe", status: 1 }
]
}
This is what I want, but the part that I DON'T want is that it leaves out other fields (namely content). How can I adjust the query so it that still returns one field in the participants array, but also returns the other fields such as content?
Thank you in advance!

Actually found the solution to my question. Just had to tweak the original query I used. I had confused the projection field and the options field since I was using Mongoose to manage mongodb interactions.
Here's the query that works:
db.find({ '_id': { $in: someArray}}, { participants: {$elemMatch: {'name': someName }}, content: 1, [anything] : 1});

EDIT:
I misunderstood the original post and example. If the only other field you are worried about returning is 'content', then you could add it to the projection argument like so:
db.collection.find(
{
'_id': {
$in: someArray
}
},
{
'participants': {
$elemMatch: {
'name': someName
}
},
'content' : 1
}
)
Hope this helps!

Related

MongoDB Select documents by the value of a specific field

MongoDB - userModel (simplified)
{
_id: "exampleid1",
name: "examplename1",
following: [
{
_id: "exampleid2",
name: "examplename2"
},
{
_id: "exampleid3",
name: "examplename3"
}
],
followers: [
{
_id: "exampleid4",
name: "examplename4"
},
{
_id: "exampleid5",
name: "examplename5"
}
]
}
Hey,
I'm building a social media platform and I need to get from the database only the users that I follow.
I tried the query below, but gives me nothing:
User.find( { "followers": { _id: "exampleid1"} } )
Try to divide the document selection ({_id: "exampleid1"}) from the projection ({followers: 1, _id: 0}):
db.collection.find({
_id: "exampleid1"
},
{
followers: 1,
_id: 0
})
See how it works on the playground example

Extract list of data from collections

I have a list of collection like the following in mongodb
{ _id: ObjectId("6087feeef467a4320883daf3"),
name: 'group 1',
admins: ['adam', 'ken']
}
{ _id: ObjectId("2087feeef467a4320883daf3"),
name: 'group 2',
admins: ['rebecca']
}
{ _id: ObjectId("9987feeef467a4320883daf3"),
name: 'group 3',
admins: []
}
I need to extract out all the admins, the end result would be something like this:
[
'admin',
'ken',
'rebecca'
]
How can i do that, i stuck at this part:
db.data.find({ admins: { $exists: true, $not: {$size: 0} } })
This will show all collections, but i just need the list
The proper way of doing so using single query is unwinding the array of admins and adding them to a new set.
db.collection.aggregate([
{
$unwind: "$admins"
},
{
$group: {
_id: null,
adminsSet: {
$addToSet: "$admins"
}
}
}
])
The end result will be the following so you could extract your flattened array:
[
{
"_id": null,
"adminsSet": [
"ken",
"rebecca",
"adam"
]
}
]
Mongo playground ref: https://mongoplayground.net/p/LFYsqHdTucN
You can do something like this
db.data.aggregate([
{$unwind: "$admins"},
{$project:{_id:0}}
]).map(x => x.admins);
Result
[
"adam",
"ken",
"rebecca"
]

Sort data based on given id first

Suppose I want to sort the data based on the current city first and then the remaining country data. Is there any way I achieve that in MongoDB?
Example
[
{ id: 2, name: 'sdf' },
{ id: 3, name: 'sfs' },
{ id: 3, name: 'aaa' },
{ id: 1, name: 'dsd' },
];
What I want as an outcome is the data with id 3 at first and the remaining other.
like
[
{ id: 3, name: 'sfs' },
{ id: 3, name: 'aaa' },
{ id: 1, name: 'dsd' },
{ id: 2, name: 'sdf' },
];
It's just a example,
My actual requirement is to sort the data based on certain category first and then the remaining one
It's not possible within mongodb but you could first fetch the documents from the db and then sort them in Javascript (or whatever other language you're using to present the data).
On a side note, having duplicate values in the "id" field is not a good practice and defies the definition of id itself.
There is no straight way to sort condationaly in MongoDB, as per your example you can try aggregation query,
$facet to separate result for both types of documents
first, to get id: 3 documents
second, to get id is not 3 documents and sort by id in ascending order
$project and $concatArrays to concat both arrays in siquance
$unwind deconstruct all array
$replaceRoot to replace all object to root
db.collection.aggregate([
{
$facet: {
first: [
{ $match: { id: 3 } }
],
second: [
{ $match: { id: { $ne: 3 } } },
{ $sort: { id: 1 } }
]
}
},
{
$project: {
all: { $concatArrays: ["$first", "$second"] }
}
},
{ $unwind: "$all" },
{ $replaceRoot: { newRoot: "$all" } }
])
Playground

MongoDB: Find and then modify the resulting object

is it possible in MongoDB to find some objects that match a query and then to modify the result without modifying the persistent data?
For example, let
students = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 22 },
{ name: "Carol", age: 19 },
{ name: "Dave", age: 18}
]
Now, I want to query all students that are younger than 20 and in the search result, I just want to replace "age: X" with "under20: 1" resulting in the following:
result = [
{ name: "Carol", under20: 1 },
{ name: "Dave", under20: 1}
]
without changing anything in the database.
Sure, it is possible to get the result and then call a forEach on it, but that sounds so inefficient because I have to rerun every object again, so I'm searching for an alternative. Or is there no one?
A possible solution would be to use an aggregation pipline with a $match followed by a $project:
db.students.aggregate(
[
{
$match: { age: { $lt: 20 } }
},
{
$project:
{
_id: false,
name: true,
under20: { $literal: 1 }
}
}
]);
The $literal: 1 is required as just using under20: 1 is the same as under20: true, requesting that field under20 be included in the result: which would fail as under20 does not exist in the document produced by the match.
Or to return all documents in students and conditionally generate the value for under20 a possible solution would be to use $cond:
db.students.aggregate(
[
{
$project:
{
_id: false,
name: true,
under20:
{
$cond: { if: { $lt: [ "$age", 20 ] }, then: 1, else: 0 }
}
}
}
]);

MongoDB: match non-empty doc in array

I have a collection structured thusly:
{
_id: 1,
score: [
{
foo: 'a',
bar: 0,
user: {user1: 0, user2: 7}
}
]
}
I need to find all documents that have at least one 'score' (element in score array) that has a certain value of 'bar' and a non-empty 'user' sub-document.
This is what I came up with (and it seemed like it should work):
db.col.find({score: {"$elemMatch": {bar:0, user: {"$not":{}} }}})
But, I get this error:
error: { "$err" : "$not cannot be empty", "code" : 13030 }
Any other way to do this?
Figured it out: { 'score.user': { "$gt": {} } } will match non-empty docs.
I'm not sure I quite understand your schema, but perhaps the most straight forward way would be to not have an "empty" value for score.user ?
Instead purposely not have that field in your document if it has no content?
Then your query could be something like ...
> db.test.find({ "score" : { "$elemMatch" : { bar : 0, "user" : {"$exists": true }}}})
i.e. looking for a value in score.bar that you want (0 in this case) checking for the mear existence ($exists, see docs) of score.user (and if it has a value, then it'll exist?)
editied: oops I missed the $elemMatch you had ...
You probably want to add an auxiliary array that keeps track of the users in the user document:
{
_id: 1,
score: [
{
foo: 'a',
bar: 0,
users: ["user1", "user2"],
user: {user1: 0, user2: 7}
}
]
}
Then you can add new users atomically:
> db.test.update({_id: 1, score: { $elemMatch: {bar: 0}}},
... {$set: {'score.$.user.user3': 10}, $addToSet: {'score.$.users': "user3"}})
Remove users:
> db.test.update({_id: 1, score: { $elemMatch: {bar: 0}}},
... {$unset: {'score.$.user.user3': 1}, $pop: {'score.$.users': "user3"}})
Query scores:
> db.test.find({_id: 1, score: {$elemMatch: {bar: 0, users: {$not: {$size: 0}}}}})
If you know you'll only be adding non-existent users and removing existent users from the user document, you can simplify users to a counter instead of an array, but the above is more resilient.
Look at the $size operator for checking array sizes.
$group: {
_id: '$_id',
tasks: {
$addToSet: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.id', ''],
},
'',
],
},
then: '$$REMOVE',
else: {
id: '$tasks.id',
description: '$tasks.description',
assignee: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.assignee._id', ''],
},
'',
],
},
then: undefined,
else: {
id: '$tasks.assignee._id',
name: '$tasks.assignee.name',
},
},
},
},
},
},
},