I have an array of objects and I would like to find if there is any field in those objects that includes a specific value.
The query I did is for searching in a specific field:
ExampleModel.find({
$expr: {
$in: ['myString', "$names"]
}
})
I would like to search if any field includes 'myString' and I did not find any answer in google.
Any suggestions ?
Use $objectToArray to transpose the document, they you can filter on v field:
db.ExampleModel.aggregate([
{ $addFields: { data: { $objectToArray: "$$ROOT" } } },
{ $match: { "data.v": "myString" } },
{ $unset: "data" }
])
Note, this does not work for embedded objects, for the first level on fields it should be fine.
Related
I have a collection with documents like this one:
{
f1: {
firstArray: [
{
secondArray: [{status: "foo1"}, {status: "foo2"}, {status: "foo3"}]
}
]
}
}
My expected result includes documents that have at least one item in firstArray, which is last object status on the secondArray is included in an input array of values (eg. ["foo3"]).
I don't must use aggregate.
I tried:
{
"f1.firstArray": {
$elemMatch: {
"secondArray.status": {
$in: ["foo3"],
},
otherField: "bar",
},
},
}
You can use an aggregation pipeline with $match and $filter, to keep only documents that their size of matching last items are greater than zero:
db.collection.aggregate([
{$match: {
$expr: {
$gt: [
{$size: {
$filter: {
input: "$f1.firstArray",
cond: {$in: [{$last: "$$this.secondArray.status"}, ["foo3"]]}
}
}
},
0
]
}
}
}
])
See how it works on the playground example
If you know that the secondArray have always 3 items you can do:
db.collection.find({
"f1.firstArray": {
$elemMatch: {
"secondArray.2.status": {
$in: ["foo3"]
}
}
}
})
But otherwise I don't think you can check only the last item without an aggregaation. The idea is that a regular find allows you to write a query that do not use values that are specific for each document. If the size of the array can be different on each document or even on different items on the same document, you need to use an aggregation pipeline
so imagine I have the following document:
{
"_id":...,
"data":{"a":[],"b":[],"x":[]}
}
I don't know beforehand which fields the subdocument data may have. I just know that every field in that subdocument will be an array
How do I make an update so that the object results like:
{
"_id":...,
"data":{"a":[1],"b":[1],"x":[1]}
}
Constraint: Using only mongodb operators. One single update. Without knowing the fields inside the 'data' subdocument
db.collection.update({},
[
{
$set: {
data: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$data" },
as: "d",
in: { k: "$$d.k", v: [ 1 ] }
}
}
}
}
}
])
mongoplayground
I'm wondering how I can perform calculations on document fields, and then update an existing field in that document based on those calculations?
I'm currently using a roundabout way of doing it (below), but I'm wondering if there's a more performant or straight-forward way?
Or if possible, have a document field ("cumulativeField") that is dynamic and updates in the following way:
db.collection
.aggregate([
{ $match: { arrayField: { $exists: true } } },
{ $addFields: { cumulativeField: { $sum: "$arrayField.number" } } }
])
.forEach(function (x){
db.collection.updateOne(
{ id: x.id },
{ $set: { cumulativeField: NumberInt(x.cumulativeField) } }
)})
Note: arrayField = an "array of objects" field, with each object in the array having a key "number" whose value(s) I am summing up to then put as a single value into the "cumulativeField" field.
MongoDB >= 4.2 supports pipeline updates, and updates can be like aggregation, aggregation result is the new value of the document.
In you case i think you only need to write the code as
updateOne({},
[{ $match: { arrayField: { $exists: true } } },
{ $addFields: { cumulativeField: { $toInt: { $sum: "$arrayField.number" } } } }])
I'm running the following query as described in the docs.
db.getCollection('things')
.find(
{ _id: UUID("...") },
{ _id: 0, history: 1 }
)
It produces a single element that, when unfolded in the GUI, shows the dictonary history. When I unfold that, I get to see the contents: bunch of keys and correlated values.
Now, I'd like to sort the keys alphabetically and pick n first ones. Please note that it's not an array but a dictionary that is stored. Also, it would be great if I could flatten the structure and pop up my history to be the head (root?) of the document returned.
I understand it's about projection and slicing. However, I'm not getting anywhere, despite many attempts. I get syntax errors or a full list of elements. Being rather nooby, I fear that I require a few pointers on how to diagnose my issue to begin with.
Based on the comments, I tried with aggregate and $sort. Regrettably, I only seem to be sorting the current output (that produces a single document due to the match condition). I want to access the elements inside history.
db.getCollection('things')
.aggregate([
{ $match: { _id: UUID("...") } },
{ $sort: { history: 1 } }
])
I'm sensing that I should use projection to pull out a list of elements residing under history but I'm getting no success using the below.
db.getCollection('things')
.aggregate([
{ $match: { _id: UUID("...") } },
{ $project: { history: 1, _id: 0 } }
])
It is a long process to just sort object properties by alphabetical order,
$objectToArray convert history object to array in key-value format
$unwind deconstruct above generated array
$sort by history key by ascending order (1 = ascending, -1 = descending)
$group by _id and reconstruct history key-value array
$slice to get your number of properties from dictionary from top, i have entered 1
$arrayToObject back to convert key-value array to object format
db.getCollection('things').aggregate([
{ $match: { _id: UUID("...") } },
{ $project: { history: { $objectToArray: "$history" } } },
{ $unwind: "$history" },
{ $sort: { "history.k": 1 } },
{
$group: {
_id: "$_id",
history: { $push: "$history" }
}
},
{
$project: {
history: {
$arrayToObject: { $slice: ["$history", 1] }
}
}
}
])
Playground
There is another option, but as per MongoDB, it can not guarantee this will reproduce the exact result,
$objectToArray convert history object to array in key-value format
$setUnion basically this operator will get unique elements from an array, but as per experience, it will sort elements by key ascending order, so as per MongoDB there is no guarantee.
$slice to get your number of properties from dictionary from top, i have entered 1
$arrayToObject back to convert key-value array to object format
db.getCollection('things').aggregate([
{ $match: { _id: UUID("...") } },
{
$project: {
history: {
$arrayToObject: {
$slice: [
{ $setUnion: { $objectToArray: "$history" } },
1
]
}
}
}
}
])
Playground
I'd like to create a query or aggregation where the returned documents do not include sub-documents. I do not know that a given field will be a sub-document ahead of time (or I would just use the projection to skip them). So for example, if I have a document like this:
{
_id: 1,
field1: "a",
field2: "b",
field3: {
subfield1: "c",
subfield2: "d"
}
}
When my query returns this document, it either skips field3, or replaces field3's value with something else (e.g. a string = "field_is_an_object").
As I said, I don't know ahead of time which fields will be sub-documents (or "object" types). The $redact operator was the closest I could find, but I couldn't figure out a syntax to get it to work.
There are at least two ways you can achieve what you want:
The first one is pretty concise and requires just one aggregation stage which, however, is a little bit more complex and harder to understand:
db.collection.aggregate({
$replaceRoot: { // create a new top level document
"newRoot": { // ...which shall be
$arrayToObject: { // ...created from an array
$filter: { // ...that again should contain only those elements
input: { // ...from our input array
$objectToArray: "$$ROOT" // ...which is our respective top level document transformed into an array of key-value pairs
},
cond: { // ...where
$ne: [ { $type: "$$this.v" }, "object" ] // ...the "v" (as in "value" field is not an object)
}
}
}
}
}
})
The second one I can think of is way more verbose but pretty easy to understand by adding the stages step-by-step (as always with the aggregation framework).
db.collection.aggregate({
$project: {
"tmp": { // we create a temporary field
$objectToArray: "$$ROOT" // that contains our respective root document represented as an array of key-value pairs
}
}
}, {
$unwind: "$tmp" // flatten the temporary array into multiple documents
}, {
$match: {
"tmp.v": { $not: { $type: "object" } } // filter all documents out that we do not want in our result
}
}, {
$group: { // group all documents together again
"_id": "$_id", // into one bucket per original document ("_id")
"tmp": {
$push: "$tmp" // and create an array with all the key-value pairs that have survived our $match stage
}
}
}, {
$replaceRoot: { // create a new top level document...
"newRoot": {
$arrayToObject: "$tmp" // ...out of the data we have left in our array
}
}
})