I have multiple documents in a collection like this
[
{
_id: 123,
data: 1,
details: [
{
item: "a",
day: 1
},
{
item: "a",
day: 2
},
{
item: "a",
day: 3
},
{
item: "a",
day: 4
}
],
someMoreField: "xyz"
}
]
Now I want document with _id: 123 and details field should only contain day within range of 1 to 3. So the result will be like below.
{
_id: 123,
data: 1,
details: [
{
item: 'a',
day: 1,
},
{
item: 'a',
day: 2,
},
{
item: 'a',
day: 3,
},
],
someMoreField: 'xyz',
};
I tried to do this by aggregate query as:
db.collectionaggregate([
{
$match: {
_id: id,
'details.day': { $gt: 1, $lte: 3 },
},
},
{
$project: {
_id: 1,
details: {
$filter: {
input: '$details',
as: 'value',
cond: {
$and: [
{ $gt: ['$$value.date', 1] },
{ $lt: ['$$value.date', 3] },
],
},
},
},
},
},
])
But this gives me empty result. Could someone please guide me through this?
You are very close, you just need to change the $gt to $gte and $lt to $lte.
Another minor syntax error is you're accessing $$value.date but the schema you provided does not have that field, it seems you need to change it to $$value.day, like so:
db.collection.aggregate([
{
$match: {
_id: 123,
"details.day": {
$gt: 1,
$lte: 3
}
}
},
{
$project: {
_id: 1,
details: {
$filter: {
input: "$details",
as: "value",
cond: {
$and: [
{
$gte: [
"$$value.day",
1
]
},
{
$lte: [
"$$value.day",
3
]
},
],
},
},
},
},
},
])
Mongo Playground
Related
Here is my Data in DB
{
name: abc
pro: 1
},
{
name:cde,
pro: 2
},
{
name:fgh,
pro:3
},
{
name:ijk,
pro:4
},
here is my query to aggregate the result I've successfully get the count of name and pro:
db.aggregate([
{
$facet: {
"name": [
{ $group: { _id: '$name', N: { $sum: 1 } } }
],
},{ $project: { "pro": {
$map: {
input: '$pro',
in:{ $arrayToObject: [[{ k: '$$this._id', v: '$$this.N'}]] }
}
},
}}])
For pro which is 1 in DB but against 1, I want to map a etc, in response.
Expecting Output:
{
name: abc
pro: a //value in DB is 1.
},
{
name:cde,
pro: b //value in DB is 2.
},
{
name:fgh,
pro:c //value in DB is 3
},
{
name:ijk,
pro:d //value in DB is 4
},
}
If I understand your question correctly, you need to map pro integer values to corresponding characters. Then this should work:
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
pro: {
$slice: [
[
"",
"a",
"b",
"c",
"d"
],
"$pro",
1
]
}
}
},
{
"$unwind": "$pro"
}
])
Here is the playground link.
Use Map pro function, it will works
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
pro: {
$slice: [
[
"",
"a",
"b",
"c",
"d"
],
"$pro",
1
]
}
}
},
{
"$unwind": "$pro"
}
])
I have a list of ids and it may contain duplicates, so ignore duplicates and count the occurrence of total duplicates. I will explain it in details.
IDS
[
5fe10a8c4d6b0fb7f70bbf84,
5fe10a8c4d6b0fb7f70bbf84,
5ff2aad439a8602fd872ab7c
]
I have used the below code to get the result,
var user_id = [
5fe10a8c4d6b0fb7f70bbf84,
5fe10a8c4d6b0fb7f70bbf84,
5ff2aad439a8602fd872ab7c
];
User.aggregate([
{
$match: {
_id: {
$in: user_id
}
},
},
{
$group: {
_id: "$_id",
count: {
$sum: 1
}
}
},
],
(err, resp) => {
console.log(resp)
})
The output for above code is,
[
{ _id: 5ff2aad439a8602fd872ab7c, count: 1 },
{ _id: 5fe10a8c4d6b0fb7f70bbf84, count: 1 }
]
The required output is,
[
{ _id: 5ff2aad439a8602fd872ab7c, count: 1 },
{ _id: 5fe10a8c4d6b0fb7f70bbf84, count: 2 }
]
I have tried many code but no success, is there anyway to achieve required output.
you trying to get the count from _id. _id is unique for all the documents so you need to change the _id to some other fields which is not unique.
example,
table
[
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 3,
values: [
1,
2,
3
]
}
]
Query
db.collection.aggregate([
{
$match: {
id: {
$in: [
1,
3
]
}
},
},
{
$group: {
_id: "$id",
count: {
$sum: 1
}
}
},
])
output
[
{
"_id": 1,
"count": 3
},
{
"_id": 3,
"count": 1
}
]
I've been working on a small project that takes MQTT data from sensors and stores it in a MongoDB database. I'm working with nodeJS and mongoose. These are my schemas.
export const SensorSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
location: { type: String, required: true },
type: { type: String, required: true },
unit: { type: String, required: true },
measurements: { type: [MeasurementSchema] }
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
export const MeasurementSchema = new mongoose.Schema({
value: {type: Number, required: true},
time: {type: Date, required: true}
});
First I wrote a function that retrieves all measurements that were made in between two timestamps.
const values = Sensor.aggregate([
{ $match: Sensor.getValuesFromPath(sensorPath) },
{ $unwind: "$measurements"},
{ $match: { "measurements.time": { $gte: startTime, $lte: endTime} }},
{ $replaceRoot: { newRoot: "$measurements" } },
{ $project: { _id: 0}},
{ $sort: {time: 1}}
]).exec();
In order to draw a graph in the UI, I need to somehow sort and then limit the data that gets sent to the client. I want to send every Nth Value in a certain interval to ensure that the data somewhat resembles the course of the data.
I would prefer a solution that doesn't fetch all the data from the database.
How would I go about doing this on the db? Can I somehow access the positional index of an element after sorting it? Is $arrayElemAt or $elemMatch the solution?
Befure you run $unwind you can use $filter to apply start/end Date filtering. This will allow you to process measurements as an array. In the next step you can get every N-th element by using $range to define a list of indexes and $arrayElemAt to retrieve elements from these indexes:
const values = Sensor.aggregate([
{ $match: Sensor.getValuesFromPath(sensorPath) },
{ $addFields: {
measurements: {
$filter: {
input: "$measurements",
cond: { $and: [
{ $gte: [ "$$this.time", startTime ] },
{ $lte: [ "$$this.time", endTime ] }
]
}
}
}
} },
{ $addFields: {
measurements: {
$map: {
input: input: { $range: [ 0, { $size: "$measurements" }, N ] },
as: "index",
in: { $arrayElemAt: [ "$measurements", "$$index" ] }
}
}
} },
{ $unwind: "$measurements" },
{ $replaceRoot: { newRoot: "$measurements" } },
{ $project: { _id: 0}},
{ $sort: {time: 1}}
]).exec();
The following aggregation (i) retrieves all measurements that were made in between two timestamps, (ii) sorts by timestamp for each sensor, and (iii) gets every Nth value (specified by the variable EVERY_N).
Sample documents (with some arbitrary data for testing):
{
name: "s-1",
location: "123",
type: "456",
measurements: [ { time: 2, value: 12 }, { time: 3, value: 13 },
{ time: 4, value: 15 }, { time: 5, value: 22 },
{ time: 6, value: 34 }, { time: 7, value: 9 },
{ time: 8, value: 5 }, { time: 9, value: 1 },
]
},
{
name: "s-2",
location: "789",
type: "900",
measurements: [ { time: 1, value: 31 }, { time: 3, value: 32 },
{ time: 4, value: 35 }, { time: 6, value: 39 },
{ time: 7, value: 6}, { time: 8, value: 70 },
{ time: 9, value: 74 }, { time: 10, value: 82 }
]
}
The aggregation:
var startTime = 3, endTime = 10
var EVERY_N = 2 // value can be 3, etc.
db.collection.aggregate( [
{
$unwind: "$measurements"
},
{
$match: {
"measurements.time": { $gte: startTime, $lte: endTime }
}
},
{
$sort: { name: 1, "measurements.time": 1 }
},
{
$group: {
_id: "$name",
measurements: { $push: "$measurements" },
doc: { $first: "$$ROOT" }
}
},
{
$addFields: {
"doc.measurements": "$measurements"
}
},
{
$replaceRoot: { newRoot: "$doc" }
},
{
$addFields: {
measurements: {
$reduce: {
input: { $range: [ 0, { $size: "$measurements" } ] },
initialValue: [ ],
in: { $cond: [ { $eq: [ { $mod: [ "$$this", EVERY_N ] }, 0 ] },
{ $concatArrays: [ "$$value", [ { $arrayElemAt: [ "$measurements", "$$this" ] } ] ] },
"$$value"
]
}
}
}
}
}
] )
how you doing?
I have a trouble making a aggregation in my project, my aggregation result is different in Robo3T and Node.
db.getCollection('companies').aggregate([
{ '$match': { _id: { '$eq': ObjectId("5e30a4fe11e6e80d7fb544a4")} } },
{ $unwind: '$jobVacancies' },
{
$project: {
jobVacancies: {
_id: 1,
name: 1,
city: 1,
openingDate: 1,
closingDate: 1,
createdAt: 1,
quantity: 1,
steps: {
$filter: {
input: '$jobVacancies.steps',
as: 'step',
cond: {
$and: [
{ $eq: ['$$step.order', 0] },
{ $ne: ['$$step.users', undefined] },
{ $ne: ['$$step.users', null] },
{ $ne: ['$$step.users', []] },
],
},
},
},
},
},
},
{ $match: { 'jobVacancies.steps': { $ne: [] } } },
])
In Robo3T this is returning 1 object, but in Node (the same aggregation) is resulting 6 objects. Can you help me? Thank you
EDIT
Nodejs:
The first match create the ObjectId match for company in context of GraphQL based on my filter.
const companies = await this.MongoClient.db
.collection('companies')
.aggregate([
{
$match: await this.getFilterObject(
filters.filter(f => !f.field.includes('$$jobVacancy') && !f.field.includes('StepOrder')),
),
},
{ $unwind: '$jobVacancies' },
{
$project: {
jobVacancies: {
_id: 1,
name: 1,
city: 1,
openingDate: 1,
closingDate: 1,
createdAt: 1,
quantity: 1,
steps: {
$filter: {
input: '$jobVacancies.steps',
as: 'step',
cond: {
$and: [
{ $eq: ['$$step.order', order] },
{ $ne: ['$$step.users', undefined] },
{ $ne: ['$$step.users', null] },
{ $ne: ['$$step.users', []] },
],
},
},
},
},
},
},
{ $match: { 'jobVacancies.steps': { $ne: [] } } },
])
.toArray();
EDIT 3
This is the result of console.dir (with {depth:null}) of the pipeline
[
{
'$match': {
_id: {
'$eq': ObjectID {
_bsontype: 'ObjectID',
id: Buffer [Uint8Array] [
94, 48, 164, 254, 17,
230, 232, 13, 127, 181,
68, 164
]
}
}
}
},
{ '$unwind': '$jobVacancies' },
{
'$project': {
jobVacancies: {
_id: 1,
name: 1,
city: 1,
openingDate: 1,
closingDate: 1,
createdAt: 1,
quantity: 1,
steps: {
'$filter': {
input: '$jobVacancies.steps',
as: 'step',
cond: {
'$and': [
{ '$eq': [ '$$step.order', 0 ] },
{ '$ne': [ '$$step.users', undefined ] },
{ '$ne': [ '$$step.users', null ] },
{ '$ne': [ '$$step.users', [] ] }
]
}
}
}
}
}
},
{ '$match': { 'jobVacancies.steps': { '$ne': [] } } }
]
I think i found the solution, the document is created with properties:
jobVacancies: {
steps: {
users: []
}
}
But sometimes users array is undefined in mongodb, so I verify with
{ '$ne': [ '$$step.users', undefined ] }
I think JS undefined is different then mongodb undefined, so I initialized all steps with an empty array of users, and removed this verification and worked! –
I have a collection with documents, of that structure
{
type: 'a',
date: '2014-01-04'
},
{
type: 'b',
date: '2014-01-04'
},
{
type: 'b',
date: '2014-01-04
},
{
type: 'c',
date: '2014-01-03'
},
{
type: 'a',
date: '2014-01-03'
}
I want to aggregate that data by date and type (group by date and count by type):
{
date: '2014-01-04': {
'a': 1,
'b': 2
},
date: '2014-01'03': {
'a': 1,
'c': 1
}
}
I have aggregate function, like this
db.items.aggregate([
{
$match: { user: user },
},
{
$group: { _id: {date: '$date'}, count: {$sum: 1}, services: {$push: '$type'}}
}
], function (err, results) {
But doing that I still need to reduce results by services.
Can this be done with one aggregation query?
You can of course group by more than one field:
{ $group: { _id: { date: '$date', services: '$services' } }
But that is not what you want it seems. You can not every easily convert data to keys, unless you can do that all by hand. The following query would be an option:
db.test.aggregate( [
{ $group: {
'_id' : { date: '$date' },
a: { $sum: {
$cond: [ { $eq: [ '$type', 'a' ] }, 1, 0 ]
} },
b: { $sum: {
$cond: [ { $eq: [ '$type', 'b' ] }, 1, 0 ]
} },
c: { $sum: {
$cond: [ { $eq: [ '$type', 'c' ] }, 1, 0 ]
} },
} },
{ $project: {
_id: 0,
date: '$_id.date',
a: '$a',
b: '$b',
c: '$c',
} }
] );
You will need to manually add a line for each new type.
By assuming you have fixed number of types, you can solve it as follows :
db.collection.aggregate(
{$group : {_id : "$date",
a:{$sum:{$cond:[{$eq:['$type','a']},1,0]}},
b:{$sum:{$cond:[{$eq:['$type','b']},1,0]}},
c:{$sum:{$cond:[{$eq:['$type','c']},1,0]}}
}},
{$project : {_id : 0, date : "$_id", a: "$a", b : "$b", c : "$c"}}
)