MongoDB filter with composite object - mongodb

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/

Related

MongoDB Comparison query Object Array Operators

Suppose that there is the applicants collection in a MongoDB database with these documents:
[
{
name: "Tom Hanks",
age: 42,
email: "tom.hanks#example.com"
job:{
"data engineer": 7,
"professor": 3
}
},
{
name: "Ken Smith",
age: 36,
email: "ken.smith#example.com"
job:{
"electronics engineer" : 10,
"database administrator" : 5
}
}
]
I want to write a query that retrieves the applicants who have some experience in the database field.
Tried: db.applications.find({ 'job': { $all: [ '.data.'] } })
However it's not working.
Can someone help, please.
I'm not sure how efficient this is or if its what you're looking for, but you can have a go at expanding the object to an array and then regex matching on each field using the $objectToArray operator and then regex matching on the keys
{
$project: {
jobMatch: {
$objectToArray: "$job"
}
}
},
{
$match: {
"jobMatch.k": {
$regex: "data"
}
}
}
])
You can go deeper and add a value match by doing values greater than a certain number with $and, let me know if you found it useful

Update a document and upsert a subdocument in a single query

How do I update an item in the parent document and upsert a subdocument in a single query?
This is my example schema.
const ExampleSchema = new Schema({
user_count: {
type: String,
default: 0
},
users: [
{
id: {
type: Schema.Types.ObjectId,
ref: "users",
unique: true
},
action: {
type: Boolean
}
}
],
});
I am trying to add +1 to user_count and upsert a document to the users array in a single query.
const result = await Example.updateOne(
{
_id: id,
},
{
$set: {
"user_count": user_count++,
"users.$.id": req.user.id,
"users.$.action": true
}
},
{ upsert: true }
);
I have tried the above code, but got the following error.
[0] 'The positional operator did not find the match needed from the query.',
[0] [Symbol(mongoErrorContextSymbol)]: {} }
I'm not familiar with mongoose, so I will take for granted that "user_count": user_count++ works.
For the rest, there are two things that won't work:
the $ operator in "users.$.id": req.user.id, is known as the positional operator, and that's not what you want, it's used to update a specific element in an array. Further reading here: https://docs.mongodb.com/manual/reference/operator/update/positional/
the upsert is about inserting a full document if the update does not match anything in the collection. In your case you just want to push an element in the array right?
In this case I guess something like this might work:
const result = await Example.updateOne(
{
_id: id,
},
{
$set: {
"user_count": user_count++
},
$addToSet: {
"users": {
"id": req.user.id,
"action": true
}
}
}
);
Please note that $push might also do the trick instead of $addToSet. But $addToSet takes care of keeping stuff unique in your array.
db.collection.findOneAndUpdate({_id: id}, {$set: {"user_count": user_count++},$addToSet: {"users": {"id": req.user.id,"action": true}}}, {returnOriginal:false}, (err, doc) => {
if (err) {
console.log("Something wrong when updating data!");
}
console.log(doc);
});

How do I query by field order in MongoDB?

Let's say I have the following documents in my MongoDB collection outfits:
{ _id: ..., shirt: { type: "T-shirt", size: "M" } }
{ _id: ..., shirt: { type: "Long-sleeve", size: "M" } }
{ _id: ..., shirt: { type: "T-shirt", size: "S" } }
{ _id: ..., shirt: { size: "M", type: "Long-sleeve" } }
{ _id: ..., shirt: { type: "T-shirt", size: "L" } }
How do I get all documents where the size-field is the first field in the embedded shirt-document?
I would also be satisfied with a solution that simply tells me which documents where size is before type in shirt.
Why?
When querying an embedded document in MongoDB, the order of the fields matter. For example:
db.outfits.find({ "shirt": { size: "M", type: "Long-sleeve" } })
will get me just one outfit, even though there are two outfits with a shirt of the type Long-sleeve in size M.
Basically I need to run a query to standardize the field order in my collection, and optimally I would like to fix the few ones that aren't in the right order instead of re-inserting everything.
Reference: https://softwareengineering.stackexchange.com/questions/319468/why-does-mongodb-check-for-order-of-keys-when-matching-embedded-documents
For MongoDB > 3.4.4 you can do this:
db.outfits.aggregate({
$project: {
firstField: { // create a new field "firstField"
$arrayElemAt: [{ // get the first element of...
$objectToArray: "$shirt" // ... the "shirt" subdocument represented as an array
}, 0]
}
}
}, {
$match: {
"firstField.k": "size" // just return the ones we are interested in
}
})
Also note that you can query like this which will eliminate the problem with the order:
db.outfits.find({ "shirt.size": "M", "shirt.type": "Long-sleeve" })
I ended up using the following:
db.outfits.find({ $where: function() {
return Object.keys(this.shirt)[0] === 'type'
} })
But I also found the more archaic:
db.outfits.find({ $where: function() {
return tojsononeline(this)[64] === 't'
} })

Using $in but matching on specific fields

I'm attempting to use $in to query a mongoDB collection. The issue I'm running up against is that the arrays are arrays of JSON objects, not just arrays of a single field. So the full object looks like this:
{
items: [
{
name: "Jacket",
other_field: "1234"
}
]
}
So if I have an array that looks like this:
[{name:"Jacket", other_field:"3456"}]
I'd like to query for documents that contain within their items array any object which has a matching name field, ignoring all other fields. Is that possible?
You can use $elemMatch for this.
Example -
db.users.find({items: { $elemMatch: {name:'Jacket'} } } )
For more understanding, you can refer to - $elemMatch
As per your question there should be two possibilities if your documents structure looks like below
{
items: [
{
name: "Jacket",
other_field: "1234"
}
]
}
In this case you use following query
db.collectionName.find({"items.name":"Jacket"},{"items":1})
so above query will return only matching items.name matching criteria.
And another more possibilities if your documents structure looks like nested as below
{
items: [
{
name: "Jacket",
other_field: "1234"
},
{
name: "Jacket",
other_field: "12345"
},
{
name: "Jacket2",
other_field: "1234e"
}
]
}
In this case should use aggregate function as below
db.collectionName.aggregate({
"$unwind": "$items"
},
{
"$match": {
"items.name": "Jacket"
}
},
{
"$project": {
"_id": 0,
"items.name": 1,
"items.other_field": 1
}
})
if I understood your requirement correctly then could could ask
db.collectionName.find({'items.name': 'Jacket'})
MongoDB automatically does the "descending down" into the arrays and pick the ones that match.
fricke

mongodb return person if comments

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.