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

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,
},
},)

Related

Creating an object dynamically - React/Typescript

I am getting an object from the backend with the following structure:
"periods": [
{
"id": 12,
"schemes": [
{
"id": 123,
"parts": [
{
"id": 1234,
"facts": {
"id": 21,
"basis": {
"id": 12344,
"amount": 10
},
"factor": {
"id": 1234,
"prop": 12
}
},
"deduction": {
"id": 133
},
"date": "22-10-2022",
"years": 12
},
{... more parts}
]
},
{... more schemes}
]
},
{... more periods}
]
I am changing the data and sending it back via a PATCH. The payload expects the id, the period.id, and an object with the same structure as above but only with the changed fields.
One example is, that I am changing the field amount. Then I need to know which part, which scheme and which period I am changing the amount in. And the object I am sending back should look something like this (the date should always be included):
{
"id": 22,
"schemes": [
{
"id": 43,
"parts": [
{
"id": 32,
"facts": {
"id": 77,
"basis": {
"id": 232,
"amount": 134 // CHANGING THIS
}
},
"date": "22-10-2022",
},
{... more parts}
]
},
{... more schemes}
]
}
At the moment I am solving it by taking the whole object from the backend and filtering out the one with the id and date (using the lodash filter function) which matches the one that changed, then creating a new object with those and the changed fields. Here is a snippet from my function:
if (name === "basis") {
const [period]: Period[] = _.filter(periods, {
schemes: [
{
parts: [
{
facts: {
id: data?.facts?.id,
basis: {
id: data?.facts?.basis?.id,
},
},
date: data?.date,
},
],
},
],
});
request = {
id: period?.id,
schemes: [
{
id: 2,
parts: [
{
id: data?.id,
facts: {
id: data?.facts?.id,
basis: {
id: data?.facts?.basis?.id,
amount: data?.facts?.basis?.amount,
},
},
date: data?.date,
},
],
},
],
};
}
return request;
I have an array of the keys of the changed fields in the state. And also a part-object with all the data and not only the changed data. My question is how I can create an object with the structure as above but only with changed data dynamically and not manually like I am doing right now?

How to project only matched array item in mongodb aggregation?

I have a collection called shows, with documents as:
{
"url": "http://www.tvmaze.com/shows/167/24",
"name": "24",
"genres": [
"Drama",
"Action"
],
"runtime": 60
},
{
"url": "http://www.tvmaze.com/shows/4/arrow",
"name": "Arrow",
"genres": [
"Drama",
"Action",
"Science-Fiction"
],
"runtime": 60
}
I wanted to search shows with genre 'Action' and project the result array as
{
"url": "http://www.tvmaze.com/shows/167/24",
"name": "24",
"genres": [
"Action" // I want only the matched item in
//my result array
],
"runtime": 60
} , //same for the second doc as well
If I use
db.shows.find({genres:'Action'}, {'genres.$': 1});
It works but the same does not work in aggregate method with $project
Shows.aggregate([
{
$match: { 'genres': 'Action'}
},
{
$project: {
_id: 0,
url: 1,
name: 1,
runtime: 1,
'genres.$': 1
}
}
]);
this is the error I get on this aggregate query
Invalid $project :: caused by :: FieldPath field names may not start with '$'."
db.collection.aggregate([
{
$match: {
"genres": {
$regex: "/^action/",
$options: "im"
}
}
},
{
$project: {
_id: 0,
url: 1,
name: 1,
runtime: 1,
genres: {
$filter: {
input: "$genres",
as: "genre",
cond: {
$regexMatch: {
input: "$$genre",
regex: "/^action/",
options: "im"
}
}
}
}
}
}
])
Here is how I solved it, Thanks #turivishal for the help

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"] }
}
}
}
}
]
);

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;

Combining Column charts and line charts with the same same data in the same container(Highcharts)

I want to build a combination chart with a column chart with multiple series and a line chart. Problem is that I am getting High charts data from nested JSON response. For that I initialized array and that array is giving in series in plotoptions highcharts as you can see in the below code.
My code is like this:
var crime_data=[];
for(var i=0;i<result.themes.length;i++){
var crime={};
var test2 = result.themes[i];
var test = test2[Object.keys(test2)];
crime.name = Object.keys(result.themes[i]);
crime.data = [];
for(var k=0;k<test.yearTheme.length;k++){
var test3=test.yearTheme[k];
var test5=test3.individualValueVariable;
for(var j=0;j<test5.length;j++){
crime.data.push(test5[j].count);
};
};
crime_data.push(crime);
};
var crimeChart = new Highcharts.Chart({
chart: {
renderTo: 'container1',
type:'column'
},
title: {
text: 'Crime'
},
xAxis: {
categories: month,
crosshair: true
},
yAxis: {
min: 0,
title: {
text: 'Count'
}
},
credits: {
enabled: false
},
tooltip: {
shared: true,
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0,
depth: 25,
allowPointSelect: true,
cursor: 'pointer',
point: {
},
}
},
series: crime_data
});
This is Column chart I am getting when i write chart type column.
This is my Line Chart I am getting when i changed type column to spline in chart in highcharts.
And this is my JSON data(Highcharts data):
{
"boundaries": {
"boundary": [
{
"boundaryId": "55083021003",
"boundaryType": "USA_CITY",
"boundaryRef": "C1"
}
]
},
"themes": [
{
"AssaultCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [
{
"name": "2013 Assault Crime",
"description": "Assault Crime for 2013",
"count": 18901
},
{
"name": "2014 Assault Crime",
"description": "Assault Crime for 2014",
"count": 17707
}
]
}
},
{
"BurglaryCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [
{
"name": "2013 Burglary Crime",
"description": "Burglary Crime for 2013",
"count": 17743
},
{
"name": "2014 Burglary Crime",
"description": "Burglary Crime for 2014",
"count": 14242
}
]
}
}
]
}
I want to combine both of them in the same container with same data.The problem is in how to tell highcharts multiple series should be represented with line and with column type with same data.For this when i write series:[{ data: crime_data ,type: spline }] instead of series:crime_data In that case I am not getting Highcharts data. Can anyone Please help me how should i do this.Please suggest me.
Pass your data, like below format. add type of chart in each data series;
Here i replaced type value but with same data.
[{
type: 'line',
name: 'AssaultCrimeTheme',
data: [3, 2, 1, 3, 4]
}, {
type: 'line',
name: 'BurglaryCrimeTheme',
data: [2, 3, 5, 7, 6]
}, {
type: 'column',
name: 'AssaultCrimeTheme',
data: [3, 2, 1, 3, 4]
}, {
type: 'column',
name: 'BurglaryCrimeTheme',
data: [2, 3, 5, 7, 6]
},]
Here is fiddle for more details.
Here is a complete example using your data.
const json = {
"boundaries": {
"boundary": [{
"boundaryId": "55083021003",
"boundaryType": "USA_CITY",
"boundaryRef": "C1"
}]
},
"themes": [{
"AssaultCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [{
"name": "2013 Assault Crime",
"description": "Assault Crime for 2013",
"count": 18901
}, {
"name": "2014 Assault Crime",
"description": "Assault Crime for 2014",
"count": 17707
}]
}
}, {
"BurglaryCrimeTheme": {
"boundaryRef": "C1",
"individualValueVariable": [{
"name": "2013 Burglary Crime",
"description": "Burglary Crime for 2013",
"count": 17743
}, {
"name": "2014 Burglary Crime",
"description": "Burglary Crime for 2014",
"count": 14242
}]
}
}]
}
// Create categories object in order filter duplicates
const cats = {}
const series = json.themes.map((o) => {
const key = Object.keys(o)[0]
return {
name: key,
data: o[key].individualValueVariable.map((o) => {
cats[o.name] = 1
return { category: o.name, y: o.count }
})
}
})
// Convert categories object to array
const categories = Object.keys(cats)
// Chart options
const options = {
chart: {type: 'column'},
xAxis: {categories: categories},
series: series
}
// Create chart
const chart = Highcharts.chart('container', options)
console.log(series, categories)
Live example: https://jsfiddle.net/Lo323gq3/
Output below:

Resources