I'm trying to query one of my mongodb collection that looks like this:
> db.collection.find()
{name: "foo", things: [{stuff:"banana", value: 1}, {stuff:"apple", value: 2}, {stuff:"strawberry", value: 3}]}
{name: "bar", things: [{stuff:"banana", value: 4}, {stuff:"pear", value: 5}]}
...
My goal is to list all the object that have the things field containing an element with stuff=banana but no stuff=apple
I tried something like this:
db.transactions.find({
"things": {
$elemMatch: {
"stuff": "banana",
$ne: {
"stuff": "apple"
}
}
}
)
But it's not working. Any ideas?
The below query will get the list of all documents that have the things field containing an element with stuff=banana but no stuff=apple:
db.test.find({"$and":[{"things.stuff":"banana"}, {"things.stuff":{$ne:"apple"}}]})
Use the $not and $and operators:
db.collection.find({
$and:[
{"things": {$elemMatch: { "stuff": "banana" }}},
{"things": {$not: {$elemMatch: { "stuff": "apple"}}}}
]
});
Use $in and $nin to include and exclude
db.transactions.find({
"things.stuff": {
$in: ["banana"],
$nin:["apple"]
}
})
Related
So, this question stems from another question I asked ( MongoDB update with conditional addToSet/pull )
Essentially in that SO question, I found that I couldn't use the typical $in syntax if my array is an array of objects.
Typical syntax for conditionally updating a field:
$cond: {
$in: [{element}, $arr], // Need to provide the element Exactly, including _id if one exists
$setDifference: [$arr, [{element}]]
$concactArrays: [$arr, [{element}]]
}
The reason $in doesn't work is that if _id is auto-generated, you won't be able to match it for the $in or the $setDifference. The solution I arrived at uses $filter, but is fragile in that the array needs to at least be initialed a default [] (otherwise $size will error out on a null array)
On to my question.
Is there a way to make $in or $elemMatch work in this scenario?
I couldn't get the following to work (says $elemMatch is an unrecognized expression:
$cond: {
$in: [arr: {$elemMatch: {element}}, $arr],
Full Example
Mongo Object
{_id: 1, message: 'text1'}
{_id: 2, message: 'text2', reactions: [{_id: autoGen1, user: 'bob', reaction:'👍'},
{_id: autoGen2, user: 'bob', reaction:'👎'}
{_id: autoGen3, user: 'meg', reaction:'😵'}]}
Update Query
db.collection.update(
{},
[
{
$set: {
reactions: {
$cond: [
{
$in: [
{
reactions: { $elemMatch: {user: "bob", reaction: "good"}}
},
"$reactions"
]
},
"1",
"2"
]
}
}
}
])
https://mongoplayground.net/p/Ig0TKMAMNkI
Data structure
{
users: [
{id: "aaa"},
{id: "bbb"},
{id: "ccc"}
]
},
{
users: [
{id: "111"},
{id: "222"},
{id: "333"}
]
},
array: ["111", "222", "333"]
I want to get the document where every "id" matches my array, like $in does. But I don't want matches where only two of three matches. So in this case, the query should return the second document.
One way:
Query an array for an element
const cursor = db.collection('inventory').find({
users: [{id: "111"},{id: "222"},{id: "333"}]
});
https://docs.mongodb.com/manual/tutorial/query-arrays/#query-an-array-for-an-element
Related Q:
MongoDB Find Exact Array Match but order doesn't matter
const userIds = ["abc", "123", ...]
I found this query to solve my problem.
{
$and: [
{users: {$elemMatch: {id: {$in: userIds}}}},
{users: {$size: userIds.length}}
]
}
I have one document as follows:
{
user: 'hvt07',
photos: [
{
link: 'http://link.to.com/image1.jpg',
isPrivate: true
},
{
link: 'http://link.to.com/image2.jpg',
isPrivate: false
}
]
}
I want to get all photos which are with:
isPrivate: false
I am using the following query:
db.collection_name.find({ photos:{ $elemMatch:{isPrivate: false} } }).pretty()
I have also tried:
db.collection_name.find({'photos.isPrivate': true}).pretty()
But both return all elements in the array even ones that are set as :
isPrivate: true
Please suggest.
Aggregation is the solution.
You need to deconstruct the photos array using the $unwind operator. Next use the $match to select documents where isPrivate: false. The $group you can regroup your documents by _id and reconstruct your photos array using the $push operator
db.collection_name.aggregate(
[
{$unwind: "$photos"},
{$match: {"photos.isPrivate": false}},
{$group: {"_id": {"id": "$_id", "user": "$user"}, photos: {$push: "$photos"}}}
{$project: {"_id": "$_id.id", "user": "$_id.user", "photos": 1, "_id": 0 }}
]
)
You can use $elemMatch to the result projection like this
db.collection_name.find(
{ photos:{ $elemMatch:{isPrivate: false} } }, //1
{photos:{$elemMatch:{isPrivate: false}}}) //2
Find all documents that have at least a photo which is not private
Select only photos that are not private for the documents found.
I've got a series of mongoDB documents that follow this structure:
{
"_id": "myId",
"myArray": [
{"foo": "bar"},
{"foo": "baz"}
]
}
Another might look like this:
{
"_id": "myOtherId",
"myArray": [
{"foo": "bar"},
{"foo": "baz"}
]
}
I need to add a property to any of the array elements where foo=bar only if the id=myOtherId ... in other words, so my second document will look like this (but the first document won't be updated):
{
"_id": "myOtherId",
"myArray": [
{"foo": "bar","foot":"bart"},
{"foo": "baz"}
]
}
I know that the first argument of my update would be something like this:
db.coll.update({$and:[{"_id":"myOtherId"},{"myArray.foo":"bar"}]}, // but what comes in the $set?
How do I then compose the $set argument so that {"$foot":"bart"} is added only where I need it? Is there a way to capture the index value of the array item, or a better way to handle it?
You don't need to use $and in this case as there's always an implicit AND between multiple terms in a query object. You can also use the $ positional update operator in your $set to identity the matched myArray element from your query to update:
db.coll.update(
{_id: "myOtherId", "myArray.foo": "bar"},
{$set: {"myArray.$.foot": "bart"}})
Building up on the answer from #JohnnyHK.
In case there are multiple elements in array myArray with {"foo": "bar"}
{
"_id": "myOtherId",
"myArray": [
{"foo": "bar"},
{"foo": "baz"},
{"foo": "bar"}
]
}
and all the matching elements have to be udpated then we need to use arrayFilters with update command.
db.coll.update(
{_id: "myOtherId", "myArray.foo": "bar"},
{$set: {"myArray.$[elem].foot": "bart"}},
{
multi: true,
arrayFilters: [ { "elem.foo": "bar" } ]
})
Here's a sample document of my Mongo DB structure:
{ _id: 1
records: [{n: "Name", v: "Kevin"},
{n: "Age", v: "100"},
...,
{n: "Field25", v: "Value25"} ]
}
To search on all documents having Name of "Kevin" and an Age of "100", I'm using $all with $elemMatch. I need to use $elemMatch's for an exact sub-document match of n: "Name" and v: "Kevin", as well as $all since I'm querying on an array.
db.collection.find({"records" : { $all: [
{$elemMatch: {n: "Name", v: "Kevin"},
{$elemMatch: {n: "Age", v: "100"}
]}})
However, the $all operator is inefficient when the first $elemMatch argument is non-selective, i.e. there are many documents that match this field.
The Mongo Docs elaborate:
In the current release, queries that use the $all operator must scan
all the documents that match the first element in the query array. As
a result, even with an index to support the query, the operation may
be long running, particularly when the first element in the array is
not very selective.
Is there a better alternative for my queries?
I would suggest an radical change to your structure, and this would simplify the queries. Sorry if this change is not possible, but without more data I do not see any problem with that:
{ _id: 1
records: [{"Name":"Kevin",
"Age":"100",
...,
"Field25":"Value25"} ]
}
And the query:
db.collection.find("records":{$elemMatch:{Name:"Kevin","Age":100}})
This will return all the objects that have a record (assuming there are more records, for example if they are students of a class) matching all of the conditions mentioned.
But in case you want to have just a document per _id, forget about the records array:
{ _id: 1,
"Name":"Kevin",
"Age":"100",
...,
"Field25":"Value25"} ]
}
and the query:
db.collection.find({Name:"Kevin","Age":100})
Hope this helps.
I think this should be the best solution.
db.collection.find({},
{
records: {
$filter: {
input: "$records",
as: "record",
cond: {
$or: [
{
$eq: [
"$$record.v",
"Kevin"
]
},
{
$eq: [
"$$record.v",
"100"
]
}
]
}
}
}
})
solution link: https://mongoplayground.net/p/aQAw0cG9Ipm