Convert Date to String in nested array in mongodb - javascript

I have a mongodb collection called cases and inside cases I have an array of cases per company object.
So the structure is:
Inside each case I want to use the createddate (which is a string) and endDate (also string) and convert it to a mongodb date.
When I use NoSQLBooster I add the following query:
db.cases.aggregate([
{ $match: { companyID: 218 }},
{ $unwind: "$cases" },
{ $match: { 'cases.id': '299' }},
{ $addFields: { 'cases.created': new Date('2010-06-21T00:00:00.000'), 'cases.closed': new Date('2014-08-29T00:00:00.000') }},
{ $group: { _id: "$_id", cases: { $push: "$cases" }}}])
This will add a date in a new field - created and then closed. This is exactly what I want.
However, in my code (using mongoose) I have the following:
scripts.commponent.ts:
runThroughCasesAndConvertDates(id) {
this.scriptsService.getAllCasesToModify({ companyID : id}).subscribe( res => {
if (res.length > 0) {
for (let i = 0; i < res[0].cases.length; i++) {
const caseID = res[0].cases[i].id;
const data = {
companyID: id,
caseID: caseID,
created: moment(res[0].cases[i].createddate, 'DD-MMM-YYYY h:mm a').format('YYYY-MM-DD[T00:00:00.000Z]'),
closed: ''
};
if (res[0].cases[i].endDate !== '') {
data.closed = moment(res[0].cases[i].endDate, 'DD-MMM-YYYY h:mm a').format('YYYY-MM-DD[T00:00:00.000Z]');
}
this.scriptsService.updateDates(data).subscribe();
}
}
});
}
scripts.service.ts
updateDates(body) {
return this.http.post('/db/cases/updateAllDates', body).pipe(
map(res => res.json())
);
}
casesDB.js
router.post('/updateAllDates', (req, res) => {
const { body } = req;
Cases.aggregate([
{ $match: { companyID: body.companyID }},
{ $unwind: "$cases" },
{ $match: { 'cases.id': body.caseID }},
{ $addFields: { 'cases.created': new Date(body.created), 'cases.closed': new Date(body.closed) } },
{ $group: { _id: "$_id" }
}],
function (err, data) {
res.json(data)
});
});
But it does not add anything into the array. Im really confused as to what Im doing wrong. Maybe there is a better way / approach to doing this?
Thank you

You can $map over the cases array and can change the date string fields to date object fields.
Cases.aggregate([
{ "$addFields": {
"cases": {
"$map": {
"input": "$cases",
"in": {
"$mergeObjects": [
"$$this",
{
"createddate": {
"$dateFromString": { "dateString": "$$this.createddate" }
},
"endDate": {
"$dateFromString": { "dateString": "$$this.endDate" }
}
}
]
}
}
}
}}
])
Update: If dates are blank string
Cases.aggregate([
{ "$addFields": {
"cases": {
"$map": {
"input": "$cases",
"in": {
"$mergeObjects": [
"$$this",
{
"createddate": {
"$cond": [
{ "$eq": ["$$this.createddate", ""] },
null
{ "$dateFromString": { "dateString": "$$this.createddate" } }
]
},
"endDate": {
"$cond": [
{ "$eq": ["$$this.endDate", ""] },
null
{ "$dateFromString": { "dateString": "$$this.endDate" } }
]
}
}
]
}
}
}
}}
])

Related

How to insert value in a nested mongodb document?

I have this document:
{
"_id": {
"$oid": "63cf19337c2df5fe442a2b69"
},
"createdAt": {
"$date": {
"$numberLong": "1674516787623"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1675035206032"
}
},
"clientIp": "89.132.225.21",
"products": {
"6cc5a480-91f0-4aa8-975c-013d6bd155a3": {
"currency": "EUR",
"title": "VVV",
"price": "12",
"barionId": "aa#aa.hu",
"ratingTimeLength": 12
}
}
}
I would insert like this:
const userId = req.query.userId
const productId = req.query.productId
const token = authorization.slice(7)
const userJWT = jwt.verify(token, process.env.JWT_SECRET) as JwtPayload
const ObjectId = require('mongodb').ObjectId
const id = new ObjectId().toHexString()
await collection.updateOne(
{ _id: ObjectId(userId), [`products.${productId}`]: { $exists: true } },
{
$set: {
[`products.$.payments.${id}`]: {
createdAt: new Date(),
createdBy: userJWT.userId,
},
},
},
{ upsert: true }
)
But it raise:
2023-01-29T23:43:33.653Z 1a42849c-d5aa-4127-8fdc-9169c1c6c405 ERROR MongoServerError: The positional operator did not find the match needed from the query.
When I query record in Compass, it returns the document:
{
"_id": ObjectId("63cf19337c2df5fe442a2b69"),
"products.6cc5a480-91f0-4aa8-975c-013d6bd155a3": {
"$exists": true
}
}
What is wrong?
You should access the products property directly with:
await collection.updateOne(
{ _id: ObjectId(userId), [`products.${productId}`]: { $exists: true } },
{
$set: {
[`products.payments.${id}`]: {
createdAt: new Date(),
createdBy: userJWT.userId,
},
},
},
{ upsert: true }
);

Mongoose get objects which match an object item from an array of objects

I would like to find a single document matching the courseID but inside the document only objects from the materials array whose moduleNo matches the one I give. But the query I currently use seems to return the correct document but also returns all the objects in materials array. Any help would be appreciated.
My schema,
const materialSchema = new mongoose.Schema({
courseID: String,
materials: [
{
moduleNo: Number,
moduleMaterial: String,
},
],
});
My code/query,
exports.getMaterials = (req, res) => {
const { courseID, moduleNo } = req.query;
Material.findOne(
{ courseID, "materials.moduleNo": moduleNo },
(err, result) => {
if (err) {
console.error(err);
} else {
res.json(result);
}
}
);
};
Use the $elemMatch operator, something lik this:
exports.getMaterials = (req, res) => {
const { courseID, moduleNo } = req.query;
Material.findOne(
{ courseID },
{"materials": { $elemMatch: {moduleNo: moduleNo}},
(err, result) => {
if (err) {
console.error(err);
} else {
res.json(result);
}
}
);
};
Update: To return all matching elements in the array, you will have to use an aggregation pipeline, having $filter stage, to filter out array elements. Like this:
exports.getMaterials = (req, res) => {
const { courseID, moduleNo } = req.query;
Material.aggregate(
[
{
$match: {
courseID: courseID
}
},
{
"$project": {
courseID: 1,
materials: {
"$filter": {
"input": "$materials",
"as": "material",
"cond": {
"$eq": [
"$$material.moduleNo",
moduleNo
]
}
}
}
}
}
]
);
};
Here's the playground link.
Way 1 : Use $elemMatch operator in project field
The $elemMatch operator limits the contents of an array field from
the query results to contain only the first element matching the
$elemMatch condition
Result : Returns only one matching array element
syntax :
db.collection.find(query,projection)
db.collection.find({
"field": field_value
},{
"array_name":{
$elemMatch:{"key_name": value }
},
field:1,
field_2:1,
field_3:0
})
https://mongoplayground.net/p/HKT1Gop32Pq
Way 2 : Array Field Limitations array.$ in project field
*
Result : Returns only one matching array element
db.collection.find({
"field": field_value,
"array_name.key_name": value
},{
"array_name.$":1,
field:1,
field_2:1,
field_3:0
});
https://mongoplayground.net/p/Db0azCakQg9
Update : Using MongoDB Aggregation
Result : Returns multiple matching array elements
db.collection.aggregate([
{
"$unwind": "$materials"
},
{
"$match": {
"courseID": "Demo",
"materials.moduleNo": 1
}
}
])
will return output as :
[
{
"_id": ObjectId("5a934e000102030405000000"),
"courseID": "Demo",
"materials": {
"moduleMaterial": "A",
"moduleNo": 1
}
},
{
"_id": ObjectId("5a934e000102030405000000"),
"courseID": "Demo",
"materials": {
"moduleMaterial": "B",
"moduleNo": 1
}
}
]
And If you want to format output :
db.collection.aggregate([
{
"$unwind": "$materials"
},
{
"$match": {
"courseID": "Demo",
"materials.moduleNo": 1
}
},
{
"$group": {
"_id": {
"courseID": "$courseID",
"moduleNo": "$materials.moduleNo"
},
"materials": {
"$push": "$materials.moduleMaterial"
}
}
},
{
"$project": {
"_id": 0,
"courseID": "$_id.courseID",
"moduleNo": "$_id.moduleNo",
"materials": "$materials"
}
}
])
will return result as :
[
{
"courseID": "Demo",
"materials": [
"A",
"B"
],
"moduleNo": 1
}
]
https://mongoplayground.net/p/vdPVbce6WkX

Retrieving a relationship field in mongodb aggregation

I am using mongodb aggregation with a collection named files
that has a relationship with another collection named file_upload.
files = {
type: String,
media: { type: Schema.Types.ObjectId, ref: 'file_upload', required: true },
}
file_upoad = {
name: String,
}
This is the query
const data = await strapi.query('files').model.aggregate([
{
$lookup: {
from: "analytics",
localField: "_id",
foreignField: "file_id",
as: "hits",
}
},
{ $unwind: '$hits' },
{ $group: { _id: "$_id", hitsCount: { $sum: 1 } } },
{ $sort: { hitsCount: -1 } },
{ $limit: 1 },
])
my goal is to retrieve the media as part of the result since it is a relationship field, at the moment I get this
[
{
"_id": "61fd74367b6ee77b89bae34d",
"hitsCount": 12
},
{
"_id": "61fd74367b6ee77b89sddfee",
"hitsCount": 8
}
]
expected result
[
{
"_id": "61fd74367b6ee77b89bae34d",
"hitsCount": 12,
"media": {
name:"name1"
}
},
{
"_id": "61fd74367b6ee77b89sddfee",
"hitsCount": 8,
"media": {
name:"name2"
}
}
]

Destructure arrays within the MongoDB aggregation pipeline

I was wondering if it was possible to destructure arrays while I am still in the MongoDB aggregation pipeline which would make my code alot neater.
For example, I have the following aggregation pipeline.
await User.aggregate([
{ $match: { _id: userID } },
{
$project: { chatLogs: 1, username: 1, profilePicURL: 1 },
},
{ $unwind: "$chatLogs" },
{
$lookup: {
from: "users",
let: { recipientID: "$chatLogs.recipientID" },
pipeline: [
{
$match: { $expr: { $eq: ["$_id", "$$recipientID"] } },
},
{ $project: { profilePicURL: 1 } },
],
as: "chatLogs.recipientID",
},
},
]);
This gives the following results when queried:
{
"_id": "5f2ffb54eea9c2180a732afa",
"username": "joe",
"profilePicURL": "/images/profile/default_profile.png",
"chatLogs": {
"recipientID": [
{
"_id": "5f2faf5ad18a76073729f475",
"profilePicURL": "/images/profile/default_profile.png"
}
],
"chat": "5f30b6c3d117441c2abda1ba"
}
}
In my case, because "recipientID" represents a default MongoDB id, it will always be unique. Hence I would prefer the following, where the resulting recipientID field is no longer a meaningless array
Desired results:
{
"_id": "5f2ffb54eea9c2180a732afa",
"username": "joe",
"profilePicURL": "/images/profile/default_profile.png",
"chatLogs": {
"recipientID": {
"_id": "5f2faf5ad18a76073729f475",
"profilePicURL": "/images/profile/default_profile.png"
}
"chat": "5f30b6c3d117441c2abda1ba"
}
}
You can deconstruct recipientID array using $unwind in last pipeline,
await User.aggregate([
... // your all pipelines
// add this line
{ $unwind: "$chatLogs.recipientID" }
]);

Mongo in node.js add an $gte/$lt inside an $in or something similar

I use MongoDB Node.js driver. My query looks like that :
var query = {
$and: [{
"custom_date": {
"$gte": minDateValue,
"$lt": maxDateValue
}
}, {"doc_name": {"$in": file_type}}, {"sample_type": {"$in": sample_type}}, {"mission": {"$in": mission}}, {"snow_thickness": {"$in": snow_thickness}},{
"depth_m": {
"$gte": minDepthOut,
"$lt": maxDepthOut
// Or equal to "NA"
}
}]
};
Everything works fine, but I woud like to add an $in or an $or to the field depth_m so this part of the query would be "$gte": minDepthOut,"$lt": maxDepthOut or equal to "NA" for exemple.
I mean the depth_m must be in the specified range or equal to "NA"
Thanks for your help
You can use $or query operator
{
"$and": [
{ "custom_date": { "$gte": minDateValue, "$lt": maxDateValue }},
{ "doc_name": { "$in": file_type } },
{ "sample_type": { "$in": sample_type } },
{ "mission": { "$in": mission } },
{ "snow_thickness": { "$in": snow_thickness } },
{ "$or": [
{ "depth_m": {
"$gte": minDepthOut,
"$lt": maxDepthOut
}},
{ "depth_m": "NA" }
}]
]
}

Categories

Resources