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)
Related
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
}
Here is my Json in Mongo DB Compass. I am just querying greater than rating products from each collection.
Note: if I am doing with pageCount it is working fine because that is not inside a collection.
{PageCount:{gte:2}} -- works.
Problem with inner arrays collection of collection if anyone matches it displays all.
When we are doing the below query if anyone of the index have greater than 99 it shows all the values.
{"ProductField.ProductDetailFields.ProductDetailInfo.ProductScore.Rating": {$exists:true, $ne: null , $gte: 99}}
----- if I perform above query, I am getting this output.
How to iterate like foreach kind of things and check the condition in MongoDB querying
{
"_id":{
"$oid":"5fc73a7b3fb52d00166554b9"
},
"ProductField":{
"PageCount":2,
"ProductDetailFields":[
{
"PageNumber":1,
"ProductDetailInfo":[
{
"RowIndex":0,
"ProductScore":{
"Name":"Samsung",
"Rating":99
},
},
{
"RowIndex":1,
"ProductScore":{
"Name":"Nokia",
"Rating":96
},
},
{
"RowIndex":2,
"ProductScore":{
"Name":"Apple",
"Rating":80
},
}
]
}
]
}
},
{
"_id":{
"$oid":"5fc73a7b3fb52d0016655450"
},
"ProductField":{
"PageCount":2,
"ProductDetailFields":[
{
"PageNumber":1,
"ProductDetailInfo":[
{
"RowIndex":0,
"ProductScore":{
"Name":"Sony",
"Rating":93
}
},
{
"RowIndex":1,
"ProductScore":{
"Name":"OnePlus",
"Rating":93
}
},
{
"RowIndex":2,
"ProductScore":{
"Name":"BlackBerry",
"Rating":20
}
}
]
}
]
}
}
#Misky How to run this query execute:
While run this query in Mongo Shell - no sql client throws below error. we are using 3.4.9 https://www.nosqlclient.com/demo/
Is this somewhat close to your idea
db.collection.aggregate({
$addFields: {
"ProductField.ProductDetailFields": {
$map: {
"input": "$ProductField.ProductDetailFields",
as: "pdf",
in: {
$filter: {
input: {
$map: {
"input": "$$pdf.ProductDetailInfo",
as: "e",
in: {
$cond: [
{
$gte: [
"$$e.ProductScore.Rating",
99
]
},
{
$mergeObjects: [
"$$e",
{
PageNumber: "$$pdf.PageNumber"
}
]
},
null
]
}
}
},
as: "i",
cond: {
$ne: [
"$$i",
null
]
}
}
}
}
}
}
},
{
$addFields: {
"ProductField.ProductDetailFields": {
"$arrayElemAt": [
"$ProductField.ProductDetailFields",
0
]
}
}
})
LIVE VERSION
aggregation in nodejs resulting in nested json, can I get it without nesting, taking only one data _id from all collections. Is there any possibility to get the data without a nested json
I was trying aggregation in nodejs with the below code. I got the output as given in output session below. But I would like to get the output as expected output, since I cant use looping on looping
Student.aggregate([
{
$match: { name: 'abcd'}
},
{
$lookup:{
from:'teachers',
pipeline: [
{
$match: { name: 'pqrs' }
},
{
$project:{
"_id":1
}
}
],
as: "teacherLookup"
}
},
{
$lookup:
{
from:'subjects',
pipeline: [
{
$match: { name: 'computer' }
},
{
$project:{
"_id":1
}
}
],
as: "subjectLookup"
}
}
])
output
[
{
_id: '52301c7878965455d2a4',
teacherLookup: [ '5ea737412589688930' ],
subjectLookup: [ '5ea745821369999917' ]
}
]
I am expecting the output as (without nested json)
[
{
studentId: '5ea1c7878965455d2a4',
teacherId: '5ea737412589688930' ,
subjectId: '5ea745821369999917'
}
]
You can use $arrayElemAt to get the first element from the array.
Student.aggregate([
{
$match: { name: "abcd" },
},
{
$lookup: {
from: "teachers",
pipeline: [
{
$match: { name: "pqrs" },
},
{
$project: {
_id: 1,
},
},
],
as: "teacherId",
},
},
{
$lookup: {
from: "subjects",
pipeline: [
{
$match: { name: "computer" },
},
{
$project: {
_id: 1,
},
},
],
as: "subjectId",
},
},
{
$project: {
teacherId: { $arrayElemAt: ["$teacherId", 0] },
subjectId: { $arrayElemAt: ["subjectId", 0] },
},
}
]);
I would like to combine arrays within arrays in MongodB
for example,
test collection has documents in following formats
**{
cmp_id :1
depts : [{"dept_id":1, emps:[1,2,3]}, {"dept_id":2, emps:[4,5,6]}, {"dept_id":2, emps:[7,8,9]} ]
}**
I need following output, How can I?
***{
cmp_id :1,
empids : [1,2,3,4,5,6,7,8,9]
}***
You can use below aggregation
db.collection.aggregate([
{
$project: {
depts: {
$reduce: {
input: "$depts",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this.emps"
]
}
}
}
}
}
])
MongoPlayground
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: "$depts"
}
},
// Stage 2
{
$unwind: {
path: "$depts.emps"
}
},
// Stage 3
{
$group: {
_id: '$cmp_id',
empids: {
$push: '$depts.emps'
}
}
},
]
);
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 } } ) )