How do I query by field order in MongoDB? - 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'
} })

Related

Delete elements from array in mongodb [duplicate]

Here is array structure
contact: {
phone: [
{
number: "+1786543589455",
place: "New Jersey",
createdAt: ""
}
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
Here I only know the mongo id(_id) and phone number(+1786543589455) and I need to remove that whole corresponding array element from document. i.e zero indexed element in phone array is matched with phone number and need to remove the corresponding array element.
contact: {
phone: [
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
I tried with following update method
collection.update(
{ _id: id, 'contact.phone': '+1786543589455' },
{ $unset: { 'contact.phone.$.number': '+1786543589455'} }
);
But it removes number: +1786543589455 from inner array object, not zero indexed element in phone array. Tried with pull also without a success.
How to remove the array element in mongodb?
Try the following query:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
It will find document with the given _id and remove the phone +1786543589455 from its contact.phone array.
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.
You can simply use $pull to remove a sub-document.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Collection.update({
_id: parentDocumentId
}, {
$pull: {
subDocument: {
_id: SubDocumentId
}
}
});
This will find your parent document against given ID and then will remove the element from subDocument which matched the given criteria.
Read more about pull here.
In Mongoose:
from the document:
To remove a document from a subdocument array we may pass an object
with a matching _id.
contact.phone.pull({ _id: itemId }) // remove
contact.phone.pull(itemId); // this also works
See Leonid Beschastny's answer for the correct answer.
To remove all array elements irrespective of any given id, use this:
collection.update(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from a specific document:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from all documents:
collection.updateMany(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
Given the following document in the profiles collection:
{ _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
The following operation will remove all items from the votes array that are greater than or equal to ($gte) 6:
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
After the update operation, the document only has values less than 6:
{ _id: 1, votes: [ 3, 5 ] }
If you multiple items the same value, you should use $pullAll instead of $pull.
In the question having a multiple contact numbers the same use this:
collection.update(
{ _id: id },
{ $pullAll: { 'contact.phone': { number: '+1786543589455' } } }
);
it will delete every item that matches that number. in contact phone
Try reading the manual.

Update or append to a subcollection in mongo

I have a collection containing a subcollection. In one request, I would like to update a record in the subcollection or append to it if a match doesn't exist. For a bonus point I would also like this update to be a merge rather than an overwrite.
A crude example:
// Schema
{
subColl: [
{
name: String,
value: Number,
other: Number,
},
];
}
// Existing record
{
_id : 123,
subColl: [
{name: 'John',
value: 10,
other: 20}
]
}
// example
const update = { _id: 123, name: 'John', other: 1000 };
const { _id, name, other } = update;
const doc = await Schema.findById(_id);
const idx = doc.subColl.findIndex(({ name: nameInDoc }) => nameInDoc === name);
if (idx >= 0) {
doc.subColl[idx] = { ...doc.subColl[idx], other };
} else {
doc.subColl.push({ name, other });
}
doc.save();
Currently I can achieve this result by pulling the record, and doing the update/append manually but I am assuming that achieving it with a pure mongo query would be much faster.
I have tried:
Schema.findOneAndUpdate(
{ _id: 123, 'subColl.name': 'John' },
{ $set: { 'subColl.$': [{ name: 'John', other: 1000 }] } }
)
but this won't handle the append behaviour and also doesn't merge the object with the existing record, rather it overwrites it completely.
I am not sure is there any straight way to do this in single query,
Update with aggregation pipeline starting from MongoDB v4.2,
$cond to check name is in subColl array,
true condition, need to merge with existing object, $map to iterate loop, check condition if matches condition then merge new data object with current object using $mergeObjects
false condition, need to concat arrays, current subColl array and new object using $concatArrays
const _id = 123;
const update = { name: 'John', other: 1000 };
Schema.findOneAndUpdate(
{ _id: _id },
[{
$set: {
subColl: {
$cond: [
{ $in: [update.name, "$subColl.name"] },
{
$map: {
input: "$subColl",
in: {
$cond: [
{ $eq: ["$$this.name", update.name] },
{ $mergeObjects: ["$$this", update] },
"$$this"
]
}
}
},
{ $concatArrays: ["$subColl", [update]] }
]
}
}
}]
)
Playground

Add new document into an array if there is no same field value in any other array embedded document

I have a document
{
_id:1,
persons:[
{name:"Jack", age:10},
{name:"Ma",age:20}
]
}
I want to push new document {name:"Ho",age;22} in persons array. But there is a condition-
the new document will be added to array if name name:"Ho" does not exist in other embedded array documents. If exists, {name:"Ho",age;22} will not be added to persons array.
What is the way?
You can use the $not operator along with $elemMatch to query the document that doesn't a person with that name.
Your update would look like this
db.collection.update({
_id: 1,
persons: {
$not: { $elemMatch: { name: "Ho" } }
}
}, { $push: { persons: { name: "Ho", age: 30 } } })
This works for me
db.collection('test').update({
_id: 1,
"persons.name" : {$ne:"Ho"}
}, { $push: {persons: { name: "Ho", age: 30 } } })

Remove element from array aggregate

How can I loop through an array and remove a specific element based on a field.
Here is the layout I have - it is in a collection called cases:
** The collection contains a companyID, cases [Array], lastModified **
So I will have to use an aggregate to unwind the cases and then search for the casenumber where it equals '17':
db.cases.aggregate([
{ $match: { companyID: 218}},
{ $unwind: '$cases' },
{ $match: {'cases.casenumber': '17'} }
])
This returns:
But now I want to delete just that specific item.
Thanks.
You can use of an updateMany request. First argument is the matching condition, the second is the action.
$pull is a special keyword that will remove matching elements from arrays.
db.collection.updateMany({
companyID: 218,
}, {
$pull: {
cases: {
casenumber: 17,
},
},
})
https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/
https://docs.mongodb.com/manual/reference/operator/update/pull/
Example from the doc :
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )

How to remove array element in mongodb?

Here is array structure
contact: {
phone: [
{
number: "+1786543589455",
place: "New Jersey",
createdAt: ""
}
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
Here I only know the mongo id(_id) and phone number(+1786543589455) and I need to remove that whole corresponding array element from document. i.e zero indexed element in phone array is matched with phone number and need to remove the corresponding array element.
contact: {
phone: [
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
I tried with following update method
collection.update(
{ _id: id, 'contact.phone': '+1786543589455' },
{ $unset: { 'contact.phone.$.number': '+1786543589455'} }
);
But it removes number: +1786543589455 from inner array object, not zero indexed element in phone array. Tried with pull also without a success.
How to remove the array element in mongodb?
Try the following query:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
It will find document with the given _id and remove the phone +1786543589455 from its contact.phone array.
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.
You can simply use $pull to remove a sub-document.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Collection.update({
_id: parentDocumentId
}, {
$pull: {
subDocument: {
_id: SubDocumentId
}
}
});
This will find your parent document against given ID and then will remove the element from subDocument which matched the given criteria.
Read more about pull here.
In Mongoose:
from the document:
To remove a document from a subdocument array we may pass an object
with a matching _id.
contact.phone.pull({ _id: itemId }) // remove
contact.phone.pull(itemId); // this also works
See Leonid Beschastny's answer for the correct answer.
To remove all array elements irrespective of any given id, use this:
collection.update(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from a specific document:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from all documents:
collection.updateMany(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
Given the following document in the profiles collection:
{ _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
The following operation will remove all items from the votes array that are greater than or equal to ($gte) 6:
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
After the update operation, the document only has values less than 6:
{ _id: 1, votes: [ 3, 5 ] }
If you multiple items the same value, you should use $pullAll instead of $pull.
In the question having a multiple contact numbers the same use this:
collection.update(
{ _id: id },
{ $pullAll: { 'contact.phone': { number: '+1786543589455' } } }
);
it will delete every item that matches that number. in contact phone
Try reading the manual.