mongodb query in two different arrays - arrays

I have been going through the mongodb querying inside an array. I have gotten the example they give to work for me for querying a single array. But how would this work if I had two different arrays and wanted to combine them into a single query? for example item1 and item2 were two different arrays.
# example given in mongodb document
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
# example of query of what I am trying achieve query two arrays
db.inventory.find( { item1: { $in: [ item1_value ] } { item2: { $in: [ item2_value ] } } )
mongodb reference doc: https://docs.mongodb.com/manual/reference/operator/query/in/
I should also mention that I am using mongodb to get the idea of what the command would look like, but ultimately this command should work for pymongo as this command will be executed via pymongo.
# correct query example as given by Moshe
db.inventory.find({
$or: [
{ item1: { $in: [ item1_value ] }},
{ item2: { $in: [ item2_value ] }}
]
});

MongoDB "OR" SYNTAX:
{ $or: [ { <expression1> }, { <expression2> }, ... , { <expressionN> } ] }
In your case:
db.inventory.find({
$or: [
{ item1: { $in: [ item1_value ] },
{ item2: { $in: [ item2_value ] }
]
});

Related

Finding documents in mongodb collection by order of elements index of array field

Array field in collection:
"fruits": [ "fruits": [ "fruits": [
{"fruit1": "banana"}, {"fruit2": "apple"}, {"fruit3": "pear"},
{"fruit2": "apple"}, {"fruit4": "orange"}, {"fruit2": "apple"},
{"fruit3": "pear"}, {"fruit1": "banana"}, {"fruit4": "orange"},
{"fruit4": "orange"} {"fruit3": "pear"} {"fruit1": "banana"}
]
I need to find those documents in collections, where "banana" signed before "apple". Does mongodb allows to compare elements in array just like :
if (fruits.indexOf('banana') < fruits.indexOf('apple')) return true;
Or maybe there is any other method to get result i need?
MongoDB's array query operations do not support any positional search as you want.
You can, however, write a $where query to do what you want:
db.yourCollection.find({
$where: function() {
return (this.fruits.indexOf('banana') < this.fruits.indexOf('apple'))
}
})
Be advised though, you won't be able to use indexes here and the performance will be a problem.
Another approach you can take is to rethink the database design, if you can specify what it is you're trying to build, someone can give you specific advise.
One more approach: pre-calculate the boolean value before persisting to DB as a field and query on true / false.
Consider refactoring your schema if possible. The dynamic field names(i.e. fruit1, fruit2...) make it unnecessarily complicated to construct a query. Also, if you require frequent queries by array index, you should probably store your array entries in individual documents with some sort keys to facilitate sorting with index.
Nevertheless, it is achievable through $unwind and $group the documents again. With includeArrayIndex clause, you can get the index inside array.
db.collection.aggregate([
{
"$unwind": {
path: "$fruits",
includeArrayIndex: "idx"
}
},
{
"$addFields": {
fruits: {
"$objectToArray": "$fruits"
}
}
},
{
"$addFields": {
"bananaIdx": {
"$cond": {
"if": {
$eq: [
"banana",
{
$first: "$fruits.v"
}
]
},
"then": "$idx",
"else": "$$REMOVE"
}
},
"appleIdx": {
"$cond": {
"if": {
$eq: [
"apple",
{
$first: "$fruits.v"
}
]
},
"then": "$idx",
"else": "$$REMOVE"
}
}
}
},
{
$group: {
_id: "$_id",
fruits: {
$push: {
"$arrayToObject": "$fruits"
}
},
bananaIdx: {
$max: "$bananaIdx"
},
appleIdx: {
$max: "$appleIdx"
}
}
},
{
$match: {
$expr: {
$lt: [
"$bananaIdx",
"$appleIdx"
]
}
}
},
{
$unset: [
"bananaIdx",
"appleIdx"
]
}
])
Mongo Playground

Mongodb: Query the size of nested arrays

I have the following Schema:
Schema({
caller_address: {
type: String,
required: true,
},
traces: [[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Call',
}]]
});
And I would like to retrieve only the objects that have traces with the Calls amount bigger than a specified number. In other words, the size of at least one nested array of traces should be bigger than a specified number.
I'm trying to use $elemMatch and $size, but no success. For now, I have this code:
CallerTraces.find({ 'traces' : { $elemMatch: { $size : { $gt: minTraceSize } }}})
Where minTraceSize is an int.
Could you guys help me?
I would really appreciate it!
Thanks for the sample data. My answer will be a raw MQL solution, not a mongoose solution, so some translation will be required.
I was able to insert two documents based on your comments in your post. I had to change the ObjectId of one of the two sample documents because your samples had the same primary key value and was generating a duplicate key exception.
Insert Sample Data
db.CallerTraces.insert(
{
"_id": ObjectId("6175e7ecc62cff004462d4a6"),
"traces": [
[
ObjectId("6175e7ecc62cff004462d4a4")
]
],
"caller_address": "0x4e204793bc4b8acee32edaf1fbba1f3ea45f7990"
})
db.CallerTraces.insert(
{
"_id": ObjectId("6175e7ecc62cff004462d4a7"),
"traces": [
[
ObjectId("6175e7ecc62cff004462d4a4"),
ObjectId("6175e7ecc62cff004462d4a4")
],
[
ObjectId("6175e7ecc62cff004462d4a4")
]
],
"caller_address": "0x4e204793bc4b8acee32edaf1fbba1f3ea45f7990"
})
If I want to find records having more than 0 items in the array traces I can issue the following:
Find more than zero traces
db.CallerTraces.find({ $expr: { $gt: [ { $size: "$traces" }, 0 ] } })
This returns the following:
Enterprise replSet [primary] barrydb> db.CallerTraces.find({ $expr: { $gt: [ { $size: "$traces" }, 0 ] } })
[
{
_id: ObjectId("6175e7ecc62cff004462d4a6"),
traces: [ [ ObjectId("6175e7ecc62cff004462d4a4") ] ],
caller_address: '0x4e204793bc4b8acee32edaf1fbba1f3ea45f7990'
},
{
_id: ObjectId("6175e7ecc62cff004462d4a7"),
traces: [
[
ObjectId("6175e7ecc62cff004462d4a4"),
ObjectId("6175e7ecc62cff004462d4a4")
],
[ ObjectId("6175e7ecc62cff004462d4a4") ]
],
caller_address: '0x4e204793bc4b8acee32edaf1fbba1f3ea45f7990'
}
]
Find more than 1 trace
If instead I want to find more than one trace I simply alter the query slightly:
db.CallerTraces.find({ $expr: { $gt: [ { $size: "$traces" }, 1 ] } })
... and this returns with the following results:
Enterprise replSet [primary] barrydb> db.CallerTraces.find({ $expr: { $gt: [ { $size: "$traces" }, 1 ] } })
[
{
_id: ObjectId("6175e7ecc62cff004462d4a7"),
traces: [
[
ObjectId("6175e7ecc62cff004462d4a4"),
ObjectId("6175e7ecc62cff004462d4a4")
],
[ ObjectId("6175e7ecc62cff004462d4a4") ]
],
caller_address: '0x4e204793bc4b8acee32edaf1fbba1f3ea45f7990'
}
]
Conclusion
When attempting to evaluate the length of the array within the query processor we must elect to use the $eval option as the syntax for MQL does not consider your use case. The $eval is somewhat of a catch-all option for things that do not fit nicely in the MQL framework.
UPDATE #1
OP introduced additional requirements. Rather than look at the count of the array, we must consider the count of the array within the array (nested inner array). Since the find() method with the $expr cannot evaluate nested arrays we must instead use the aggregation framework and unwind the outer array. This example stores the original form in a new field called original then replaces root after all the evaluation is complete. Since unwinding can result in duplicates in the pipeline we finalize with a $group to suppress duplicates.
Solution
db.CallerTraces.aggregate([
{
$addFields: {
"original._id": "$_id",
"original.traces": "$traces",
"original.caller_address": "$caller_address"
}
},
{
$unwind: "$traces"
},
{
$match: { $expr: { $gt: [ { $size: "$traces" }, 1 ] } }
},
{
$replaceRoot: { newRoot: "$original" }
},
{
$group:
{
_id: "$_id",
traces: { "$first": "$traces" },
caller_address: { "$first": "$caller_address" }
}
}
])

MongoDB: How to take multiple fields within a document and output their values into an array (as a new field)?

MongoDB: 4.4.9, Mongosh: 1.0.4
I have a MongoDB collection full of documents with monthly production data as separate fields (monthlyProd1, monthlyProd2, etc.). Each field is one month's production data, and the values are an object data type.
Document example:
_id: ObjectId("314e0e088f183fb7e699d635")
name: "documentName"
monthlyProd1: Object
monthlyProd2: Object
monthlyProd3: Object
...
I want to take all the months and put them into a single new field (monthlyProd) -- a single array of objects.
I can't seem to access the fields with the different methods I've tried. For example, this gets close to doing what I want:
db.monthlyProdData.updateMany({},
{ $push: { "monthlyProd": { $each: [ "$monthlyProd1", "$monthlyProd2", "$monthlyProd3" ] } } }
)
...but instead of taking the value / object data from each field, like I had hoped, it just outputs a string into the monthlyProd array ("$monthlyProd1", "$monthlyProd2", ...):
Actual output:
monthlyProd: Array
0: "$monthlyProd1"
1: "$monthlyProd2"
2: "$monthlyProd3"
...
Desired output:
monthlyProd: Array
0: Object
1: Object
2: Object
...
I want the data, not a string! Lol. Thank you for your help!
Note: some months/fields may be an empty string ("") because there was no production. I want to make sure to not add empty strings into the array -- only months with production / fields that have an object data type. That being said, I can try figuring that out on my own, if I can just get access to these fields' data!
Try this one:
db.collection.updateMany({}, [
// convert to k-v Array
{ $set: { monthlyProd: { $objectToArray: "$$ROOT" } } },
{
$set: {
monthlyProd: {
// removed not needed objects
$filter: {
input: "$monthlyProd",
cond: { $not: { $in: [ "$$this.k", [ "name", "_id" ] ] } }
// or cond: { $in: [ "$$this.k", [ "monthlyProd1", "monthlyProd2", "monthlyProd3" ] ] }
}
}
}
},
// output array value
{ $project: { monthlyProd: "$monthlyProd.v" } }
])
Mongo playground
Thank you to #Wernfried for the original solution to this question. I have modified the solution to incorporate my "Note" about ignoring any empty monthlyProd# values (aka months that didn't have any production), so that they are not added into the final monthlyProd array.
To do this, I added an $and operator to the cond: within $filter, and added the following as the second expression for the $and operator (I used "" and {} to take care of the empty field values if they are of either string or object data type):
{ $not: { $in: [ "$$this.v", [ "", {} ] ] } }
Final solution:
db.monthlyProdData2.updateMany({}, [
// convert to k-v Array
{ $set: { monthlyProd: { $objectToArray: "$$ROOT" } } },
{
$set: {
monthlyProd: {
// removed not needed objects
$filter: {
input: "$monthlyProd",
cond: { $and: [
{ $not: { $in: [ "$$this.k", [ "name", "_id" ] ] } },
{ $not: { $in: [ "$$this.v", [ "", {} ] ] } }
]}
}
}
}
},
// output array value
{ $project: { monthlyProd: "$monthlyProd.v", name: 1 } }
])
Thanks again #Wernfried and Stackoverflow community!

Mongo DB find value in array of multiple nested arrays

I need to check if an ObjectId exists in a non nested array and in multiple nested arrays, I've managed to get very close using the aggregation framework, but got stuck in the very last step.
My documents have this structure:
{
"_id" : ObjectId("605ce5f063b1c2eb384c2b7f"),
"name" : "Test",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("6058e94c3994d04d2863962e")
],
"variations" : [
{
"varName" : "Var1",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
{
"varName" : "Var2",
"attrs" : [
ObjectId("60591791d4d41d0a6817d22a"),
ObjectId("60591791d4d41d0a6817d255"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
],
"storeId" : "9acdq9zgke49pw85"
}
Let´s say I need to check if this if this _id exists "6058e94c3994d04d28639616" in all arrays named attrs.
My aggregation query goes like this:
db.product.aggregate([
{
$match: {
storeId,
},
},
{
$project: {
_id: 0,
attrs: 1,
'variations.attrs': 1,
},
},
{
$project: {
attrs: 1,
vars: '$variations.attrs',
},
},
{
$unwind: '$vars',
},
{
$project: {
attr: {
$concatArrays: ['$vars', '$attrs'],
},
},
},
]);
which results in this:
[
{
attr: [
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
6058e94c3994d04d2863962e,
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f
]
},
{
attr: [
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
6058e94c3994d04d28639624,
6058e94c3994d04d28639627,
6058e94c3994d04d28639628,
6058e94c3994d04d2863963e
]
}
]
Assuming I have two products in my DB, I get this result. Each element in the outermost array is a different product.
The last bit, which is checking for this key "6058e94c3994d04d28639616", I could not find a way to do it with $group, since I dont have keys to group on.
Or with $match, adding this to the end of the aggregation:
{
$match: {
attr: "6058e94c3994d04d28639616",
},
},
But that results in an empty array. I know that $match does not query arrays like this, but could not find a way to do it with $in as well.
Is this too complicated of a Schema? I cannot have the original data embedded, since it is mutable and I would not be happy to change all products if something changed.
Will this be very expensive if I had like 10000 products?
Thanks in advance
You are trying to compare string 6058e94c3994d04d28639616 with ObjectId. Convert the string to ObjectId using $toObjectId operator when perform $match operation like this:
{
$match: {
$expr: {
$in: [{ $toObjectId: "6058e94c3994d04d28639616" }, "$attr"]
}
}
}

How to $match an item from the list

I am trying to display the items, that have specific name brand.
This is how that field looks like:
"brand" : [
[
"Samsung",
"Iphone",
"Huawei"
]
]
Tried that query, but I get 0 results:
db.collection.aggregate([{$match: { brand: "Samsung" }}])
Any ideas, what's wrong?
Your data model is an array of arrays so you have to deal with two dimensions. You can use $map along with $in first and then use $anyElementTrue to see if there's any sub-array matching your condition:
db.collection.aggregate([
{
$match: {
$expr: {
$anyElementTrue: {
$map: {
input: "$brand",
in: { $in: [ "Samsung", "$$this" ] }
}
}
}
}
},
{
$project: {
_id: 1,
color: 1
}
}
]);
Mongo Playground
EDIT: use $project to display only certain fields

Resources