Can $in and $or replace each other in MongoDB?
db.restaurants.find(
{
"borough" :{$in :["Staten Island","Queens","Bronx","Brooklyn"]}},
{
"restaurant_id" : 1,
"name":1,"borough":1,
"cuisine" :1
}
);
db.restaurants.find(
{
"borough": "Bronx" ,
$or : [
{ "cuisine" : "American " },
{ "cuisine" : "Chinese" }
]
}
);
Here I observe that both these queries require us to choose from some options:
Does it make sense to replace $in in the first query with $or as follows:
db.restaurants.find(
{ $or: [{ borough: 'Staten Island',
borough: 'Queens',
borough: 'Bronx',
borough: 'Brooklyn' }],
{ _id : 1,
name: 1,
borough : 1,
cuisine : 1
}
})
Are $in and $or replaceable?
Update:
I tried to use two queries in a hope to get identical results:
Why is the second query selecting two rows of status 'D' only?
> db.inventory.find( {status : { $in: [ 'A', 'D'] }}, {item:1, status: 1})
{ "_id" : ObjectId("5eb67598bee5213484d45087"), "item" : "journal", "status" : "A" }
{ "_id" : ObjectId("5eb67598bee5213484d45088"), "item" : "notebook", "status" : "A" }
{ "_id" : ObjectId("5eb67598bee5213484d45089"), "item" : "paper", "status" : "D" }
{ "_id" : ObjectId("5eb67598bee5213484d4508a"), "item" : "planner", "status" : "D" }
{ "_id" : ObjectId("5eb67598bee5213484d4508b"), "item" : "postcard", "status" : "A" }
>
>
> db.inventory.find( {$or: [ {status: 'A', status: 'D'} ] }, {item:1, status: 1})
{ "_id" : ObjectId("5eb67598bee5213484d45089"), "item" : "paper", "status" : "D" }
{ "_id" : ObjectId("5eb67598bee5213484d4508a"), "item" : "planner", "status" : "D" }
>
From their official documentation itself or-versus-in :
When using $or with that are equality checks for the
value of the same field, use the $in operator instead of the $or
operator.
If you've docs like below :
[
{
"price": 100
},
{
"price": 200
},
{
"price": 300
},
{
"price": 400
},
{
"price": 500
}
]
If you wanted to get docs where price is equal to 100 or 500, query like :
db.collection.find({ price: { $in: [ 100, 500 ] } })
By doing like above, query is simple & clean. You can also use $or instead of $in but why would you loose shorthand notation and try to make your query look bulky by adding more objects of same field again and again ?
By default if you wanted to do logical OR on two different operators you would use $or, But when to use $or on same field :
db.collection.find({ $or: [ { price: { $lt: 200 } }, { price: { $gt: 400 } } ] })
As like above when you've multiple different conditions to match on same field you'll use it.
These two queries yield same result when executed but when you use $in - if input values are straight numbers or can be strings or other types where input values will exactly match with values of price field in docs, but when you use $or you're checking for different conditions on same field.
Test : mongoplayground
Related
With the following document:
{
"_id" : "123",
"firstArray" : [
{
"_id" : "456",
"status" : "open",
"nestedArray" : [
{
"_id" : "100",
"quantity" : 10
},
{
"_id" : "101",
"quantity" : 10
},
{
"_id" : "102",
"quantity" : 10
}
},
{
"_id" : "789",
"status" : "open",
"nestedArray" : [
{
"_id" : "200",
"quantity" : 10
},
{
"_id" : "201",
"quantity" : 10
},
{
"_id" : "202",
"quantity" : 10
}
}
]
}
How can I update the quantity by 20 of the nested ID 101 element and pull the one with the ID 201 from the same MongoDB query ?
I am trying to do that in Java with $set and $pull operator and I'm stuck with the following error:
[BulkWriteError{index=0, code=40, message='Update created a conflict
at 'firstArray.0.nestedArray'', details={}}]
MongoDB doesn’t allow multiple operations on the same property in the same update call. This means that the two operations must happen in two individual queries.
The first solution is you can write 2 seperate queries for both the operations.
The second solution is you can try update with aggregation pipeline, starting from MongoDB 4.2,
$map to iterate loop of firstArray
$filter to iterate loop of nestedArray and remove _id: "201" record
$map to iterate loop of above filtered nestedArray
$cond check condition if _id: "101" then return new quantity otherwise return current
$mergeObjects to merge current object with updated properties
db.collection.update(
{ "firstArray.nestedArray._id": "101" },
[{
$set: {
firstArray: {
$map: {
input: "$firstArray",
in: {
$mergeObjects: [
"$$this",
{
nestedArray: {
$map: {
input: {
$filter: {
input: "$$this.nestedArray",
cond: { $ne: ["$$this._id", "201"] }
}
},
in: {
_id: "$$this._id",
quantity: {
$cond: [
{ $eq: ["$$this._id", "101"] },
20,
"$$this.quantity"
]
}
}
}
}
}
]
}
}
}
}
}
])
Playground
Can someone please help me with this query ??
Query >>> Find all warehouses that keep item "Planner" and having in-stock quantity less than 20
This is the sample document in the items collection of the Inventory database :
{
"_id" : ObjectId("6067640da9a907175caaca34"),
"id" : 101,
"name" : "Planner",
"status" : "A",
"height" : 12,
"tags" : [
"mens",
"womens"
],
"warehouses" : [
{
"name" : "Phoenix",
"quantity" : 25
},
{
"name" : "Quickshift",
"quantity" : 15
},
{
"name" : "Poona",
"quantity" : 10
}
]
}
This is what I have tried doing :
db.items.find({"name":"Planner","warehouses.quantity":{"$lt":20}},{"warehouses":1,"_id":0}).pretty()
But it gives me the result as
{
"warehouses" : [
{
"name" : "Phoenix",
"quantity" : 25
},
{
"name" : "Quickshift",
"quantity" : 15
},
{
"name" : "Poona",
"quantity" : 10
}
]
}
Demo - https://mongoplayground.net/p/IpD5ypWSZyt
Use aggregation query
db.collection.aggregate([
{ $match: { "name": "Planner" } },
{ $unwind: "$warehouses" }, // break into individual documents
{ $match: { "warehouses.quantity": { $lt: 20 } } }, // query the data
{ $group: { _id: "_id", warehouses: { $push: "$warehouses" } } } // join them back
])
Demo - https://mongoplayground.net/p/pdTY0IkIqgF
Use $elemMatch only if you think there will be only 1 array element matching per document
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
db.collection.find({
"name":"Planner",
"warehouses": { "$elemMatch": { "quantity": { $gt: 20 } } }
},
{ "warehouses.$": 1})
https://docs.mongodb.com/manual/reference/method/db.collection.find/#find-projection
Hey guys I'm trying to update a value that is in an array in MongoDB, am trying to use the mongo queries but is not working, am following the next documentation from Mongo doc
this one is the array:
{
"_id" : "605e3d9b9ef219de662113d0",
"distribution" : [
{
"floor" : 1,
"rooms" : [
{
"number" : 301,
"beds" : [
{
"number" : 818,
"status" : "Vacante Sucia"
},
{
"number" : 819,
"status" : "Vacante Sucia"
}
],
"gender" : "M"
},
{
"number" : 302,
"beds" : [
{
"number" : 820,
"status" : "Vacante Sucia"
},
{
"number" : 821,
"status" : "Vacante Sucia"
}
],
"gender" : "M"
},
{
"number" : 303,
"beds" : [
{
"number" : 822,
"status" : "Vacante Sucia"
},
{
"number" : 823,
"status" : "Vacante Sucia"
}
],
"gender" : "M"
}
]
}
],
"name" : "Meteorologia",
"code" : "METEO"
}
this one is the query that is using in mongoDB to update the status from the bed 801, room 301, floor 1:
in the arrayFilters i specified the index 0 to get the first element of the arrays
db.getCollection('establishments_copy').findAndModify({query: { code: "METEO"}, update: { $set: { "distribution.$[i].rooms.$[i].beds.$[i].status": "TEST"}}, arrayFilters: [{"i.rooms": 0, "i.beds": 0, "i.status": 0}]})
they are returning me the collection but without changes, is possible to navigate validating not for the index just with the values.
for example using the next query:
db.getCollection('establishments_copy').findAndModify({query: { code: 'METEO', distribution: { $elemMatch: { floor: 1, 'rooms.number': 301, 'rooms.beds.number': 818}}}, update: { $set: { '...': 'CHANGED'}}})
thanks!
You just need to create separate condition as per sub document's field name,
f for floor field in distribution array
r for number field in rooms array
b for number field in beds array
db.getCollection('establishments_copy').findAndModify({
query: { code: "METEO"},
update: {
$set: {
"distribution.$[f].rooms.$[r].beds.$[b].status": "TEST"
}
},
arrayFilters: [
{ "f.floor": 1 },
{ "r.number": 301 },
{ "b.number": 818 }
]
})
Playground
Since $lte and $gt does a lexicographical comparison if the value is in string format, I have tried to convert the values to int or double. But I am not getting the expected results. For this case i want the query to execute to 2 as only 2 documents are present with the "userDL" field value less than or equal to 5. Please note: I cannot use aggregate and I have to use db.collection.find(query).count(). Please help me here with the query.
Collection content:
{ "_id" : "14_0", "data" : [ { "dn" : "subdata=data_a", "userUL" : "0", "objectClass" : "NEWDATA", "userDL" : "5" } ] }
{ "_id" : "15_0", "data" : [ { "dn" : "subdata=data_b", "userUL" : "0", "objectClass" : "NEWDATA", "userDL" : "3" } ] }
{ "_id" : "16_0", "data" : [ { "dn" : "subdata=data_c", "userUL" : "0", "objectClass" : "NEWDATA", "userDL" : "9" } ] }
1st, I tried normal querying which gave me unsupported conversion error:
db.testcol.find({ $expr: { $lte: [ { $toDouble: "$data.userDL" }, 5 ] } }).count()
2020-09-18T06:26:37.010+0530 E QUERY [js] uncaught exception: Error: count failed: {
"ok" : 0,
"errmsg" : "Unsupported conversion from array to double in $convert with no onError value",
"code" : 241,
"codeName" : "ConversionFailure"
} :
_getErrorWithCode#src/mongo/shell/utils.js:25:13
DBQuery.prototype.count#src/mongo/shell/query.js:376:11
#(shell):1:1
The above error i fixed in the below query but i am using aggregate. This works fine in terms of comparison and outputs true/false.
db.testcol.aggregate([{ $project:{ adjustedGrades:{$map:{input: "$data.userDL",as: "grade",in: {$lte : [{ $toInt: "$$grade" },5] } }}}}])
{ "_id" : "14_0", "adjustedGrades" : [ true ] }
{ "_id" : "15_0", "adjustedGrades" : [ true ] }
{ "_id" : "16_0", "adjustedGrades" : [ false ] }
I tried using the above aggregate inside find query but either i am not getting any output or the wrong output(i am getting 3 instead to 2)
db.testcol.find({ $expr: { $lte: [ {$map:{input: "$data.userDL",as: "grade",in: { $toInt: "$$grade" } }} , 5 ] } })
no output here
db.testcol.find({$expr: {$map: {input: "$data.userDL",as: "grade",in: {$lte : [{ $toInt: "$$grade" },5] } }}}).count()
3
[UPDATE]
> db.testcol.aggregate([{ $project:{ adjustedGrades:{$map:{input: "$data.userDL",as: "grade",in: {$lte : [{ $toInt: "$$grade" },5] } }}}}, {$match: {adjustedGrades: {$eq: true}}}])
{ "_id" : "14_0", "adjustedGrades" : [ true ] }
{ "_id" : "15_0", "adjustedGrades" : [ true ] }
I want to able able to do this using db.collection.find(). Can someone help here? I don't actually need the adjustedGrades field. I want these 2 documents to be returned when I do db.testcol.find(aggregatequery). Finally I want to be able to do db.testcol.find(aggregatequery).count().
PS: I have to use find() and count() only.
An example of using $reduce to iterate the array and test each element:
db.collection.find({
$expr: {
$reduce: {
input: "$data",
initialValue: false,
in: {
$or: [
"$$value",
{$lte: [{$toDouble: "$$this.userDL"}, 5]}
]
}
}
}
})
Playground
Note that using $expr in this way will not be able to use an index.
If it is permissible to modify the documents, updating so that each userDL is numeric will permit both querying directly and indexing that field.
MongoDB Enterprise mongos> db.foo.insert({a:'20',b:[{c:'20'}]})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise mongos> db.foo.find({$expr:{$gte:[{$convert:{input:'$a',to:'int'}},30]}})
MongoDB Enterprise mongos> db.foo.find({$expr:{$gte:[{$convert:{input:'$a',to:'int'}},3]}})
{ "_id" : ObjectId("5f64632b24059b0e3ee998b8"), "a" : "20", "b" : [ { "c" : "20" } ] }
This should work:
db.getCollection("myCollection").find({ "data.0.userDL": { $lte: "5" } }, {}).count()
I have a mongodb document like this,
{
"_id" : ObjectId("4e8ae86d08101908e1000001"),
"eId": 101,
"about": "test",
"tags" : [
{"name": "jana"},
{"name":"bala"},
{"name":"jk"},
{"name":"charles"}
]
}
I need to find all matched array elements, where the name matched with given array.
db.coll.find({"tags": {"$elemMatch": {"name": {"$in": [/^jana/i, /^charles/i] }}}})
for this query i got the following result
{
"_id" : ObjectId("4e8ae86d08101908e1000001"),
"tags" : [
{
"name" : "jana"
}
]
}
$elemMatch query only return the first matched element, but i want all the matched array element like this,
{
"_id" : ObjectId("4e8ae86d08101908e1000001"),
"tags" : [
{
"name" : "jana"
},
{
"name" : "charles"
}
]
}
Is it possible to get the result like this?
note: i don't want any others fields, I want only the matched array elements along with _id
You can use MongoDB Aggregation Pipeline:
db.coll.aggregate([
{'$unwind': '$tags'},
{'$match':
{"tags.name":
{"$in": [/^jana/, /^charles/i] }
}
},
{'$group':
{
'_id': '$_id',
'tags':
{'$push': '$tags'}
}
}
])
Result : -
{
"result" : [
{
"_id" : ObjectId("5538b214706a90c718f75a41"),
"tags" : [
{
"name" : "jana"
},
{
"name" : "charles"
}
]
}
],
"ok" : 1
}