Grouping Mongo DB document with three nested arrays - 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

Related

How to Update Array dict Elements in mongodb based on another field

How can I update a value in a document based on applying functions to another field (which is in a different embedded document)?
With the sample data below, I want to
get the col field for the farm having id 12
multiply that by 0.025
add the current value of the statistic.crypt field
ensure the value is a double by converting it with $toDouble
store the result back into statistic.crypt
data:
{
"_id": {
"$oid": "6128c238c144326c57444227"
},
"statistic": {
"balance": 112570,
"diamond": 14,
"exp": 862.5,
"lvl": 76,
"mn_exp": 2.5,
"lvl_mn_exp": 15,
"coll_ms": 8047,
"all_exp": 67057.8,
"rating": 0,
"crypt": 0
},
"inventory": {
"farm": [{
"id": 12,
"col": 100,
"currency": "diamond",
"cost": 2,
"date": "2021-09-02 18:58:39"
}, {
"id": 14,
"col": 1,
"currency": "diamond",
"cost": 2,
"date": "2021-09-02 16:57:08"
}],
"items": []
},
...
}
My initial attempt is:
self.collection
.update_many({"inventory.farm.id": 12}, [{
"$set": {
"test": {
'$toDouble': {
"$sum": [
{'$multiply':["$inventory.farm.$[].col", 0.025]},
'$test'
]
}
} }
},])
This does not work as it applies to test rather than statistic.crypt, and I cannot figure out how to modify it to apply to statistic.crypt.
A field can be updated based on another in the following stages:
add a field containing the farm
set statistic.crypt to the result of the mathematical expression (applied to the newly embedded farm)
remove extra fields
In code:
self.collection.update_many({"inventory.farm.id": 12 }, [
{
$addFields: {
hh: {
$filter: {
input: "$inventory.farm",
as: "z",
cond: { $eq: ["$$z.id", 12] },
},
},
},
},
{
$set: {
"statistic.crypt": {
$toDouble: {
$sum: [
{
$multiply: [{ $first: "$hh.col" }, 0.025],
},
"statistic.crypt",
],
},
},
},
},
{
$project: {
id_pr: 1,
id_server: 1,
role: 1,
warns: 1,
id_clan: 1,
statistic: 1,
design: 1,
date: 1,
inventory: 1,
voice: 1,
},
},)

Update the existing collection from Aggregate Pipeline

I am very new to MongoDB and have been trying to create a new field within my collection that is calculated using existing data.
Is there a way to add the field myRating to the movies collection?
Here is what I came up with.
db.movies.aggregate([
{$unwind: "$genres"},
{$project:{_id:0, title:1, genres:1,
durationScore: {$cond: {if: {$gte: ["$runtime", 90]}, then: 10, else: 5}},
yearScore: {$cond: {if: {$gte: ["$year", 1990]}, then: 10, else: 5}},
genreScore: {$switch:{branches:[
{
case: {$eq :["$genres", "Action"]}, "then": 30 ,
},
{
case: {$eq :["$genres", "Western"]}, "then": 20 ,
},
{
case: {$eq :["$genres", "Comedy"]}, "then": 5 ,
},
{
case: {$eq :["$genres", "Drama"]}, "then": 15 ,
},
],
default: 10
}},
directorScore: {$switch:{branches:[
{
case: {$eq :["$director", "Quentin Tarantino"]}, "then": 20 ,
},
{
case: {$eq :["$director", "Martin Scorsese"]}, "then": 20 ,
},
],
default: 10
}}
}},
{$addFields: { myRating: { $sum: [ "$yearScore", "$durationScore", "$genreScore", "$directorScore" ]}}},
])
Sample of Data.
{
"_id": {
"$oid": "60502686eb0d3e3e849677ef"
},
"title": "Once Upon a Time in the West",
"year": 1968,
"rated": "PG-13",
"runtime": 175,
"countries": [
"Italy",
"USA",
"Spain"
],
"genres": [
"Western"
],
"director": "Sergio Leone",
"writers": [
"Sergio Donati",
"Sergio Leone",
"Dario Argento",
"Bernardo Bertolucci",
"Sergio Leone"
],
"actors": [
"Claudia Cardinale",
"Henry Fonda",
"Jason Robards",
"Charles Bronson"
],
"plot": "Epic story of a mysterious stranger with a harmonica who joins forces with a notorious desperado to protect a beautiful widow from a ruthless assassin working for the railroad.",
"poster": "http://ia.media-imdb.com/images/M/MV5BMTEyODQzNDkzNjVeQTJeQWpwZ15BbWU4MDgyODk1NDEx._V1_SX300.jpg",
"imdb": {
"id": "tt0064116",
"rating": 8.6,
"votes": 201283
},
"tomato": {
"meter": 98,
"image": "certified",
"rating": 9,
"reviews": 54,
"fresh": 53,
"consensus": "A landmark Sergio Leone spaghetti western masterpiece featuring a classic Morricone score.",
"userMeter": 95,
"userRating": 4.3,
"userReviews": 64006
},
"metacritic": 80,
"awards": {
"wins": 4,
"nominations": 5,
"text": "4 wins & 5 nominations."
},
"type": "movie"
}
I would suggest you keep _id field in $project stage.
Without considering performance, simply iterating through the aggregate result and $set myRating field through updateOne using the _id field.
db.movies.aggregate([
...
{$project:{_id:1, title:1, genres:1,
...
]).forEach(result = > {
db.movies.updateOne(
{_id : result._id},
{$set : {myRating : {result.myRating}}
})
})
Starting in MongoDB 4.2, you can use the aggregation pipeline for update operations. Try this query:
db.movies.updateOne(
{ "_id": ObjectId("60502686eb0d3e3e849677ef") },
[
{
$set: {
myRating: {
$let: {
vars: {
durationScore: { $cond: { if: { $gte: ["$runtime", 90] }, then: 10, else: 5 } },
yearScore: { $cond: { if: { $gte: ["$year", 1990] }, then: 10, else: 5 } },
genreScore: {
$switch: {
branches: [
{ case: { $in: ["Action", "$genres"] }, "then": 30 },
{ case: { $in: ["Western", "$genres"] }, "then": 20 },
{ case: { $in: ["Comedy", "$genres"] }, "then": 5 },
{ case: { $in: ["Drama", "$genres"] }, "then": 15 }
],
default: 10
}
},
directorScore: {
$switch: {
branches: [
{ case: { $eq: ["$director", "Quentin Tarantino"] }, "then": 20 },
{ case: { $eq: ["$director", "Martin Scorsese"] }, "then": 20 }
],
default: 10
}
}
},
in: { $sum: ["$$yearScore", "$$durationScore", "$$genreScore", "$$directorScore"] }
}
}
}
}
]
);

MongoDB remove elements depending on element before (Iterating)

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

jsonb query in postgres

I've a table in postgres named: op_user_event_data, which has a column named data, where I store a jsonb, and what I have at the moment is a json like this:
{
"aisles": [],
"taskGroups": [
{
"index": 0,
"tasks": [
{
"index": 1,
"mandatory": false,
"name": "Dados de Linear",
"structuresType": null,
"lines": [
{
"sku": {
"skuId": 1,
"skuName": "Limiano Bola",
"marketId": [
1,
3,
10,
17
],
"productId": 15,
"brandId": [
38,
44
]
},
"taskLineFields": [
{
"tcv": {
"value": "2126474"
},
"columnType": "skuLocalCode",
"columnId": 99
},
{
"tcv": {
"value": null
},
"columnType": "face",
"columnId": 29
},
]
},
{
"sku": {
"skuId": 3,
"skuName": "Limiano Bolinha",
"marketId": [
1,
3,
10,
17
],
"productId": 15,
"brandId": [
38,
44
]
},
"taskLineFields": [
{
"tcv": {
"value": "2545842"
},
"columnType": "skuLocalCode",
"columnId": 99
},
{
"tcv": {
"value": null
},
"columnType": "face",
"columnId": 29
},
]
},
{
"sku": {
"skuId": 5,
"skuName": "Limiano Bola 1/2",
"marketId": [
1,
3,
10,
17
],
"productId": 15,
"brandId": [
38,
44
]
},
"taskLineFields": [
{
"tcv": {
"value": "5127450"
},
"columnType": "skuLocalCode",
"columnId": 99
},
{
"tcv": {
"value": "5.89"
},
"columnType": "rsp",
"columnId": 33
}
]
}
Basically I've an object which has
Aisles [],
taskGroups,
id and name.
Inside the taskGroups as shown in the json, one of the atributes is tasks which is an array, that also have an array called lines which have an array of sku and tasklines.
Basically:
taskGroups -> tasks -> lines -> sku or taskLineFields.
I've tried different queries to get the sku but when I try to get anything further than 'lines' it just came as blank or in some other tries 'cannot call elements from scalar'
Can anyone help me with this issue? Note this is just a sample json.
Anyone knows how make this to work:
I Want all lines where lines->taskLineFields->columnType = 'offer'
All I can do is this, but throwing error on scalar:
SELECT lines->'sku' Produto, lines->'taskLineFields'->'tcv'->>'value' ValorOferta
FROM sd_bel.op_user_event_data,
jsonb_array_elements(data->'taskGroups') taskgroups,
jsonb_array_elements(taskgroups->'tasks') tasks,
jsonb_array_elements(tasks->'columns') columns,
jsonb_array_elements(tasks->'lines') lines
WHERE created_by = 'belteste'
AND lines->'taskLineFields'->>'columnType' = 'offer'
say your data is in some json_column in your table
with t as (
select json_column as xyz from table
),
tg as ( select json_array_elements(xyz->'taskGroups') taskgroups from t),
tsk as (select json_array_elements(taskgroups->'tasks') tasks from tg)
select json_array_elements(tasks->'lines') -> 'sku' as sku from tsk;

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