So I have a document and model that is structured like this
{
"name":"root",
"children":[
{
"_id":1,
"name":"A",
"children":[
{
"_id":2,
"name":"A child1",
"children":[
]
}
]
},
{
"_id":3,
"name":"B",
"children":[
{
"_id":4,
"name":"B child1",
"children":[
{
"_id":5,
"name":"B1 child1",
"children":[
{
"_id":6,
"name":"B1-1 child1",
"children":[
]
}
]
}
]
}
]
}
]
}
if I wanted to find children of A or B of root I can go
db.treeModel.find({'treeModel.children': 1}
db.treeModel.find({'treeModel.children': 3}
however, it doesn't seem to work on the ids of the children of the individual nodes. Does mongo or mongoose support this or am I missing an operator?
you need _id in the path (and to omit "treeModel") like:
db.treeModel.find({'children._id': 1})
db.treeModel.find({'children._id': 3})
keep in mind that this will still return the entire parent document
mongo/mongoose does not support recursive nested document lookups in a find directly. if you have max depth, you can use multiple $or conditions
db.treeModel.find({
$or: [
{ 'children._id': 1 },
{ 'children.children._id': 1 },
{ 'children.children.children._id': 1 },
]
});
otherwise for recursive nested, you may need to redo you data structures so you can use $graphLookup in an aggregation
Related
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
My question is a combination of:
This question: Removing the array element in mongoDB based on the position of element
And this question: mongodb set object where key value dynamically changes
I know you can define the field (array) dynamically like this:
{ [arrayName]: { <condition> } }
But, I then want to remove a certain element in a dynamically defined array by specifying the position (which is also defined dynamically). In other words, the function that processes this query is coming in which two parameters: the array's name and the index of the element to remove.
The options given by the selected answer were the following:
Option 1, does not work (in general), adapted to my case this looks like:
{ $pull : { [arrayName] : { $gt: index-1, $lt: index+1 } } }
Option 2, I cannot use dynamically defined values in field selectors with quotation marks (as far as I am aware):
{ $pull : "[arrayName].[index]" }
or
{ $pull : "[arrayName].$": index }
Option 3, is different method but can't use it for the same reason:
{ $unset: { "[arrayName].[index]": 1 } } // Won't work
{ $pull: { [arrayName]: null } } // Would probably work
The only workarounds I can think of right now involve significantly changing the design which would be a shame. Any help is appreciated!
PS: I'm using mongoose as a driver on the latest version as of today (v6.3.5) and MongoDB version 5.0.8
On Mongo version 4.2+ You can use pipelined updates to achieve this, you can get it done in multiple ways, here is what I consider the easiest two ways:
using $slice and $concatArrays to remove a certain element:
db.collection.update({},
[
{
$set: {
[arrayName]: {
$concatArrays: [
{
$slice: [
`$${arrayName}`,
index,
]
},
{
$slice: [
`$${arrayName}`,
index + 1,
{
$size: `$${arrayName}`
}
]
}
]
}
}
}
])
Mongo Playground
using $filter and $zip to filter out based on index:
db.collection.updateOne(
{},
[
{
"$set": {
[arrayName]: {
$map: {
input: {
$filter: {
input: {
$zip: {
inputs: [
{
$range: [
0,
{
$size: `$${arrayName}`
}
]
},
`$${arrayName}`
]
}
},
cond: {
$ne: [
{
"$arrayElemAt": [
"$$this",
0
]
},
index
]
}
}
},
in: {
$arrayElemAt: [
"$$this",
1
]
}
}
}
}
}
])
Alternatively you can just prepare
I have a database in MongoDB where there are entries with the following structure image
I am using a script in Python that I use to retrive information from this strucutre and then saves it to a csv.
The end goal is to remove ONLY SOME parts of the parameters structure. For instance, I want to keep parameters[0].values and parameters[1].values but not parameters[2:5].values
I am not experienced in this but the command I am using is the following
{"$unset":"exercises.parameters.values"] }
but this will remove all values from all the objects inside parameters. Since the variable has the same name amongst the objects I can't find a way to specify which ones I want.
I have also tried indexing like so
{"$unset":"exercises.parameters[2].values"] }
but that doesn't seem to work...
Help is much appreciated!
update
db.collection.update({
"_id": 1
},
{
"$unset": {
"exercises.$[].parameters.2.values": "",
"exercises.$[].parameters.3.values": "",
"exercises.$[].parameters.4.values": "",
"exercises.$[].parameters.5.values": ""
}
},
{
"multi": true
})
mongoplayground
aggregate
db.collection.aggregate([
{
$set: {
"exercises": {
"$map": {
"input": "$exercises",
"as": "item",
"in": {
"$mergeObjects": [
"$$item",
{
parameters: {
$slice: [ "$$item.parameters", 2 ]
}
}
]
}
}
}
}
}
])
mongoplayground
tldr; I'm struggling to construct a query to
Make an aggregation to get a count of values on a certain key ("original_text_source"), which
Is in a sub-document that is in an array
Full description
I have embedded documents with arrays that are structured like this:
{
"_id" : ObjectId("0123456789"),
"type" : "some_object",
"relationships" : {
"x" : [ ObjectId("0123456789") ],
"y" : [ ObjectId("0123456789") ],
},
"properties" : [
{
"a" : "1"
},
{
"b" : "1"
},
{
"original_text_source" : "foo.txt"
},
]
}
The docs were created from exactly 10k text files, sorted in various folders. During inserting documents into the MongoDB (in batches) I messed up and moved a few files around, causing one file to be imported twice (my database has a count of exactly 10001 docs), but obviously I don't know which one it is. Since one of the "original_text_source" values has to have a count of 2, I was planning on just deleting one.
I read up on solutions with $elemMatch, but since my array element is a document, I'm not sure how to proceed. Maybe with mapReduce? But I can't transfer the logic to my doc structure.
I also could just create a new collection and reupload all, but in case I mess up again, I'd rather like to learn how to query for duplicates. It seems more elegant :-)
You can find duplicates with a simple aggregation like this:
db.collection.aggregate(
{ $group: { _id: "$properties.original_text_source", docIds: { $push: "$_id" }, docCount: { $sum: 1 } } },
{ $match: { "docCount": { $gt: 1 } } }
)
which gives you something like this:
{
"_id" : [
"foo.txt"
],
"docIds" : [
ObjectId("59d6323613940a78ba1d5ffa"),
ObjectId("59d6324213940a78ba1d5ffc")
],
"docCount" : 2.0
}
Run the following:
db.collection.aggregate([
{ $group: {
_id: { name: "$properties.original_text_source" },
idsForDuplicatedDocs: { $addToSet: "$_id" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} }
]);
Given a collection which contains two copies of the document you showed in your question, the above command will return:
{
"_id" : {
"name" : [
"foo.txt"
]
},
"idsForDuplicatedDocs" : [
ObjectId("59d631d2c26584cd8b7b3337"),
ObjectId("59d631cbc26584cd8b7b3333")
],
"count" : 2
}
Where ...
The attribute _id.name is the value of the duplicated properties.original_text_source
The attribute idsForDuplicatedDocs contains the _id values for each of the documents which have a duplicated properties.original_text_source
"reviewAndRating": [
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7517",
"_id": "5bd9745e2d66162a6dd1f0ef",
"rating": "5"
},
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7518",
"_id": "5bd974612d66162a6dd1f0f0",
"rating": "5"
},
{
"review": "aksjdhfkashdfkashfdkjashjdkfhasdkjfhsafkjhasdkjfhasdjkfhsdakfj",
"productId": "5bd956f29fcaca161f6b7517",
"_id": "5bd974622d66162a6dd1f0f1",
"rating": "5"
}
]
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 ] }
]
});