Mongodb sort inner array - mongodb

I've been looking for a while now and can't seem to sort an inner array and keep that in the doc that I'm currently working with.
{
"service": {
"apps": {
"updates": [
{
"n" : 1
"date": ISODate("2012-03-10T16:15:00Z")
},
{
"n" : 2
"date": ISODate("2012-01-10T16:15:00Z")
},
{
"n" : 5
"date": ISODate("2012-07-10T16:15:00Z")
}
]
}
}
}
So I want to keep the item to be returned as the service, but have my updates array sorted. So far with the shell I have:
db.servers.aggregate(
{$unwind:'$service'},
{$project:{'service.apps':1}},
{$unwind:'$service.apps'},
{$project: {'service.apps.updates':1}},
{$sort:{'service.apps.updates.date':1}});
Anyone think they can help on this?

You can do this by $unwinding the updates array, sorting the resulting docs by date, and then $grouping them back together on _id using the sorted order.
db.servers.aggregate(
{$unwind: '$service.apps.updates'},
{$sort: {'service.apps.updates.date': 1}},
{$group: {_id: '$_id', 'updates': {$push: '$service.apps.updates'}}},
{$project: {'service.apps.updates': '$updates'}})

Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.
For instance, in order to sort an array of objects by one of their fields:
// {
// "service" : { "apps" : { "updates" : [
// { "n" : 1, "date" : ISODate("2012-03-10T16:15:00Z") },
// { "n" : 2, "date" : ISODate("2012-01-10T16:15:00Z") },
// { "n" : 5, "date" : ISODate("2012-07-10T16:15:00Z") }
// ]}}
// }
db.collection.aggregate(
{ $set: {
{ "service.apps.updates":
{ $function: {
body: function(updates) {
updates.sort((a, b) => a.date - b.date);
return updates;
},
args: ["$service.apps.updates"],
lang: "js"
}}
}
}
)
// {
// "service" : { "apps" : { "updates" : [
// { "n" : 2, "date" : ISODate("2012-01-10T16:15:00Z") },
// { "n" : 1, "date" : ISODate("2012-03-10T16:15:00Z") },
// { "n" : 5, "date" : ISODate("2012-07-10T16:15:00Z") }
// ]}}
// }
This modifies the array in place, without having to apply a combination of expensive $unwind, $sort and $group stages.
$function takes 3 parameters:
body, which is the function to apply, whose parameter is the array to modify.
args, which contains the fields from the record that the body function takes as parameter. In our case "$service.apps.updates".
lang, which is the language in which the body function is written. Only js is currently available.

Starting in Mongo 5.2, it's the exact use case for the new $sortArray aggregation operator:
// {
// service: { apps: { updates: [
// { n: 1, date: ISODate("2012-03-10") },
// { n: 2, date: ISODate("2012-01-10") },
// { n: 5, date: ISODate("2012-07-10") }
// ]}}
// }
db.collection.aggregate([
{ $set: {
"service.apps.updates": {
$sortArray: {
input: "$service.apps.updates",
sortBy: { date: 1 }
}
}
}}
])
// {
// service: { apps: { updates: [
// { n: 2, date: ISODate("2012-01-10") },
// { n: 1, date: ISODate("2012-03-10") },
// { n: 5, date: ISODate("2012-07-10") }
// ]}}
// }
This:
sorts ($sortArray) the service.apps.updates array (input: "$service.apps.updates")
by applying a sort on dates (sortBy: { date: 1 })
without having to apply a combination of expensive $unwind, $sort and $group stages

Related

Mongodb aggregation count by nested object key

db.artists.insertMany([
{ "_id" : 1, "achievements" : {"third_record":true, "second_record": true} },
{ "_id" : 3, "achievements" : {"sixth_record":true, "second_record": true} },
{ "_id" : 2, "achievements" : {"first_record":true, "fifth_record": true} },
{ "_id" : 4, "achievements" : {"first_record":true, "second_record": true} },
])
I would like to count how many first_record, second_record, etc achievements have been obtained, I don't know beforehand the names of the achievements. I just want it to count all the achievements matched in the first stage. How do I use aggregation to count this? I saw another question suggest using unwind but that seems to be for arrays only and not objects?
May be this:
db.collection.aggregate([
{
$project: {
as: {
$objectToArray: "$achievements"
}
}
},
{
$unwind: "$as"
},
{
$group: {
_id: "$as.k",
number: {
$sum: {
"$cond": [
{
$eq: [
"$as.v",
true
]
},
1,
0
]
}
}
}
}
])
Idea
convert object to array
unwind to get them separate
group by id, adding 1 for true, 0 for false.

MongoDB two groups Aggregate

Aggregation operations process data records and return computed results. Aggregation operations group values from multiple documents together, and can perform a variety of operations on the grouped data to return a single result. MongoDB provides three ways to perform aggregation: the aggregation pipeline, the map-reduce function, and single purpose aggregation methods.
I would like to transform that :
{
"_id" : ObjectId("5836b919885383034437d4a7"),
"Identificador" : "G-3474",
"Miembros" : [
{
"_id" : ObjectId("5836b916885383034437d238"),
"Nombre" : "Pilar",
"Email" : "pcarrillocasa#gmail.es",
"Edad" : 24,
"País" : "España",
"Tipo" : "Usuario individual",
"Apellidos" : "Carrillo Casa",
"Teléfono" : 637567234,
"Ciudad" : "Santander",
"Identificador" : "U-3486",
"Información_creación" : {
"Fecha_creación" : {
"Mes" : 4,
"Día" : 22,
"Año" : 2016
},
"Hora_creación" : {
"Hora" : 15,
"Minutos" : 34,
"Segundos" : 20
}
}
}
}
into that
{
"Nombre_Grupo" : "Amigo invisible"
"Ciudades" : [
{
"Ciudad" : "Madrid",
"Miembros": 30
},
{
"Ciudad" : "Almería",
"Miembros": 10
}
{
"Ciudad" : "Badajoz",
"Miembros": 20
}
]
}
with MongoDB.
I tried with that:
db.Grupos_usuarios.aggregate([
{ $group: { _id: "$Nombre_Grupo",total: { $sum: "$amount" } },
$group: { _id: "$Ciudad",total: { $sum: "$amount" } } }
])
but I could not get what I needed.
May somebody help me to know what I am doing bad?
The following aggregation gets the output you are looking for.
The $unwind stage deconstructs an array field from the input documents to output a document for each element. These documents are used to group by the Miembros.Ciudad and get the total Miembros for each Ciudad. In the second group stage we Pivot data to get all the Ciudades from the previous grouping into an array. The last $project is for formatting the output.
db.test.aggregate( [
{
$unwind: "$Miembros"
},
{
$group: {
_id: "$Miembros.Ciudad",
total: { $sum: 1 }
}
},
{
$group: {
_id: "Amigo invisible",
Ciudades: { $push: { Ciudad: "$_id", Miembros: "$total"} }
}
},
{
$project: {
Nombre_Grupo: "$_id",
Ciudades: 1,
_id: 0
}
}
] )

How to get a sorted result of $lookup [duplicate]

I've been looking for a while now and can't seem to sort an inner array and keep that in the doc that I'm currently working with.
{
"service": {
"apps": {
"updates": [
{
"n" : 1
"date": ISODate("2012-03-10T16:15:00Z")
},
{
"n" : 2
"date": ISODate("2012-01-10T16:15:00Z")
},
{
"n" : 5
"date": ISODate("2012-07-10T16:15:00Z")
}
]
}
}
}
So I want to keep the item to be returned as the service, but have my updates array sorted. So far with the shell I have:
db.servers.aggregate(
{$unwind:'$service'},
{$project:{'service.apps':1}},
{$unwind:'$service.apps'},
{$project: {'service.apps.updates':1}},
{$sort:{'service.apps.updates.date':1}});
Anyone think they can help on this?
You can do this by $unwinding the updates array, sorting the resulting docs by date, and then $grouping them back together on _id using the sorted order.
db.servers.aggregate(
{$unwind: '$service.apps.updates'},
{$sort: {'service.apps.updates.date': 1}},
{$group: {_id: '$_id', 'updates': {$push: '$service.apps.updates'}}},
{$project: {'service.apps.updates': '$updates'}})
Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.
For instance, in order to sort an array of objects by one of their fields:
// {
// "service" : { "apps" : { "updates" : [
// { "n" : 1, "date" : ISODate("2012-03-10T16:15:00Z") },
// { "n" : 2, "date" : ISODate("2012-01-10T16:15:00Z") },
// { "n" : 5, "date" : ISODate("2012-07-10T16:15:00Z") }
// ]}}
// }
db.collection.aggregate(
{ $set: {
{ "service.apps.updates":
{ $function: {
body: function(updates) {
updates.sort((a, b) => a.date - b.date);
return updates;
},
args: ["$service.apps.updates"],
lang: "js"
}}
}
}
)
// {
// "service" : { "apps" : { "updates" : [
// { "n" : 2, "date" : ISODate("2012-01-10T16:15:00Z") },
// { "n" : 1, "date" : ISODate("2012-03-10T16:15:00Z") },
// { "n" : 5, "date" : ISODate("2012-07-10T16:15:00Z") }
// ]}}
// }
This modifies the array in place, without having to apply a combination of expensive $unwind, $sort and $group stages.
$function takes 3 parameters:
body, which is the function to apply, whose parameter is the array to modify.
args, which contains the fields from the record that the body function takes as parameter. In our case "$service.apps.updates".
lang, which is the language in which the body function is written. Only js is currently available.
Starting in Mongo 5.2, it's the exact use case for the new $sortArray aggregation operator:
// {
// service: { apps: { updates: [
// { n: 1, date: ISODate("2012-03-10") },
// { n: 2, date: ISODate("2012-01-10") },
// { n: 5, date: ISODate("2012-07-10") }
// ]}}
// }
db.collection.aggregate([
{ $set: {
"service.apps.updates": {
$sortArray: {
input: "$service.apps.updates",
sortBy: { date: 1 }
}
}
}}
])
// {
// service: { apps: { updates: [
// { n: 2, date: ISODate("2012-01-10") },
// { n: 1, date: ISODate("2012-03-10") },
// { n: 5, date: ISODate("2012-07-10") }
// ]}}
// }
This:
sorts ($sortArray) the service.apps.updates array (input: "$service.apps.updates")
by applying a sort on dates (sortBy: { date: 1 })
without having to apply a combination of expensive $unwind, $sort and $group stages

Add some kind of row number to a mongodb aggregate command / pipeline

The idea is to return a kind of row number to a mongodb aggregate command/ pipeline. Similar to what we've in an RDBM.
It should be a unique number, not important if it matches exactly to a row/number.
For a query like:
[ { $match: { "author" : { $ne: 1 } } }, { $limit: 1000000 } ]
I'd like to return:
{ "rownum" : 0, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }
{ "rownum" : 1, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }
{ "rownum" : 2, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }
{ "rownum" : 3, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }
{ "rownum" : 4, "title" : "Iliad", "author" : "Homer", "copies" : 10 }
Is it possible to generate this rownum in mongodb?
Not sure about the performance in big queries, but this is at least an option.
You can add your results to an array by grouping/pushing and then unwind with includeArrayIndex like this:
[
{$match: {author: {$ne: 1}}},
{$limit: 10000},
{$group: {
_id: 1,
book: {$push: {title: '$title', author: '$author', copies: '$copies'}}
}},
{$unwind: {path: '$book', includeArrayIndex: 'rownum'}},
{$project: {
author: '$book.author',
title: '$book.title',
copies: '$book.copies',
rownum: 1
}}
]
Now, if your database contains a big amount of records, and you intend to paginate, you can use the $skip stage and then $limit 10 or 20 or whatever you want to display per page, and just add the number from the $skip stage to your rownum and you'll get the real position without having to push all your results to enumerate them.
Starting in Mongo 5, it's a perfect use case for the new $setWindowFields aggregation operator and its $documentNumber operation:
// { x: "a" }
// { x: "b" }
// { x: "c" }
// { x: "d" }
db.collection.aggregate([
{ $setWindowFields: {
sortBy: { _id: 1 },
output: { rowNumber: { $documentNumber: {} } }
}}
])
// { x: "a", rowNumber: 1 }
// { x: "b", rowNumber: 2 }
// { x: "c", rowNumber: 3 }
// { x: "d", rowNumber: 4 }
$setWindowFields allows us to work for each document with the knowledge of previous or following documents. Here we just need the information of the place of the document in the whole collection (or aggregation intermediate result), as provided by $documentNumber.
Note that we sort by _id because the sortBy parameter is required, but really, since you don't care about the ordering of your rows, it could be anything you'd like.
Another way would be to keep track of row_number using "$function"
[{ $match: { "author" : { $ne: 1 } }} , { $limit: 1000000 },
{
$set: {
"rownum": {
"$function": {
"body": "function() {try {row_number+= 1;} catch (e) {row_number= 0;}return row_number;}",
"args": [],
"lang": "js"
}
}
}
}]
I am not sure if this can mess up something though!

How to retrieve all matching elements present inside array in Mongo DB?

I have document shown below:
{
name: "testing",
place:"London",
documents: [
{
x:1,
y:2,
},
{
x:1,
y:3,
},
{
x:4,
y:3,
}
]
}
I want to retrieve all matching documents i.e. I want o/p in below format:
{
name: "testing",
place:"London",
documents: [
{
x:1,
y:2,
},
{
x:1,
y:3,
}
]
}
What I have tried is :
db.test.find({"documents.x": 1},{_id: 0, documents: {$elemMatch: {x: 1}}});
But, it gives first entry only.
As JohnnyHK said, the answer in MongoDB: select matched elements of subcollection explains it well.
In your case, the aggregate would look like this:
(note: the first match is not strictly necessary, but it helps in regards of performance (can use index) and memory usage ($unwind on a limited set)
> db.xx.aggregate([
... // find the relevant documents in the collection
... // uses index, if defined on documents.x
... { $match: { documents: { $elemMatch: { "x": 1 } } } },
... // flatten array documennts
... { $unwind : "$documents" },
... // match for elements, "documents" is no longer an array
... { $match: { "documents.x" : 1 } },
... // re-create documents array
... { $group : { _id : "$_id", documents : { $addToSet : "$documents" } }}
... ]);
{
"result" : [
{
"_id" : ObjectId("515e2e6657a0887a97cc8d1a"),
"documents" : [
{
"x" : 1,
"y" : 3
},
{
"x" : 1,
"y" : 2
}
]
}
],
"ok" : 1
}
For more information about aggregate(), see http://docs.mongodb.org/manual/applications/aggregation/