I have a query to return posts and person content types.
var query = {
type: {
$in: ["post", "person"]
},
}
But I only want to return person if person has any comments, but posts always.
var query = {
type: {
$in: ["post", "person"]
},
// somehow make this only for person
// comments: {"$exists": true}
}
Is this possible or should I use map reduce?
Thanks.
var query = {
$or: [
{ type: "post" },
{ type: "person", comments: { $exists: true }},
]
}
This should work, I've just tested it:
db.things.find({$or: [ { type: "post" },
{ type: "person", comments: { $exists: true }} ]})
I've initially thought that it is not possible to use same field name in query twice, but it was just an issue with some drivers. here you can find discussion about this.
Also map/reduce usual not acceptable for real time queries, because it pretty slow.
Related
Imagine having a MongoDB collection with this "schema":
mycollection: {
name: String
treatments: [{
type: ObjectId,
colors: ObjectId[]
}]
}
and have a couple of record like this
{
name: "Foo",
treatments:[
{ type: ObjectId('123456'), colors: [ObjectId('654321')] }
]
},
{
name: "Bar",
treatments:[
{ type: ObjectId('123456'), colors: [ObjectId('789012')] }
]
}
If I try to filter those record using:
{ "treatments.type": ObjectId('123456'), "treatments.colors": { $in: [ObjectId('654321')] } }
I expect that returns only the record with name "Foo", but instead it returns both records, treating the filter like an OR. Even
{ $and: [{ "treatments.type": ObjectId('123456') }, { "treatments.colors": { $in: [ObjectId('654321')] } ] }
returns the same.
Any help or clarification would be greatly appreciated ❤️
In this case you need to use elemMatch in mongodb this will find the element from array which has both the condition true
{ treatments: { $elemMatch: { type: ObjectId('123456'), colors: { $in: [ObjectId('654321')] } } } }
otherwise it is default behaviour in mongodb that when specifying conditions on more than one field nested in an array of documents, you can specify the query such that either a single document meets these condition or any combination of documents (including a single document) in the array meets the conditions.
check this link for further explanation https://docs.mongodb.com/manual/tutorial/query-array-of-documents/
I am having some issue writing a find/aggregate mongo query where my requirement is to get all the documents but having condition like:
Suppose I have 2 documents:
{
_id: 5ccaa76939d95d395791efd2,
name: 'John Doe',
email: 'john.doe#foobar.com',
private: true
}
{
_id: 5ccaa76939d95d395791efd2,
name: 'Jane Doe',
email: 'jane.doe#foobar.com',
private: false
}
Now the query I am trying to get my head around is if the field private is true then when I query I must get all documents except email fields not included if private is true, like this:
{
_id: 5ccaa76939d95d395791efd2,
name: 'John Doe',
private: true
}
{
_id: 5ccaa76939d95d395791efd2,
name: 'Jane Doe',
email: 'jane.doe#foobar.com',
private: false
}
Tried $redact, $cond, $$PRUNE, $$DESCEND in aggregate() as well as came across $$REMOVE (looks like it is newest feature) but unable to get the required output. Please help me out with the Query
You can use $$REMOVE to remove a field from returned documents.
db.collection.aggregate([
{ "$addFields": {
"email": {
"$cond": [
{ "$eq": ["$private", true] },
"$$REMOVE",
"$email"
]
}
}}
])
MongoPlayground
Thank you Anthony Winzlet, his solution worked like a charm.
If anyone faces same problem and requires to include more than 1 fields, I am doing so by writing this method:
function condition(privateFieldLimitationArray, publicFieldLimitationArray) {
const condition = {};
privateFieldLimitationArray.map((d, i) => {
condition[d] = {
"$cond": [
{ "$eq": ["$private", true] },
"$$REMOVE",
publicFieldLimitationArray.includes(d) ? '$$REMOVE' : '$'+d
]
}
});
return condition;
}
Then, you can use the above function like:
const privateLimitationArray = ['updatedAt', 'createdAt', 'email', 'lname', 'friendslist', '__v'];
const publicLimitationArray = ['updatedAt', 'createdAt', '__v'];
YourModel.aggregate([
{
$match: {
// Your query to find by
}
}, {
"$addFields": condition(privateLimitationArray, publicLimitationArray)
}
])
.then(result => {
// handle result
})
.catch(error => {
// handle error
});
I am trying to remove an entry in an array that is a sub property of a document field.
The data for a document looks like this:
{
_id: 'user1',
feature: {
enabled: true,
history: [
{
_id: 'abc123'
...
}
]
},
...
}
For some reason I have not been able to remove the element using $pull and I'm not sure what is wrong.
I've looked at the official docs for $pull, this well-known answer, as well this one and another.
I have tried the following query
db.getCollection('userData').update({ _id:'user1' }, {
$pull: {
'feature.history': { _id: 'abc123' }
}
})
and it has no effect. I've double-checked _id and it is a proper match. I've also tried filtering based on the same entry, thinking I need to target the data I'm trying to remove:
db.getCollection('userData')
.update({ _id: 'user1', 'feature.history': { _id: 'abc123' }, { ... })
So far no luck
You need to cast your id to mongoose ObjectId
db.getCollection('userData').update(
{ "_id": "user1" },
{ "$pull": { "feature.history": { "_id": mongoose.Types.ObjectId(your_id) } }
})
db.getCollection('userData').update({ _id:'user1', "feature.history._id" : "abc123" }, {
$pull: {
'feature.history.$._id': 'abc123'
}
})
I'm seeing some odd behavior with Mongoose 4.11.
The issue is that I'm getting documents returned that I don't think should be. See below for specifics after the code.
(Sorry for the long code snippets... it's tough when talking about DBs cause you need model, data, query, and results, right?)
Here's the model:
const UserSchema = new Schema({
firstName: {
type: String
},
lastName: {
type: String
}
groups: [
{
name: String,
members: [
{
user: {
type: Schema.Types.ObjectId, ref: 'User'
},
hasAccepted: {
type: Boolean,
default: false
}
}
]
}
]
});
the query:
const query = {
'groups.members.user':aUserID, // '5a712ca2db684000102e51b7' in this case
'groups.members.hasAccepted':'false'
};
const fields = 'firstName lastName groups.name';
User.find(query, fields, function(err, result){
res.send(result);
});
and the data set (leaving out unnecessary name and id fields):
[
{
"groups": [
{
"name": "Test Group",
"members": [
{
"user": {
"$oid": "5a712ca2db684000102e51b7"
},
"hasAccepted": false
},
{
"user": {
"$oid": "5a7141fb5b19bb0011e27658"
},
"hasAccepted": false
}
]
}
]
},
{
"groups": [
{
"name": "Test Group 2",
"members": [
{
"user": {
"$oid": "5a712ca2db684000102e51b7"
},
"hasAccepted": true
},
{
"user": {
"$oid": "5a7141fb5b19bb0011e27658"
},
"hasAccepted": false
}
]
}
]
}
]
I would expect to get back only the Test Group, and not Test Group 2 because my query is looking only for groups with the user id matched and hasAccepted property false.
However, I'm getting both groups returned.
I've tried a number of thing to confirm that the query is setup correctly otherwise.
For example if I remove the user id I'm looking for from Test Group 2 and change the query to:
const query = {
'groups.members.user':aUserID, // '5a712ca2db684000102e51b7' in this case
'groups.name':'Test Group 2'
};
I get nothing returned (this confirms it's not an "OR" operator in play)
If I change the query to:
const query = {
'groups.members.hasAccepted':true
};
I get only Test Group 2 returned (this confirms my hasAccepted criteria is working)
It's when I add both together that it stops making sense.
(wish I could vote up anyone who reads this far :) )
Anyone have any ideas as to what could be going on here?
Thanks.
You need to use $elemMatch twice as the embedded array depth is 2, to match correct embedded array element,
Query should be
const query = {
groups : {
$elemMatch : { members : { $elemMatch : {user : aUserID, hasAccepted : false} } }
}
};
I am using mongoose. I want to create a document chat with an array users (including userId1, userId2), if I do not find it:
This is how I do:
ChatModel.findOneAndUpdate(
{ users: { $all: [userId1, userId2] }},
{ $setOnInsert: {
users: [userId1, userId2]
}},
{ upsert: true })
.exec()
.catch(err => console.log(err));
But I got the error:
MongoError: cannot infer query fields to set, path 'users' is matched
twice
This is Chat Schema:
{
users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
createdAt: { type: Date, default: Date.now }
}
How can I do it correctly? Thanks
I use this as the condition
{
"users": {
$all: [
{"$elemMatch": userId1},
{"$elemMatch": userId2}
]
}......
}
I know this already has an answer but to hopefully save someone else some time, I had to do this:
{
"users": {
$all: [
{ $elemMatch: { $eq: mongoose.Types.ObjectId(userId1) }},
{ $elemMatch: { $eq: mongoose.Types.ObjectId(userId2) }}
]
}......
}
Modifications from accepted answer:
The $eq was needed just like Dave Howson said in his comment on the accepted answer.
mongoose.Types.ObjectId was needed because I guess the _id property on my schema instance was a string.
There is a workaround for this issue:
db.foo.update({a:{$all:[{$elemMatch:{$eq:0}},{$elemMatch:{$eq:1}}]}},{$set:{b:1}},{upsert:true})
This will match when a is an array with both 0 and 1 in it and it will upsert otherwise.
From: https://jira.mongodb.org/browse/SERVER-13843?focusedCommentId=2305903&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-2305903