mongo db nested query with wildcards and equality condition - mongodb

Trying to run a mongo db find query with equality condition in a nested VARIABLE key value
Bonus points for pymongo solution
db.utestmongo.insert({'name':'test','doc':{'heading':{'title':'awesome'},'body':{'title':'content'}}})
# want: 'title'=='awesome' => {'name':'test','doc':{'heading':{'title':'awesome'},'body':{'title':'content'}}}
# ideally
db.utestmongo.find( { "doc.*.title": "awesome" } )
# tried
db.utestmongo.find( { doc: { $elemMatch: { title: 'awesome' } } } )
Also tried links below, got very confused, maybe its because its not an equality query
https://jira.mongodb.org/browse/SERVER-2989
https://docs.mongodb.com/manual/tutorial/query-embedded-documents/
Nested mongo query
Mongo nested query with keys

if you wish to leave it as a find query you need to use the where clause and give it a function, however i'm not sure how efficient this approach is.
another alternative is using aggregate with objectToArray
and then iterating over all the keys.
it should look like this:
db.utestmongo.aggregate([
{
$addFields: { array: { $objectToArray: "$doc" }}
},
{
$match: {'array.v.title': "awesome"}
}])
pymongo solution
import pymongo
db_cnxn = pymongo.MongoClient()
db = db_cnxn['db']
pipeline = [
{"$addFields":{"array": { "$objectToArray": "$doc" }}},
{"$match": {'array.v.title': "awesome"}}
]
list(db['utestmongo'].aggregate(pipeline))

Related

Mongoose: search for ObjectID by Array

I want to filter my collection by aggregation for one of many ObjectIDs.
Because of some DocumentDB restrictions I can not build a single pipeline with uncorrelated subqueries. So my fix is to do it in two queries.
for example: I have an aggregation that returns all teamIds, for some conditions as an array of Object with the IDs.
[{_id: ObjectID("abcdef")}, {_id: ObjectID("ghijkl")}, {_id: ObjectID("vwxyz")}, ...]
I now want to have a second aggregation filter another collection using the ObjectIDs.
This would work in Mongo Compass:
{
"team": {
"$in": [ObjectId("60aabcb05c7462f42b3d7zyx"), ObjectId("60aabc7b05c7462f42b3dxyz")]
},
....
}
My issue is that i can not find the correct syntax for JS to generate such a pipeline.
What ever I try, JS always converts my Array of ObjectIDs to something like this:
{
"team": {
"$in": [{
"_id": "60aabcb05c7462f42b3d7zyx"
},{
"_id": "60aabc7b05c7462f42b3dxyz"
}]
},
I fixed it like this. I am not 100% why this syntax works because it is still just an array of objects, formatted like before, but I guess there is some stuff mongoose does, that is opaque to me.
let teams = await TeamMgmt.getTeamsAggregatedByFilter( teamFilter )
// make an array of ObjectIds so we can filter for them.
let idArray = []
Object.keys( teams ).map( function ( key, index ) {
idArray.push( new mongoose.Types.ObjectId( teams[ index ]._id.toString() ) )
} );
const shiftFilter = [
{
'$match': {
'team': {
"$in": idArray
},
....
}

MongoDB $elemMatch comparison to field in same document

I'm wanting to create an aggregation step to match documents where the value of a field in a document exists within an array in the same document.
In a very worked example (note this is very simplified; this will be fitting into a larger existing pipeline), given documents:
{
"_id":{"$oid":"61a9085af9733d0274c41990"},
"myArray":[
{"$oid":"61a9085af9733d0274c41991"},
{"$oid":"61a9085af9733d0274c41992"},
{"$oid":"61a9085af9733d0274c41993"}
],
"myField":{"$oid":"61a9085af9733d0274c41991"} // < In 'myArray' collection
}
and
{
"_id":{"$oid":"61a9085af9733d0274c41990"},
"myArray":[
{"$oid":"61a9085af9733d0274c41991"},
{"$oid":"61a9085af9733d0274c41992"},
{"$oid":"61a9085af9733d0274c41993"}
],
"myField":{"$oid":"61a9085af9733d0274c41994"} // < Not in 'myArray' collection
}
I want to match the first one because the value of myField exists in the collection, but not the second document.
It feels like this should be a really simple $elemMatch operation with an $eq operator, but I can't make it work and every example I've found uses literals. What I've got currently is below, and I've tried with various combinations of quotes and dollar signs round myField.
[{
$match: {
myArray: {
$elemMatch: {
$eq: '$this.myField'
}
}
}
}]
Am I doing something very obviously wrong? Is it not possible to use the value of a field in the same document with an $eq?
Hoping that someone can come along and point out where I'm being stupid :)
Thanks
You can simply do a $in in an aggregation pipeline.
db.collection.aggregate([
{
"$match": {
$expr: {
"$in": [
"$myField",
"$myArray"
]
}
}
}
])
Here is the Mongo playground for your reference.

Cannot use aggregation operations in $set inside updateMany

I have a mongoDB (4.4.8) collection where I want to change the value of some field based on its previous value. For example, I want to convert all strings to uppercase.
For this, I use the following query:
db.collection.updateMany(
{ field: { $regex: "[a-z]+"}},
{ $set: { field: { $toUpper: "$field" } } }
)
when executing the query, it gives me the following error:
MongoError: The dollar ($) prefixed field '$toUpper' in 'field.$toUpper' is not valid for storage
The same occurs if I use similar operations such as $concat (with an array parameter) to append something to the field.
When I look up similar questions, it all uses update and tells me to use updateMany instead, or it says that it only works in mongoDB >= 4.2. However, I have both of these things.
If I am correct, you are able to use aggregation syntax (among which $toUpper) in conjunction with $set inside updateMany queries for these newer versions of mongoDB.
Does anyone know what I'm doing wrong here?
As in the comments of J.F. and turivishal, I managed to solve this by changing it into the following:
db.collection.updateMany(
{ field: { $regex: "[a-z]+"}},
[ { $set: { field: { $toUpper: "$field" } } } ]
)

Query mongodb array of documents

I've a bunch of documents that look like:
{
"ids": [{"name":"aa", "age":1}, {"name":"bb", "age":2}]
}
I'd like to be able to query my documents providing a collection of ids, something like
db.getCollection('Collection').find({"ids":{$in : [{"name":"aa", "age":1}, {"name":"bb", "age":2}]}})
Generally that works, however it breaks when the fields order is changed, so for example I cannot find documents when I execute the following query
db.getCollection('Collection').find({"ids":{$in : [{"age":1,"name":"aa"}, { "age":2, "name":"bb"}]}})
I know that I could try to always execute a query with fields "in order", but from my current task perspective it's not always possible. Any help with that ?
You need $elemMatch when you want to run your query against an array of objects:
db.col.find({ $or: [ { "ids": { $elemMatch: {"age":1,"name":"aa"} } }, { "ids": { $elemMatch: { "age":2, "name":"bb"} } } ] })
Mongo Playground
EDIT: you can decide whether $or or $and should be a top level operator (depending on your use case)

How to check order of Array element in Mongodb?

In MongoDB, is there any easy way to check Order of element in Array? For example I have a document like this:
{
_id: 1,
tags: ["mongodb", "rethinkdb", "couchbase", "others"]
}
I would like to check in tags field if mongodb come before rethinkdb or not(lets see in array element, mongodb=0, rethinkdb=1 index, so mongodb come first and our case match.)?
but if there is another document (like below) where rethinkdb comes before mongodb,It case does not match.
{
_id: 2,
tags: ["rethinkdb", "mongodb", "couchbase"]
}
Here mongodb(1) comes after rethinkdb(0) so our case does not match.
Your question is not really as clear as you think it is, and thus why there are several ways to answer it:
If you are looking just to find out if a document has "mongodb" as the first element of the array then you just issue a query like this:
db.collection.find({ "tags.0": "mongodb" })
And that will return only the documents that match the given value at the specified index position using "dot notation".
If you actually expect to match if an array is in an "expected order" then you can get some help from the aggregation pipeline and set operators that are available and other features in MongoDB 2.6:
db.collection.aggregate([
{ "$project": {
"$_id": "$$ROOT",
"matched": { "$setEquals": [
"$tags",
["mongodb", "rethinkdb", "couchbase", "others"]
]}
}},
{ "$match": { "matched": true }}
])
Or if your want is to make sure that the "mongodb" value comes before the "rethinkdb" value, then you will need to evaluate in JavaScript with mapReduce, or something equally not nice like the $where operator:
db.collection.find({
"$where": function() {
return this.tags.indexOf("mongodb") < this.tags.indexOf("rethinkdb");
}
})