MongoDB convert group object to key:value (_id:count) object - arrays

I have an aggregation pipeline with result like that.
[
{
"_id":"1621",
"count":567
},
{
"_id":"1658",
"count":1089
}
...
]
How i can convert this result into key:value pair ("_idValue": "countValue"), like this?
{
"1621": 567,
"1658": 1089
...
}
My pipeline is:
pipeline = [
{'$match': {
'date': {'$gte': start_date, '$lt': end_date}
}},
{
'$group': {
'_id': '$networkId',
'count': {'$sum': 1}
}
},
{
'$sort': {'_id': 1}
},
]

Add below stages after your pipeline stages,
$group by null and construct the array of key-value pair
$arrayToObject convert above formatted array to object
$repalceRoot to replace above converted object to root
pipeline = [
// .. add your pipeline stages here
{
$group: {
_id: null,
object: {
$push: { k: "$_id", v: "$count" }
}
}
},
{
$replaceRoot: { newRoot: { $arrayToObject: "$object" } }
}
]
Playground

Related

How to add field to nested array that looks at another field in the same array item MongoDb

for example i have
{
...
myObjects = [ {nmbr: 1}, {nmbr:2}]
}
now I want:
{
...
myObjects = [ {nmbr: 1, id: 1}, {nmbr:2, id :2}]
}
using:
db.collection.aggregate([
{
"$addFields": {
"myObjects.id": "$myObjects.nmbr"
}
}
])
has this result
{
...
myObjects = [ {nmbr: 1, id_:[1,2]}, {nmbr:2, id:[1,2]}]
}
which is not what I expected, any solution?
$unwind: Deconstructs myObjects array field from the source documents to output a document for each element.
$addFields: Create id property with value myObject.nmbr in myObject field.
$group: Group by $id (ObjectId) to combine into myObjects array (reverse $unwind).
db.collection.aggregate([
{
"$unwind": "$myObjects"
},
{
"$addFields": {
"myObjects.id": "$myObjects.nmbr"
}
},
{
$group: {
_id: "$id",
"myObjects": {
$push: "$myObjects"
}
}
}
])
Output
[
{
"_id": null,
"myObjects": [
{
"id": 1,
"nmbr": 1
},
{
"id": 2,
"nmbr": 2
}
]
}
]
Sample Mongo Playground

How to query two collections with related data?

I have 2 collections, collection A has some documents like {'id':1,'field':'name'},{'id':1,'field':'age'},and collection B has some documents like
{'_id':1,'name':'alice','age':18,'phone':123},{'_id':2,'name':'bob','age':30,'phone':321}
and I want to find all the document whose '_id' is in collectionA, and just project the corresponding field.
for example:
collection A
{'id':1,'field':'name'},
{'id':1,'field':'age'}
collection B
{'_id':1,'name':'alice','age':18,'phone':123},
{'_id':2,'name':'bob','age':30,'phone':321}
the result is:
{'name':'alice','age':18},
I don't know if there is an easy way to do that?
You can use $lookup to join two collection
db.col1.aggregate([
{
$match: {
id: 1
}
},
{
"$lookup": {
"from": "col2",
"localField": "id",
"foreignField": "_id",
"as": "listNames"
}
},
{
$project: {
listNames: {
$first: "$listNames"
}
}
},
{
$project: {
_id: 0,
name: "$listNames.name",
age: "$listNames.age"
}
}
])
Mongo Playground: https://mongoplayground.net/p/E-0WvK_SUS_
So the idea is:
Convert the documents in to key, value pair for both the collections using $objectToArray.
Then perform a join operation based on key k and (id <-> _id) using $lookup.
Replace the result as root element using $replaceRoot.
Convert array to object using $arrayToObject and again $replaceRoot.
Query:
db.colB.aggregate([
{
$project: {
temp: { $objectToArray: "$$ROOT" }
}
},
{
$lookup: {
from: "colA",
let: { temp: "$temp", colB_id: "$_id" },
pipeline: [
{
$addFields: {
temp: { k: "$field", v: "$id" }
}
},
{
$match: {
$expr: {
$and: [
{ $in: ["$temp.k", "$$temp.k"] },
{ $eq: ["$temp.v", "$$colB_id"] }
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$$temp",
as: "item",
cond: { $eq: ["$field", "$$item.k"] }
}
}
}
}
}
],
as: "array"
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$array" }
}
}
]);
Output:
{
"name" : "alice",
"age" : 18
}

mongodb array aggregation extract to parent

I am trying to get first date from inner array in mongodb object and add it to it's parent with aggregation. Example:
car: {
"model": "Astra",
"productions": [
"modelOne": {
"dateOfCreation": "2019-09-30T10:15:25.026+00:00",
"dateOfEstimation": "2017-09-30T10:15:25.026+00:00",
"someOnterInfo": "whatever"
},
"modelTwo": {
"dateOfCreation": "2017-09-30T10:15:25.026+00:00",
"dateOfEstimation": "2019-09-30T10:15:25.026+00:00",
"someOnterInfo": "whatever"
}
]
}
to be turned in
car: {
"model": "Astra",
"earliestDateOfEstimation": "2017-09-30T10:15:25.026+00:00",
"earliestDateOfCreation": "2017-09-30T10:15:25.026+00:00"
}
How can I achieve that?
I'm assuming that modelOne and modelTwo are unknown when you start your aggregation. The key step is to run $map along with $objectToArray in order to get rid of those two values. Then you can just use $min to get "earliest" values:
db.collection.aggregate([
{
$addFields: {
dates: {
$map: {
input: "$car.productions",
in: {
$let: {
vars: { model: { $arrayElemAt: [ { $objectToArray: "$$this" }, 0 ] } },
in: "$$model.v"
}
}
}
}
}
},
{
$project: {
_id: 1,
"car.model": 1,
"car.earliestDateOfEstimation": { $min: "$dates.dateOfEstimation" },
"car.earliestDateOfCreation": { $min: "$dates.dateOfCreation" },
}
}
])
Mongo Playground
EDIT:
First step can be simplified if there's always modelOne, 'modelTwo'... (fixed number)
db.collection.aggregate([
{
$addFields: {
dates: { $concatArrays: [ "$car.productions.modelOne", "$car.productions.modelTwo" ] }
}
},
{
$project: {
_id: 1,
"car.model": 1,
"car.earliestDateOfEstimation": { $min: "$dates.dateOfEstimation" },
"car.earliestDateOfCreation": { $min: "$dates.dateOfCreation" },
}
}
])
Mongo Playground (2)

Updating data type to an Object in mongoDB

I have changed one of the fields of my collection in mongoDB from an array of strings to an array of object containing 2 strings. New documents get inserted without any problem, but when a get method is called to get , querying all the documents I get this error:
Failed to decode 'Students'. Decoding 'photoAddresses' errored
with: readStartDocument can only be called when CurrentBSONType is
DOCUMENT, not when CurrentBSONType is STRING.
photoAddresses is the field that was changed in Students.
I was wondering is there any way to update all the records so they all have the same data type, without losing any data.
The old version of photoAdresses:
"photoAddresses" : ["something","something else"]
This should be updated to the new version like this:
"photoAddresses" : [{photoAddresses:"something"},{photoAddresses:"something else"}]
The following aggregation queries update the string array to object array, only if the array has string elements. The aggregation operator $map is used to map the string array elements to objects. You can use any of the two queries.
db.test.aggregate( [
{
$match: {
$expr: { $and: [ { $isArray: "$photo" },
{ $gt: [ { $size: "$photo" }, 0 ] }
]
},
"photo.0": { $type: "string" }
}
},
{
$project: {
photo: {
$map: {
input: "$photo",
as: "ph",
in: { addr: "$$ph" }
}
}
}
},
] ).forEach( doc => db.test.updateOne( { _id: doc._id }, { $set: { photo: doc.photo } } ) )
The following query works with MongoDB version 4.2+ only. Note the update operation is an aggregation instead of an update. See updateMany.
db.test.updateMany(
{
$expr: { $and: [ { $isArray: "$photo" },
{ $gt: [ { $size: "$photo" }, 0 ] }
]
},
"photo.0": { $type: "string" }
},
[
{
$set: {
photo: {
$map: {
input: "$photo",
as: "ph",
in: { addr: "$$ph" }
}
}
}
}
]
)
[EDIT ADD]: The following query works with version MongoDB 3.4:
db.test.aggregate( [
{
$addFields: {
matches: {
$cond: {
if: { $and: [
{ $isArray: "$photoAddresses" },
{ $gt: [ { $size: "$photoAddresses" }, 0 ] },
{ $eq: [ { $type: { $arrayElemAt: [ "$photoAddresses", 0 ] } }, "string" ] }
] },
then: true,
else: false
}
}
}
},
{
$match: { matches: true }
},
{
$project: {
photoAddresses: {
$map: {
input: "$photoAddresses",
as: "ph",
in: { photoAddresses: "$$ph" }
}
}
}
},
] ).forEach( doc => db.test.updateOne( { _id: doc._id }, { $set: { photoAddresses: doc.photoAddresses } } ) )

Alias for each "_id" in array

How can i set alias for each "_id" in array of students.
I want to set an alias for _id in this json output.
{
"items": [
{
"id": "5d7aa7c1cba3435ebcb069c6",
"start": "2019-01-01T10:00:00.000Z",
"end": "2019-01-01T10:00:00.000Z",
"description": "test",
"students": [
{
"_id": "5d7aa779cba3435ebcb069c5", // <- alias _id to id
"name": "Jon",
"surname": "Snow"
}
]
}
]
}
How can i do that with Aggregation operations?
Use the native $map operator in your aggregate pipeline to transform the array. You would need to nest two $map operations; one
for the items array and an inner $map for the students array:
const studentsMap = {
'$map': {
'input': '$$this.students',
'as': 'student',
'in': {
'id': '$$student._id',
'name': '$$student.name',
'surname': '$$student.surname'
}
}
}
db.collection.aggregate([
{ '$addFields': {
'items': {
'$map': {
'input': '$items',
'in': {
'id': '$$this.id',
'start': '$$this.start',
'end': '$$this.end',
'description': '$$this.description',
'students': studentsMap
}
}
}
} }
])
Use map to transform.
items = items.map((item)=>{
item.students = item.students.map((student)=>{
student.id = student._id;
delete student._id;
return student;
})
return item;
})
A better approach would be to avoid hard coding of the fields in the $map stage as the maintenance of such queries becomes difficult when document size grows. The following query can get us the expected output and it's independent of the fields present in the document. As it would only focus on replacing the _id present in items.students.
db.collection.aggregate([
{
$addFields:{
"items":{
$map:{
"input":"$items",
"as":"item",
"in":{
$mergeObjects:[
"$$item",
{
"students":{
$map:{
"input":"$$item.students",
"as":"student",
"in":{
$mergeObjects:[
"$$student",
{
"id":"$$student._id"
}
]
}
}
}
}
]
}
}
}
}
},
{
$project:{
"items.students._id":0
}
}
]).pretty()

Resources