I have some data structure like this
{
a: 1,
array1: [
{
b: 2
array2: [
{
// this is my target
c: 3,
d: 3
},
{
c: 4,
d: 4
}
]
},
{
b: 3
array2: [
{
c: 5,
d: 5
},
{
c: 6,
d: 6
}
]
}
]
}
I know use {"array1" : {"$elemMatch" : {"b" : 2} } } to match element of first level array1. But I don't know how to match element {c: 3, d: 3} of array2 of array1.
$elemMatch is used to state association among multiple fields within the same nested structure.
For eg. If you are looking to query c and d and need them to belong to the same sub-document, the you can query it like.
{"array1.array2" : {"$elemMatch" : {"c" : 3, "d":3} } }
Note: if you are querying on a single field, you dont really need to use $elemMatch (since there is no association)
For instance, in your query example, you can instead do
{"array1.b" : 2}
try this,it help me a lot.
{
"array1": {
"$elemMatch": {
"array2": {
"$elemMatch": {
"c": 3
}
}
}
}
}
let feed = await Doc.findOneAndUpdate(
{
$and: [
{
_id: req.params.id,
},
{
'feeds.$[].locations': {
$elemMatch: {
uniqueName,
},
},
},
],
},
{
$pull: {
//#ts-ignore
'feeds.$[].locations': { uniqueName },
},
},
{ new: true }
);
Related
I'm new in Mongodb and I try to learn aggregation.
My database look like this:
{
_id : 1,
a: 5,
b: 6,
c : {
ca: 3
cb: 5
}
},
{
_id : 1,
a: 3,
b: 4,
c : {
ca: 3
cb: 7
}
}
Is there anyway to :
- First: find max in c
- Second: create a new object like this:
{
a : [ 5, 3],
b : [6, 4],
c : [5, 7]
}
My apologies if this is a repeated question but I wasn't able to find a similar one.
Try this,
db.collection.aggregate([
{
$addFields: {
c: {
$objectToArray: "$c"
}
}
},
{
$group: {
_id: null,
a: {
$push: "$a"
},
b: {
$push: "$b"
},
c: {
$push: {
$max: "$c.v"
}
}
}
}
])
Mongo Playground
I have nested arrays and objects. I want to filter out each medicine separately. Such as document:
{
person: "X",
treatment: [
{
id: 1,
medication: [
{
name: "A"
},
{
name: "B"
}
]
},
{
id: 2,
medication: [
{
name: "A"
},
{
name: "C"
}
]
}
]
},
{
person: "Y",
treatment: [
{
id: 1,
medication: [
{
name: "B"
},
{
name: "C"
}
]
}
]
}
How could I get a distinct list of top medication? Like this: A:2, B:2, C:2
You need to use $unwind which outputs one document for each element in the array. As there are nested array's you'll need to apply $unwind twice.
E.g. for a collection named medicine:
db.medicine.aggregate([
{
$unwind : "$treatment"
},
{
$unwind : "$treatment.medication"
},
{
$group : { _id : "$treatment.medication.name", total: { $sum: 1 } }
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: {
$let: {
vars: { data: [ { k: "$_id", v: "$total" } ] },
in: "$$data"
}
}
}
}
}
])
Outputs:
{ "C" : 2 }
{ "B" : 2 }
{ "A" : 2 }
Note that when unwinding nested arrays, many documents can be generated if there are large arrays, so you might want to check this question Consequences of using $unwind on nested arrays?
I have a collection of documents where I want to find the maximum values of each of the ratios of every possible pair of fields in the data object. For example:
Documents:
[
{ data: { a: 1, b: 5, c: 2 } },
{ data: { a: 4, b: 1, c: 1 } },
{ data: { a: 2, b: 4, c: 3 } }
]
Desired output:
{
a: { a: 1, b: 4, c: 4 },
b: { a: 5, b: 1, c: 2.5 },
c: { a: 2, b: 1, c: 1 }
}
So the output a.b is the largest of the a:b ratios 1/5, 4/1, and 2/4.
So I figure I first use $objectToArray to convert data, then $unwind on the result, but I'm having a hard time figuring out how to group everything together. The number of documents I have won't be too large, but the number of keys in data can be in the low thousands, so I'm not sure how well Mongo will be able to handle doing a bunch of $lookup's and comparing the values like that.
You can try following aggregation:
db.col.aggregate([
{
$addFields: { data: { $objectToArray: "$data" } }
},
{
$project: {
pairs: {
$map: {
input: { $range: [ 0, { $multiply: [ { $size: "$data" }, { $size: "$data" } ] } ] },
as: "index",
in: {
$let: {
vars: {
leftIndex: { $floor: { $divide: [ "$$index", { $size: "$data" } ] } },
rightIndex: { $mod: [ "$$index", { $size: "$data" } ] }
},
in: {
l: { $arrayElemAt: [ "$data", "$$leftIndex" ] },
r: { $arrayElemAt: [ "$data", "$$rightIndex" ] }
}
}
}
}
}
}
},
{ $unwind: "$pairs" },
{
$group: {
_id: { l: "$pairs.l.k", r: "$pairs.r.k" },
value: { $max: { $divide: [ "$pairs.l.v", "$pairs.r.v" ] } }
}
},
{
$sort: {
"_id.l": 1, "_id.r": 1
}
},
{
$group: {
_id: "$_id.l",
values: { $push: { k: "$_id.r", v: "$value" } }
}
},
{
$addFields: { values: { $arrayToObject: "$values" } }
},
{
$project: {
root: [ { k: "$_id", v: "$values" } ]
}
},
{
$sort: { "root.k": 1 }
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$root"
}
}
}
])
Basically you need $objectToArray and $arrayToObject to transform between arrays and objects. Basically the point is that for each object you need to generate nxn pairs (3x3=9 in this case). You can perform such iteration using $range operator. Then using $mod and $divide with $floor you can get index pairs like (0,0)...(2,2). Then you just need $group with $max to get max values for each pair type (like a with b and so on). To get final shape you also need $replaceRoot.
Outputs:
{ "a" : { "a" : 1, "b" : 4, "c" : 4 } }
{ "b" : { "a" : 5, "b" : 1, "c" : 2.5 } }
{ "c" : { "a" : 2, "b" : 1, "c" : 1 } }
How can I find all documents in a MongoDb collection where a property of the document or its sub-documents contains an empty object value {}?
The name of the property is not known.
Example of which documents should be returned:
{
data: {
comment: {}
}
}
As said data and comment as property names are unknown.
The way to iterate object properties within aggregation pipeline is $objectToArray operator, which converts a document to the array of key-value pairs. Unfortunately, it does not flatten embedded documents. Until such support is implemented, I don't see a way to accomplish your task with the pure aggregation pipeline.
However you could always use $where operator and put the logic into JavaScript code. It should recursively iterate over all document properties and check whether the value is an empty document. Here is a working sample:
db.collection.find({"$where" : function () {
function hasEmptyProperties(doc) {
for (var property in doc) {
var value = doc[property];
if (value !== null && value.constructor === Object &&
(Object.keys(value).length === 0 || hasEmptyProperties(value))) {
return true;
}
}
return false;
}
return hasEmptyProperties(this);
}});
If you fill the collection with the following data:
db.collection.insert({ _id: 1, p: false });
db.collection.insert({ _id: 2, p: [] });
db.collection.insert({ _id: 3, p: null });
db.collection.insert({ _id: 4, p: new Date() });
db.collection.insert({ _id: 5, p: {} });
db.collection.insert({ _id: 6, nestedDocument: { p: "Some Value" } });
db.collection.insert({ _id: 7, nestedDocument: { p1: 1, p2: {} } });
db.collection.insert({ _id: 8, nestedDocument: { deepDocument: { p: 1 } } });
db.collection.insert({ _id: 9, nestedDocument: { deepDocument: { p: {} } } });
the query will correctly detect all documents with empty properties:
{ "_id" : 5, "p" : { } }
{ "_id" : 7, "nestedDocument" : { "p1" : 1, "p2" : { } } }
{ "_id" : 9, "nestedDocument" : { "deepDocument" : { "p" : { } } } }
Just for reference, here is an aggregation pipeline based on $objectToArray which detects empty properties, however not within nested documents:
db.collection.aggregate(
[
{ "$project": {
_id: 1,
"properties": { "$objectToArray": "$$ROOT" }
}},
{ "$project": {
_id: 1,
propertyIsEmpty: {
$map: {
input: "$properties.v",
as: "value",
in: { $eq: ["$$value", {} ] }
}
}
}},
{ "$project": {
_id: 1,
anyPropertyIsEmpty: { $anyElementTrue: [ "$propertyIsEmpty" ] }
}},
{$match : {"anyPropertyIsEmpty" : true}},
{ "$project": {
_id: 1,
}},
]);
I'm kinda stuck doing something seemingly simple with MongoDB's aggregation framework.
Imagine you have documents that would look like this :
[
{ a: 1, b: 2 },
{ a: 1, b: 3 },
{ a: 5, b: 6 }
]
How can you group documents by the field a and then regroup sub-documents by another field, say b while still calculating the total number of documents at each step ?
For our example, the results would look be the following output document :
{
results: [
{
_id: {
a: 1
},
sum_a: 2,
doc_a: [
{
_id: {
b: 2
},
sum_b: 1
},
{
_id: {
b: 3
},
sum_b: 1
}
]
},
{
_id: {
a: 5
},
sum_a: 1,
doc_a: [
{
_id: {
b: 6
},
sum_b: 1
}
]
}
]
}
I tried things like this :
printjson(db.getSiblingDB('mydb').mycollection.aggregate([
{
$project: {
a: 1,
b: 1
}
},
{
$group: {
_id: {
a: '$a'
},
sum_a: {
$sum: 1
},
b: {
$first: '$b'
}
}
},
{
$group: {
_id: {
b: '$b'
},
sum_b: {
$sum: 1
}
}
},
{
$sort: {
sum_a: 1
}
}
]));
But in the different tests I made, it keeps overwriting previous group stage results, wrongly calculating sums, ...etc.
So I'm not really sure how to approach this problem.
If you group by main field ('a') and sub-field ('b') together and then group by only 'a' (summing the counts from the first step) and push 'b's into an array (copying counts from the first step), it should produce what you need:
{
$group : {
_id : {
a : '$a',
b : '$b'
},
count : {
$sum : 1
}
}
},{
$group : {
_id : {
a : '$_id.a'
},
count_a : {$sum: '$count'},
doc_a : {
$push : {
b : '$_id.b',
count_b : '$count'
}
}
}
}