MongoDB remove elements depending on element before (Iterating) - arrays

I want to remove ($unset) elements from my MongoDB Objects with condition if the same Object has a similar element.
My Object:
{
"_id": "5eabf8b144345b36b00bfbaa",
"ranktime": [{
"pos":"2",
"datum":"Mon May 05 2020 12:22:52 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"1",
"datum":"Fri May 01 2020 12:23:10 GMT+0200 (GMT+02:00)",
"source":"SOURCE1"
},{
"pos":"37",
"datum":"Fri May 01 2020 12:25:14 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"12",
"datum":"Fri May 01 2020 12:25:14 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"37",
"datum":"Fri May 01 2020 18:45:27 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
}]
}
So I want to remove the entry in ranktime if ranktime.source == "SOURCE2" and if the date is the same as with the object before. Actually I have to iterate through the single elements of ranktime. Is this possible in MongoDB ?
The Expected outcome would be:
{
"_id": "5eabf8b144345b36b00bfbaa",
"ranktime": [{
"pos":"2",
"datum":"Mon May 05 2020 12:22:52 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
},{
"pos":"1",
"datum":"Fri May 01 2020 12:23:10 GMT+0200 (GMT+02:00)",
"source":"SOURCE1"
},{
"pos":"37",
"datum":"Fri May 01 2020 12:25:14 GMT+0200 (GMT+02:00)",
"source":"SOURCE2"
}]
}

Basically you can use $reduce to process an array and define previous element using $let and $arrayElemAt statements. The new $set syntax allows you to use aggregation within update statement:
db.col.updateMany({}, [
{
$set: {
ranktime: {
$reduce: {
input: "$ranktime",
initialValue: [],
in: {
$let: {
vars: { last: { $arrayElemAt: [ "$$value", -1 ] } },
in: {
$cond: [
{
$and: [
{ "$eq": [ "$$last.source", "SOURCE2" ] },
{ "$eq": [ { $substr: [ "$$last.datum", 0, 15 ] }, { $substr: [ "$$this.datum", 0, 15 ] } ] },
]
},
"$$value",
{ $concatArrays: [ "$$value", [ "$$this" ] ] }
]
}
}
}
}
}
}
}
])
Aggregation Example

Related

Grouping Mongo DB document with three nested arrays

I have a Mongo document with three nested arrays. Inside the document linearTvSessions, I have an array of linearTvChannels which has an array of sessions which has an array of tunerDetails.
{
"_id": "GATES:1123:E8186354149C:2022-03-04",
"date": {
"$date": {
"$numberLong": "1646352000000"
}
},
"lastReportedTime": {
"$date": {
"$numberLong": "1646436034000"
}
},
"macAddress": "E8186354149C",
"propCode": "GATES",
"roomNumber": "1123",
"linearTvChannels": [
{
"channelId": 3,
"channelName": "WMC",
"sessions": [
{
"created": {
"$date": {
"$numberLong": "1646435214000"
}
},
"duration": 170,
"isOccupied": 0,
"startTime": {
"$date": {
"$numberLong": "1646435044000"
}
},
"stopTime": {
"$date": {
"$numberLong": "1646435214000"
}
},
"tunerDetails": [
{
"created": {
"$date": {
"$numberLong": "1646435044000"
}
},
"errorLevel": 0,
"signalLevel": 44,
"signalLocked": 1,
"signalQuality": 91
},
{
"created": {
"$date": {
"$numberLong": "1646435104000"
}
},
"errorLevel": 0,
"signalLevel": 4487,
"signalLocked": 0,
"signalQuality": 1111
},
{
"created": {
"$date": {
"$numberLong": "1646435164000"
}
},
"errorLevel": 0,
"signalLevel": 44,
"signalLocked": 0,
"signalQuality": 63
}
]
}
]
}
]
}
Current output:
{
_id: 'GATES:1123:E8186354149C:2022-03-04',
lastReportedTime: 2022 - 03 - 04 T23: 20: 34.000 Z,
propCode: 'GATES',
roomNumber: '1123',
macAddress: 'E8186354149C',
date: 2022 - 03 - 04 T00: 00: 00.000 Z,
linearTvChannels: [{
channelName: 'WMC',
channelId: 3,
sessions: [{
created: 2022 - 03 - 04 T23: 06: 54.000 Z,
duration: 170,
isOccupied: 0,
startTime: 2022 - 03 - 04 T23: 04: 04.000 Z,
stopTime: 2022 - 03 - 04 T23: 06: 54.000 Z,
tunerDetails: {
created: 2022 - 03 - 04 T23: 05: 04.000 Z,
errorLevel: 0,
signalLevel: 4487,
signalLocked: 0,
signalQuality: 1111
}
},
{
created: 2022 - 03 - 04 T23: 06: 54.000 Z,
duration: 170,
isOccupied: 0,
startTime: 2022 - 03 - 04 T23: 04: 04.000 Z,
stopTime: 2022 - 03 - 04 T23: 06: 54.000 Z,
tunerDetails: {
created: 2022 - 03 - 04 T23: 06: 04.000 Z,
errorLevel: 0,
signalLevel: 44,
signalLocked: 0,
signalQuality: 63
}
}]
}]
}
Desired output:
{
_id: 'GATES:1123:E8186354149C:2022-03-04',
lastReportedTime: 2022 - 03 - 04 T23: 20: 34.000 Z,
propCode: 'GATES',
roomNumber: '1123',
macAddress: 'E8186354149C',
date: 2022 - 03 - 04 T00: 00: 00.000 Z,
linearTvChannels: [{
channelName: 'WMC',
channelId: 3,
sessions: [{
created: 2022 - 03 - 04 T23: 06: 54.000 Z,
duration: 170,
isOccupied: 0,
startTime: 2022 - 03 - 04 T23: 04: 04.000 Z,
stopTime: 2022 - 03 - 04 T23: 06: 54.000 Z,
tunerDetails: [{
created: 2022 - 03 - 04 T23: 05: 04.000 Z,
errorLevel: 0,
signalLevel: 4487,
signalLocked: 0,
signalQuality: 1111
},
{
created: 2022 - 03 - 04 T23: 06: 04.000 Z,
errorLevel: 0,
signalLevel: 44,
signalLocked: 0,
signalQuality: 63
}]
}]
}]
}
My Current query:
db.linearTvSessions.aggregate(
[{$unwind:"$linearTvChannels"}, {$unwind:"$linearTvChannels.sessions"}, {$unwind:"$linearTvChannels.sessions.tunerDetails"},
{$match:{"linearTvChannels.sessions.tunerDetails.signalLocked":0}},
{ "$group": {
"_id": {
"_id": "$_id",
"channelName": "$linearTvChannels.channelName",
"channelId": "$linearTvChannels.channelId"
},
"lastReportedTime": { "$first": "$lastReportedTime" },
"propCode": { "$first": "$propCode" },
"roomNumber": { "$first": "$roomNumber" },
"macAddress": { "$first": "$macAddress" },
"date": { "$first": "$date" },
"sessions": { "$push": "$linearTvChannels.sessions" }
}},
{ "$group": {
"_id": "$_id._id",
"lastReportedTime": { "$first": "$lastReportedTime" },
"propCode": { "$first": "$propCode" },
"roomNumber": { "$first": "$roomNumber" },
"macAddress": { "$first": "$macAddress" },
"date": { "$first": "$date" },
"linearTvChannels": { "$push": {
"channelName": "$_id.channelName",
"channelId": "$_id.channelId",
"sessions": "$sessions"
}}
}}
]
);
I unwind the three arrays in the query above and inside the third array tunerDetails, I'm matching on the signalLocked field. I'm having trouble grouping the tunerDetails in an array by sessions. In my output above, each tunerDetails is placed in a redundant session. This is expected behavior but I'm trying to place the tunerDetails in an array as shown in my desired output.
I create a third group operation with the id of sessions details similar to this but I'm loosing track of linearTvChannels details when I do so.
{ "$group": {
"_id": {
"created": "$linearTvChannels.sessions.created",
"duration": "$linearTvChannels.sessions.duration",
"isOccupied": "$linearTvChannels.sessions.isOccupied",
"startTime": "$linearTvChannels.sessions.startTime",
"stopTime": "$linearTvChannels.sessions.stopTime"
},
"tunerDetails": { "$push": "$linearTvChannels.sessions.tunerDetails" }
}}
I feel like I'm not grouping by the ids properly?
If the innermost array is the only thing to be modified, you could accomplish this with $map and $filter:
$map over linearTvChannels, replace with the result of the map
$map over sessions, replace with the result of the map
$filter tunerDetails, keeping only those with signalLocked 0
This could be done in a single $addFields stage like so:
{$addFields: {
linearTvChannels: {$map: {
input: "$linearTvChannels",
as: "chan",
in: {$mergeObjects: [
"$$chan",
{sessions: {
$map: {
input: "$$chan.sessions",
as: "session",
in: {"$mergeObjects": [
"$$session",
{tunerDetails: {
$filter: {
input: "$$session.tunerDetails",
cond: {$eq: ["$$this.signalLocked",0]}
}
}}
]}
}
}}
]}
}
}
}}
Playground

Mongo query Update year depends on the inner document field

Here is my data. I wanted to change year but It should effective to only the first item of the document array
{
"_id": {
"$oid": "62053aa8aa1cfbe8c4e72662"
},
"school": "Test",
"reports": [
{
"year": "2020", // This has to be changed to 2019
"createdAt": {
"$date": "2022-02-10T17:05:25.682Z"
},
"pid": {
"$oid": "620545d5097761628f32365a"
},
"details": {
"end_date": {
"$date": "2020-03-31T00:00:00.000Z" // when end date is prior to July 01 of the $year mentioned.
}
}
}, {
"year": "2020",
"createdAt": {
"$date": "2022-03-14T19:08:38.125Z"
},
"pid": {
"$oid": "622f92b68a408531d4b784de"
},
"details": {
"end_date": {
"$date": "2021-03-31T00:00:00.000Z"
}
}
}
]
}
In the above data, I want to reduce the year to the previous year, if details.end_date is prior to July 01 of the current mentioned year. But It should change only the first item of the embedded array.
For example,
If Year is 2020 and details.end_date is prior to 01-July-2020, then change the year to 2019
If Year is 2020 and details.end_date is after 01-July-2020, then do not change the year
If Year is 2021 and details.end_date is prior to 01-July-2021, then change the year to 2020
If Year is 2021 and details.end_date is after 01-July-2021, then do not change the year
You can do the followings in an aggregation pipeline:
isolate the first elem of the reports array for easier processing using $arrayElemAt
use $cond to derive the year value with $month. Use $toInt and $toString for type conversion
use $concatArrays to append back the processed first Elem back to the reports array. Keep only the "tail" (i.e. without the first elem) using $slice
$merge to update the result back to the collection
db.collection.aggregate([
{
"$addFields": {
"firstElem": {
"$arrayElemAt": [
"$reports",
0
]
}
}
},
{
"$addFields": {
"firstElem.year": {
"$cond": {
"if": {
$lt: [
{
"$month": "$firstElem.end_date"
},
7
]
},
"then": {
"$toString": {
"$subtract": [
{
"$toInt": "$firstElem.year"
},
1
]
}
},
"else": "$firstElem.year"
}
}
}
},
{
"$addFields": {
"reports": {
"$concatArrays": [
[
"$firstElem"
],
{
"$slice": [
"$reports",
1,
{
"$subtract": [
{
"$size": "$reports"
},
1
]
}
]
}
]
}
}
},
{
"$project": {
firstElem: false
}
},
{
"$merge": {
"into": "collection",
"on": "_id",
"whenMatched": "replace"
}
}
])
Here is the Mongo playground for your reference.

JSON inline Date converting

I have D3 data graphic like here: https://www.metricsgraphicsjs.org/examples.htm#lines with example code
MG.data_graphic({
title: "Singleton",
description: "Handling a solitary data point.",
data: [{'date': new Date('2015-03-05T21:00:00Z'), 'value': 12000}],
width: 600,
height: 200,
right: 40,
target: '#singleton'
});
and difference is that I store data into database. But I have problem with displaying dates. My JSON data (pulled from MySQL) looks like this:
var data1 = [
[
{
"date": "2018-04-28T13:42:55Z",
"value": 20.8,
"added": "2018-04-28T13:42:55Z"
}
],
[
{
"date": "2018-04-28T13:42:55Z",
"value": 22.8,
"added": "2018-04-28T13:42:55Z"
}
],
[
{
"date": "2018-04-28T13:42:55Z",
"value": 23.9,
"added": "2018-04-28T13:42:55Z"
}
],
[
{
"date": "2018-04-28T13:42:55Z",
"value": 21.3,
"added": "2018-04-28T13:42:55Z"
}
]
];
When I try to display that data, it does not work, shows me errors like <path> attribute d: Expected number, "MNaN,86.570057581…"
But on other hand, if I set another variable for example
var data2 = [
[
{'date': new Date('2015-03-05T21:07:00Z'), 'value': 12000},
{'date': new Date('2015-03-05T21:10:00Z'), 'value': 5500},
{'date': new Date('2015-03-05T21:15:00Z'), 'value': 1000}
],
[
{'date': new Date('2015-03-05T21:05:00Z'), 'value': 18000},
{'date': new Date('2015-03-05T21:07:00Z'), 'value': 15000},
{'date': new Date('2015-03-05T21:15:00Z'), 'value': 7000}
]
];
This second example works and I was wondering what this "new Date()" does to data. How can I "fix" first data1 array and store it in another variable with correct dates? - I did try this type of date: Date Sat Apr 28 2018 13:56:55 GMT+0000 (UTC) but that did not help, did not work. Like this:
[
{'date': 'Apr 28 2018 13:57:55 GMT+0000 (UTC)', 'value': 18000},
{'date': 'Apr 28 2018 13:58:55 GMT+0000 (UTC)', 'value': 15000},
{'date': 'Apr 28 2018 13:59:55 GMT+0000 (UTC)', 'value': 7000}
]
This did not work. Does "new Date()" converts date and time to integer or string or something else?
I just want to find a way to change data1 variable and convert its dates to proper values to be able to show them on graph.
Thanks guys!

How can I group a field in subdocument in MongoDB

I have the following document:
{
"_id": "5a5cae1890254804e3a4655d",
"additional_ingredient": {
"concentration": 40.0,
"formulations": [
"5a1826c664f7fe04afbe9198"
]
}
}
Inside additional_ingredient there is an array field formulations. When I have an array field in the first level of a document I can use $unwind then $lookup to join an external document and the group by this field.
But I cannot do the same with a nested array. What is the best solution for that?
What I expect:
{
"_id": "5a5cae1890254804e3a4655d",
"additional_ingredient": {
"concentration": 40.0,
"formulations": [
{
"_id": "5a1826c664f7fe04afbe9198",
"date_created": "Fri, 24 Nov 2017 14:03:50 GMT",
"date_modified": "Fri, 24 Nov 2017 14:03:50 GMT",
}
]
}
}
I have a problem with grouping. The field name 'additional_ingredient.formulations' cannot contain '.'
This is a part of the query:
{"$unwind": "$additional_ingredient.formulations"},
self.__ADDITIONAL_INGREDIENT_FORMULATION_LOOKUP_QUERY,
{"$unwind": "$additional_ingredient.formulations"},
{"$group": {
"_id": "$_id",
"additional_ingredient.formulations": {"$push": "$additional_ingredient.formulations"},
}},

How to iterate over the following structure in Meteor Spacebars?

I'm trying to group a collection by date in Meteor using Underscore's _.groupBy function.
Here is a sample of the code that outputs:
"Mon Dec 07 2015 00:00:00 GMT+0000 (GMT)":[
{
"_id":"q9TMi9ZyoRjmddzfY",
"title":"New event",
"type":"collectif",
"product":"passeport",
"date":"2015-12-07T00:00:00.000Z",
"start":"2015-12-07T08:00:00.000Z",
"end":"2015-12-07T09:00:00.000Z",
"teachers":[
],
"clients":[
{
"clientId":"M4DDCGWGMzX7bJRHa",
"manual":"true"
}
],
"clientLimit":99
}
],
"Tue Dec 08 2015 00:00:00 GMT+0000 (GMT)":[
{
"_id":"Jbchuc58zWDyEqnQZ",
"title":"New event",
"type":"collectif",
"product":"passeport",
"date":"2015-12-08T00:00:00.000Z",
"start":"2015-12-08T08:30:00.000Z",
"end":"2015-12-08T09:30:00.000Z",
"teachers":[
],
"clients":[
],
"clientLimit":15
},
{
"_id":"EsqygwCCPucGhx9nP",
"title":"New event",
"type":"collectif",
"product":"passeport",
"date":"2015-12-08T00:00:00.000Z",
"start":"2015-12-08T09:30:00.000Z",
"end":"2015-12-08T10:30:00.000Z",
"teachers":[
"eLExMRh3TT5eYWpki",
"wxFjH39M9kuBTv4zN"
],
"clients":[
],
"clientLimit":10
}
]
}
The problem is, I'm not really sure how I can output these in the front-end. Do I have to somehow convert it to normal arrays or a cursor? Or is there perhaps another way of grouping a collection by date headings?
I'm looking to output something like the following:
Saturday 21st September
- Item 1
- Item 2
Tuesday 24th September
- Item 3
Thanks for any ideas.
This is how I would do it:
loops.html
{{#each arrayify data}}
{{name}} <br>
<ul>
{{#each value}}
<li>ClientLimit: {{this.clientLimit}}</li>
{{/each}}
</ul>
{{/each}}
loops.js
Template.registerHelper('arrayify',function(obj){
result = [];
for (var key in obj) result.push({name:key,value:obj[key]});
return result;
});
Template.hello.helpers({
data: function() {
return {
"Mon Dec 07 2015 00:00:00 GMT+0000 (GMT)":[
{
"_id":"q9TMi9ZyoRjmddzfY",
"title":"New event",
"type":"collectif",
"product":"passeport",
"date":"2015-12-07T00:00:00.000Z",
"start":"2015-12-07T08:00:00.000Z",
"end":"2015-12-07T09:00:00.000Z",
"teachers":[
],
"clients":[
{
"clientId":"M4DDCGWGMzX7bJRHa",
"manual":"true"
}
],
"clientLimit":99
}
],
"Tue Dec 08 2015 00:00:00 GMT+0000 (GMT)":[
{
"_id":"Jbchuc58zWDyEqnQZ",
"title":"New event",
"type":"collectif",
"product":"passeport",
"date":"2015-12-08T00:00:00.000Z",
"start":"2015-12-08T08:30:00.000Z",
"end":"2015-12-08T09:30:00.000Z",
"teachers":[
],
"clients":[
],
"clientLimit":15
},
{
"_id":"EsqygwCCPucGhx9nP",
"title":"New event",
"type":"collectif",
"product":"passeport",
"date":"2015-12-08T00:00:00.000Z",
"start":"2015-12-08T09:30:00.000Z",
"end":"2015-12-08T10:30:00.000Z",
"teachers":[
"eLExMRh3TT5eYWpki",
"wxFjH39M9kuBTv4zN"
],
"clients":[
],
"clientLimit":10
}
]
}
});

Resources