Monogdb $expr with multiple level of nesting - mongodb

I don't know whether this is possible or not and the doc for $expr doesn't seem to mention it as well, but I'm trying to compare two fields where one is a hash type and the other is an array of hashes. I'm trying to query for records where the hash field is equal to the first hash element in the array of hashes. Here is my collection layout:
{
_id: "Some ID",
answers: [ { staff_id: 1, answer: { dob: true, type: true } } ,
qa_answer: { dob: true, type: true }
}
The query expression I've tried is:
collection.find({ '$expr': { '$eq': ['$answers.0.answer', '$qa_answer'] } })
But this doesn't seem to work. I thought it was the nested dot notation that was the problem but I've tried the following and it worked:
collection.find({ 'answers.0.answer': { "dob": true, "type": true } })

Okay I finally got it to work, it turns out the dot notation was the issue. Here's the query I ended up with:
collection.find({
'$expr': {
'$eq': [
{ '$arrayElemAt': [ '$answers.answer', 0 ] },
'$qa_answer'
]
}
})

Related

Arguments must be aggregate pipeline operators at Aggregate.append

https://mongoplayground.net/p/MlQFd6J4rZc
Products.aggregate({
$match: {
$or: [
{ '_id': {$regex: queryRegex } },
{ 'name': {$regex: queryRegex } },
]
},
{
$addFields: {
favorite: {
$cond: [
{
$in: [
'xxx',
'$favoritedBy'
]
},
true,
false
]
}
}
})
Adding the $addFields portion broke the code. I am not sure why. It's working on the mongoplayground example I set up. Is there any reason for this? removing $addFields removes the error, but I should be able to create a new field, so I am confused as to what's going on.
Mongoose is version 5.11.12, so it's not caused by the version number, I think.
Error: Arguments must be aggregate pipeline operators at Aggregate.append
useNewUrlParser was set to true.

How to query all instances of a nested field with variable paths in a MongoDb collection

I have a collection that has documents with a field called overridden. I need to find the number of times overridden: true.
So for example, I have a collection as follows:
[ // assume this is my collection / array of documents
{ // this is a document
textfield1: {
overridden: true,
},
page1: {
textfield2: {
overridden: true,
},
textfield3: {
overridden: false,
}
},
page2: {
section1: {
textfield4: {
overridden: true,
}
}
}
},
{ // this is a different document
page1: {
section1: {
textfield1: {
overridden: false,
},
textfield2: {
overridden: false,
}
},
section2: {
textfield3: {
overridden: true,
}
}
}
}
}
So from the above, I need to get # of fields overridden = 4.
This is a simplified example of how the documents in the collection are structured. I'm attempting to show that:
There is no guaranteed structure to find the path to each overridden field in the document.
I need to aggregate across documents in the collection.
From research online, I did the following:
db.reports.aggregate()
.group({
_id: "overridden",
totalOverridden: {
"$sum": {
$cond: [ { "overridden": true }, 1, 0]
}
}
})
That gave me a value of 2472 in the actual collection, which looks like the total number of times that field occurs because if I remove the entire $cond I still get the same value. From the looks of it, { "overridden": true } always returns true, because if I flip the if-else return values (or just do $sum: 1), I get 0.
If it's any help, I do actually have mongoose schemas with defined paths for each overridden field but the schema is a bit large and heavily nested, therefore tracking each path would be quite tedious. That being said, if the above is not possible, I'm open to suggestions for analyzing the schema/document JSON as well to get the different paths and somehow using those to query all the fields.
Thanks a ton! I'd appreciate any help. :-)
This is possible, but not very pretty. You can repeatedly convert the object to any array, eliminate all fields that do not contain objects or have the name "overridden", and repeat.
There is no flow control in an aggregation pipeline, so you won't be able to have it automatically detect when to stop. Instead, you'll need to repeat the extraction for the number of levels of embedding that you want to support.
Perhaps something like:
reapeating = [
{$unwind: {
path: "$root",
preserveNullAndEmptyArrays: true,
}},
{$unwind: {
path: "$v",
preserveNullAndEmptyArrays: true
}},
{$match: {
$or: [
{"root.k": "overridden", "root.v":true},
{"root.v": {$type: 3}}
]
}},
{$project: {
root: {
$cond: {
if: {
$eq: [
"object",
{$type: "$root.v"}
]
},
then: {$objectToArray: "$root.v"},
else: "$root"
}
}
}}
]
Then to find all "overridden" fields down to 5 levels deep:
pipeline = [
{$project: {
_id: 0,
root: {$objectToArray: "$$ROOT"}
}}
];
final = [
{$match: {
"root.k": "overridden",
"root.v": true
}},
{$count: "overridden"}
];
for(i=0;i<5;i++){
pipeline = pipeline.concat(repeating)
}
pipeline = pipeline.concat(final);
db.reports.aggregate(pipeline)
Playground

Find all entries where one of attributes within array is empty

I've following mongodb query:
db
.getCollection("entries")
.find({
$and: [
{
"array.attribute_1": {
$exists: true,
$not: {
$size: 0
}
}
},
{
$or: [
{ "array.attribute_2": { $exists: true, $size: 0 } },
{ "array.attribute_2": { $exists: true, $eq: {} } }
]
},
]
})
And example of my document:
{
_id: 'foo',
array: [
{attribute_1: [], attribute_2: []},
{attribute_1: ['bar'], attribute_2: []}
]
}
In my understanding my query should find all entries that have at least one element within array that has existent and not empty attribute_1 and existent empty array or empty object attribute_2. However, this query finds all entries that has all elements within array that has existent and not empty attribute_1 and existent empty array or empty object attribute_2. As such, my foo entry won't be found.
What should be the correct formula for my requirements?
$find would find the first document with the matching criteria and in your case that first document contains all the arrays. You either need to use $project with $filter or aggregation with $unwind and $match.
Something like this:
db.collection.aggregate([
{ $unwind: "$array" },
{
$match: {
$and: [
{ "array.attribute_1.0": { $exists: true }},
{
$or: [
{ "array.attribute_2.0": { $exists: false } },
{ "array.attribute_2.0": { $eq: {} } }
]
}
]
}
}
])
You can see it working here
Also since you are trying to find out if array is empty and exists at the same time using .0 with $exists is a quick and one statement way to get the same result as with both $exists and $size.

MongoDB / Mongoose: is there a way to query for a value within a field that is an object that contains two arrays?

At this moment I am using 2 queries to search for a username value inside a field that is an object that contains 2 arrays then merging them together. I can't seem to figure out how to write a query that would search through both arrays at once and return all results that match a given username in any of the arrays. I was wondering if this is even possible? Thanks for any inputs!
Game.find({
active: false,
'current_players.team1': { $elemMatch: { $in: [username] } }
})
Game.find({
active: false,
'current_players.team2': { $elemMatch: { $in: [username] } }
})
This is the schema I am working with:
var Game = mongoose.Schema({
id: ...,
...,
...,
current_players: {
team1: Array // [ 'jim', 'bob', 'sarah', null ]
team2: Array // [ 'peter', 'frank', null, 'simon']
}
})
To search both arrays in one query for this problem you can use the $or operator.
db.collection.find({
"active": false,
$or: [
{
"current_players.team1": "username"
},
{
"current_players.team2": "username"
}
]
})

MongoError: cannot infer query fields to set, path 'users' is matched twice

I am using mongoose. I want to create a document chat with an array users (including userId1, userId2), if I do not find it:
This is how I do:
ChatModel.findOneAndUpdate(
{ users: { $all: [userId1, userId2] }},
{ $setOnInsert: {
users: [userId1, userId2]
}},
{ upsert: true })
.exec()
.catch(err => console.log(err));
But I got the error:
MongoError: cannot infer query fields to set, path 'users' is matched
twice
This is Chat Schema:
{
users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
createdAt: { type: Date, default: Date.now }
}
How can I do it correctly? Thanks
I use this as the condition
{
"users": {
$all: [
{"$elemMatch": userId1},
{"$elemMatch": userId2}
]
}......
}
I know this already has an answer but to hopefully save someone else some time, I had to do this:
{
"users": {
$all: [
{ $elemMatch: { $eq: mongoose.Types.ObjectId(userId1) }},
{ $elemMatch: { $eq: mongoose.Types.ObjectId(userId2) }}
]
}......
}
Modifications from accepted answer:
The $eq was needed just like Dave Howson said in his comment on the accepted answer.
mongoose.Types.ObjectId was needed because I guess the _id property on my schema instance was a string.
There is a workaround for this issue:
db.foo.update({a:{$all:[{$elemMatch:{$eq:0}},{$elemMatch:{$eq:1}}]}},{$set:{b:1}},{upsert:true})
This will match when a is an array with both 0 and 1 in it and it will upsert otherwise.
From: https://jira.mongodb.org/browse/SERVER-13843?focusedCommentId=2305903&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-2305903