Project array of objects from results into array of arrays - mongodb

I have a mongodb query that results data in the following format
[{"_id":1471424941,"value":[1444,0]},{"_id":1471424941,"value":[1444,0]}]
and I would like to convert the result into
[[1471424941, 1444,0],[1471424941, 1444,0]]
Will this be possible using aggregate method ? I want to avoid using Javascript for the conversion and want to do it using mongodb if possible.
Can MongoDb produce an aggreation that will remote the value of the keys from the result

Run the following aggregation pipeline which uses the $concatArrays operator to concatenate arrays and get the desired result as a key:
db.collection.aggregate([
{
"$project": {
"items": { "$concatArrays": [ "$value", ["$_id"] ] }
}
},
{
"$group": {
"_id": null,
"items": { "$push": "$items" }
}
}
])

Related

MongoDB Aggregation: Dedupe by array in subdocuments

I have an aggregation query which calculates records by tag combinations this query is working well however it has one issue which is that it duplicates documents for tag combinations that are in different orders e.g. i could have one document with the tags: ['one', 'two'] and a second document with ['two' 'one'] the rest of the data would be exactly the same.
My first thought would be to do a $group aggregation query and search how to order the arrays in a project query however i cannot find anywhere how to do this. I did see for update queries you can use '$push' however this feature doesnt seem to exist for $project queries.
an example document at this phase is something like this
_id: "sadasdsad"
tags: ['one', 'two'],
total_count:37,
second_count:14,
what would be the best approach to solving this issue?
You can sort your array using $unwind,$sort and finally $group so all your tags are the same before grouping. Example : https://mongoplayground.net/p/EZi04LfY1ff
However, I would try to store those tags already sorted. So you can avoid these steps.
db.collection.aggregate({
"$unwind": "$tag"
},
{
"$sort": {
key: 1,
tag: 1
}
},
{
"$group": {
"_id": "$key",
"tag": {
"$push": "$tag"
}
}
},
{
"$group": {
"_id": "$tag",
"field": {
"$push": "$$ROOT"
}
}
})

How to group documents of a collection to a map with unique field values as key and count of documents as mapped value in mongodb?

I need a mongodb query to get the list or map of values with unique value of the field(f) as the key in the collection and count of documents having the same value in the field(f) as the mapped value. How can I achieve this ?
Example:
Document1: {"id":"1","name":"n1","city":"c1"}
Document2: {"id":"2","name":"n2","city":"c2"}
Document3: {"id":"3","name":"n1","city":"c3"}
Document4: {"id":"4","name":"n1","city":"c5"}
Document5: {"id":"5","name":"n2","city":"c2"}
Document6: {"id":"6,""name":"n1","city":"c8"}
Document7: {"id":"7","name":"n3","city":"c9"}
Document8: {"id":"8","name":"n2","city":"c6"}
Query result should be something like this if group by field is "name":
{"n1":"4",
"n2":"3",
"n3":"1"}
It would be nice if the list is also sorted in the descending order.
It's worth noting, using data points as field names (keys) is somewhat considered an anti-pattern and makes tooling difficult. Nonetheless if you insist on having data points as field names you can use this complicated aggregation to perform the query output you desire...
Aggregation
db.collection.aggregate([
{
$group: { _id: "$name", "count": { "$sum": 1} }
},
{
$sort: { "count": -1 }
},
{
$group: { _id: null, "values": { "$push": { "name": "$_id", "count": "$count" } } }
},
{
$project:
{
_id: 0,
results:
{
$arrayToObject:
{
$map:
{
input: "$values",
as: "pair",
in: ["$$pair.name", "$$pair.count"]
}
}
}
}
},
{
$replaceRoot: { newRoot: "$results" }
}
])
Aggregation Explanation
This is a 5 stage aggregation consisting of the following...
$group - get the count of the data as required by name.
$sort - sort the results with count descending.
$group - place results into an array for the next stage.
$project - use the $arrayToObject and $map to pivot the data such
that a data point can be a field name.
$replaceRoot - make results the top level fields.
Sample Results
{ "n1" : 4, "n2" : 3, "n3" : 1 }
For whatever reason, you show desired results having count as a string, but my results show the count as an integer. I assume that is not an issue, and may actually be preferred.

Robomongo query to return a list of ids

I want to query my database in Mongo and then be able to copy and paste the list of ids the query returns.
I know I can project the _id like
db.getCollection('mymodel').find({}}, { _id: 1 })
But I want to be able to copy and paste the result as an array of ids, is there a way to achieve this with Robomongo/Mongo?
Is this query you want?
Using aggregate add all _ids to a set:
db.collection.aggregate([
{
"$group": { "_id": null, "ids": { "$addToSet": "$_id" } }
},
{
"$project": { "_id": 0 }
}
])
And the ouput is similar to this, an array called ids with all id:
"ids": [
ObjectId("5a934e000102030405000000"),
ObjectId("5a934e000102030405000004"),
ObjectId("5a934e000102030405000001"),
ObjectId("5a934e000102030405000005"),
ObjectId("5a934e000102030405000003"),
ObjectId("5a934e000102030405000002")
]
You can use $match to filter the documents you want to get the id like this example.

Unwind dictionary values in mongodb aggregation framework

I need to create some plots from single documents existing in mongodb. I can only use the mongodb aggregation framework (so for example I cannot just pull the documents into python and work with it there). I am using the query builder of metabase, so I am limited from this regard.
In order to do this, I am first using some $match queries in order to identify the documents that I need to look at (these are predefined and static). After the $match stage, I am left with one document (this is ok) with the following structure.
{
"id": 1,
"locs": {
"a":1,
"b":2,
"c":3
}
}
I need to change this structure to something like this:
[{"a":1}, {"b":2}, {"c":3"}]
or any other form that would allow me to create pie charts out of the structure.
Thanks!
You can convert locs object to array using $objectToArray. Now $unwind the locs array to split into multiple documents. Use $group with $push accumulator to make the split data again into k and v format. And finally use $replaceRoot with the final data field to move it to $$ROOT position.
db.collection.aggregate([
{ "$project": { "data": { "$objectToArray": "$locs" }}},
{ "$unwind": "$data" },
{ "$group": {
"_id": "$data",
"data": { "$push": { "k": "$data.k", "v": "$data.v" }}
}},
{ "$project": {
"data": { "$arrayToObject": "$data" }
}},
{ "$replaceRoot": { "newRoot": "$data" }}
])

Encapsulate/Transform an Array into an Array of Objects

I am currently in the process of modifying a schema and I need to do a relatively trivial transform using the aggregation framework and a bulkWrite.
I want to be able to take this array:
{
...,
"images" : [
"http://example.com/...",
"http://example.com/...",
"http://example.com/..."
]
}
and aggregate to a similar array where the original value is encapsulated:
{
...,
"images" : [
{url: "http://example.com/..."},
{url: "http://example.com/..."},
{url: "http://example.com/..."}
]
}
This slow query works, but it is ridiculously expensive to unwind an entire collection.
[
{
$match: {}
},
{
$unwind: {
path : "$images",
}
},
{
$group: {
_id: "$_id",
images_2: {$addToSet: {url: "$images"}}
}
},
]
How can this be achieved with project or some other cheaper aggregation?
$map expression should do the job, try this:
db.col.aggregate([
{
$project: {
images: {
$map: {
input: '$images',
as: 'url',
in: {
url: '$$url'
}
}
}
}
}
]);
You don't need to use the bulkWrite() method for this.
You can use the $map aggregation array operator to apply an expression to each element element in your array.
Here, the expression simply create a new object where the value is the item in the array.
let mapExpr = {
"$map": {
"input": "$images",
"as": "imageUrl",
"in": { "url": "$$imageUrl }
}
};
Finally you can use the $out aggregation pipeline operator to overwrite your collection or write the result into a different collection.
Of course $map is not an aggregation pipeline operator so which means that the $map expression must be use in a pipeline stage.
The way you do this depends on your MongoDB version.
The best way is in MongoDB 3.4 using $addFields to change the value of the "images" field in your document.
db.collection.aggregate([
{ "$addFields": { "images": mapExpr }},
{ "$out": "collection }
])
From MongoDB 3.2 backwards, you need to use the $project pipeline stage but you also need to include all the other fields manually in your document
db.collection.aggregate([
{ "$project": { "images": mapExpr } },
{ "$out": "collection }
])