I have an array of objects that I'd like to refactor (make a different structure)..
Existing Array:
[ 0: { category: 100 },
1: { category: 101 },
2: { category: 102 },
3: { tag: 200 },
4: { tag: 201 },
5: { tag: 202 },
6: { year: 300 },
7: { year: 301 },
8: { year: 302 }
]
Want Array:
[ 0: { category: [ 100, 101, 102 ] },
1: { tag: [ 200, 201, 202 ] },
2: { year: [ 300, 301, 302 ] }
]
Any help is greatly appreciated. ES6 syntax welcome.
Use Array.prototype.reduce
let input = [{ category: 100 },
{ category: 101 },
{ category: 102 },
{ tag: 200 },
{ tag: 201 },
{ tag: 202 },
{ year: 300 },
{ year: 301 },
{ year: 302 }
];
let output = input.reduce((acc, curr) => {
let key = Object.keys(curr)[0];
acc[key] = acc[key] ? [curr[key], ...acc[key]] : [curr[key]];
return acc;
}, {});
console.log(output)
Related
{
metadata:{
eventcode:100
}
power:on // this can be either on or off
time:1667984669//unix timestamp
}
My document looks something like this the power can be on or it can be off, given to and from time I have to calculate how many hours it was on, I am having trouble because in 1 day it can even have 100 on and 100 off values which means 200 documents, so how to calculate the number of operational hour(i.e time that the system was on) in mongodb query?
I would use this one:
db.collection.aggregate([
{ $set: { time: { $toDate: { $multiply: ["$time", 1000] } } } },
{
$setWindowFields: {
partitionBy: "$metadata.eventcode",
sortBy: { time: 1 },
output: {
off: {
$last: "$time",
window: { documents: ["current", 1] }
}
}
}
},
{ $match: { power: "on" } },
{
$set: {
operationalHours: {
$dateDiff: {
startDate: "$time",
endDate: "$off",
unit: "hour"
}
}
}
}
])
You can customized it, let's assume you have glitch input data like this:
[
{ id: 1, power: 'on', time: 1667984669 },
{ id: 1, power: 'on', time: 1667984669 }, // accidentally inserted twice
{ id: 2, power: 'off', time: 1667984669 + 1000 },
{ id: 3, power: 'off', time: 1667984669 + 2000 },
{ id: 4, power: 'off', time: 1667984669 + 3000 }
]
then use
{
$setWindowFields: {
partitionBy: "$eventcode",
sortBy: { time: 1 },
output: {
off: {
$min: { $cond: [{ $eq: ["$power", "off"] }, "$time", null] },
window: { documents: [1, "unbounded"] }
}
}
}
}
it will take time#id: 2 - time#id: 1
Note, $dateDiff does not return fractional values. You may use unit: "minute" and divide result by 60.
In case someone in the future faces the same problem:
t.aggregate([
{
$match: {
"metadata.eventCode": "100",
actualtime:{$gte: 1662143400000,$lte:1662229799999}
},
},
{
$sort: { actualtime: 1 },
},
{
$facet: {
on: [
{
$match: {
"metadata.type": "on",
},
},
],
off: [
{
$match: {
"metadata.type": "off",
},
},
],
},
},
{
$project: {
operationalHours: {
$sum: {
$map: {
input: {
$range: [
0,
{
$size: "$on",
},
],
},
as:"el",
in: {
$subtract: [
{
$arrayElemAt: ["$off.actualtime", "$$el"],
},
{
$arrayElemAt: ["$on.actualtime", "$$el"],
},
],
},
},
},
},
},
},
]);
If we imagine this kind of document structure :
[
{
id: 1,
name: "",
values : {
a: 24,
b: 42
}
},
{
id: 2,
name: "",
values : {
a: 43,
b: 53
}
},
{
id: 3,
name: "",
values : {
a: 33,
b: 25
}
},
{
id: 4,
name: "",
values : {
a: 89,
b: 2
}
}
// ...
]
Is it possible to get one or more lists of documents where, for example, the sum of the $.values.a equals 100 and the sum of the $.values.b equals 120? Or if not is it possible to sort the bests fits with a kind of threshold?
For example, the best output can be something like that :
[
{
id: 1,
name: "",
values : {
a: 24,
b: 42
}
},
{
id: 2,
name: "",
values : {
a: 43,
b: 53
}
},
{
id: 3,
name: "",
values : {
a: 33,
b: 25
}
}
]
There is no any native implementation...
But, You can have desired results if your data meets some requirements:
You collection has no too much data (this solution scales badly)
Your id field is unique
Your collection has index for id field
Explanation
We sort by id
With $lookup with the same collection (it's important ´id´ to be indexed) and pick next 10 documents for the current document L i=(Doc i+1 ... Doc i+11)
With $reduce, we count from i ... i+n untill a > 100 and b > 120
With $facet, we separate lists which meets exactly a=100, b=120 results (equals) and threshold (+- 10 for values.a and values.b)
Last steps, if any equals exists, we ignore threshold. Otherwise, we take threshold.
db.collection.aggregate([
{
$sort: {
id: 1
}
},
{
$lookup: {
from: "collection",
let: {
id: "$id"
},
pipeline: [
{
$sort: {
id: 1
}
},
{
$match: {
$expr: {
$gt: [
"$id",
"$$id"
]
}
}
},
{
$limit: 10
}
],
as: "bucket"
}
},
{
$replaceRoot: {
newRoot: {
$reduce: {
input: "$bucket",
initialValue: {
a: "$values.a",
b: "$values.b",
data: [
{
_id: "$_id",
id: "$id",
name: "$name",
values: "$values"
}
]
},
in: {
a: {
$add: [
"$$value.a",
{
$cond: [
{
$and: [
{
$lt: [
"$$value.a",
100
]
},
{
$lt: [
"$$value.b",
120
]
}
]
},
"$$this.values.a",
0
]
}
]
},
b: {
$add: [
"$$value.b",
{
$cond: [
{
$and: [
{
$lt: [
"$$value.a",
100
]
},
{
$lt: [
"$$value.b",
120
]
}
]
},
"$$this.values.b",
0
]
}
]
},
data: {
$concatArrays: [
"$$value.data",
{
$cond: [
{
$and: [
{
$lt: [
"$$value.a",
100
]
},
{
$lt: [
"$$value.b",
120
]
}
]
},
[
"$$this"
],
[]
]
}
]
}
}
}
}
}
},
{
$facet: {
equals: [
{
$match: {
a: 100,
b: 120
}
}
],
threshold: [
{
$match: {
a: {
$gte: 90,
$lt: 110
},
b: {
$gte: 110,
$lt: 130
}
}
}
]
}
},
{
$project: {
result: {
$cond: [
{
$gt: [
{
$size: "$equals"
},
0
]
},
"$equals",
"$threshold"
]
}
}
},
{
$unwind: "$result"
}
])
MongoPlayground
My dataset :
{
"codepostal": 84000,
"siren": 520010234,
"type": "home"
},
{
"codepostal": 84000,
"siren": 0,
"type": "home"
},
{
"codepostal": 84000,
"siren": 450123003,
"type": "appt"
} ...
My pipeline (total is an integer) :
var pipeline = [
{
$match: { codepostal: 84000 }
},
{
$group: {
_id: { type: "$type" },
count: { $sum: 1 }
}
},
{
$project: {
percentage: { $multiply: ["$count", 100 / total] }
}
},
{
$sort: { _id: 1 }
}
];
Results :
[ { _id: { type: 'appt' }, percentage: 66 },
{ _id: { type: 'home' }, percentage: 34 } ]
Expected results is to count when "siren" is set to 0 or another number.
Count siren=0 => part
Count siren!=0 => pro
[ { _id: { type: 'appt' }, totalPercent: 66, proPercent: 20, partPercent: 80},
{ _id: { type: 'home' }, totalPercent: 34, proPercent: 45, partPercent: 55 } ]
Thanks a lot for your help !!
You can use $cond to get 0 or 1 for pro/part documents depending o value of siren field. Then it's easy to calculate totals for each type of document:
[
{
$match: { codepostal: 84000 }
},
{
$group: {
_id: { type: "$type" },
count: { $sum: 1 },
countPro: { $sum: {$cond: [{$eq:["$siren",0]}, 0, 1]} },
countPart: {$sum: {$cond: [{$eq:["$siren",0]}, 1, 0]} }
}
},
{
$project: {
totalPercent: { $multiply: ["$count", 100 / total] },
proPercent: { $multiply: ["$countPro", {$divide: [100, "$count"]}] },
partPercent: { $multiply: ["$countPart", {$divide: [100, "$count"]}] }
}
},
{
$sort: { _id: 1 }
}
]
Note that I used $divide to calculate pro/part percentage relative to the count of document within type group.
For your sample documents (total = 3) output will be:
[
{
"_id" : { "type" : "appt" },
"totalPercent" : 33.3333333333333,
"proPercent" : 100,
"partPercent" : 0
},
{
"_id" : { "type" : "home" },
"totalPercent" : 66.6666666666667,
"proPercent" : 50,
"partPercent" : 50
}
]
I have the following set of objects:
[
{
id: 1,
clientId: 1,
cost: 200
},
{
id: 1,
clientId: 2,
cost: 500
},
{
id: 1,
clientId: 2,
cost: 800
},
{
id: 2,
clientId: 1,
cost: 600
},
{
id: 2,
clientId: 2,
cost: 100
}
]
And I made a group of that with:
db.collection.aggregate(
{
'$group': {
'_id': '$id',
'clients': {
'$addToSet': {
'id': '$clientId',
'cost': '$cost'
}
}
}
}
)
So I obteined the following:
[
{
'_id': 1,
'clients': [
{
id: 1,
cost: 200
},
{
id: 2,
cost: 500
},
{
id: 2,
cost: 800
}
],
'_id': 2,
'clients': [
{
id: 1,
cost: 600
},
{
id: 2,
cost: 100
}
]
}
]
As you can see in the array of clients of the first value, I have 2 repeated and what I want is to have 1 with the cost added. So instead of have:
'clients': [
{
id: 1,
cost: 200
},
{
id: 2,
cost: 500
},
{
id: 2,
cost: 800
}
]
I need:
'clients': [
{
id: 1,
cost: 200
},
{
id: 2,
cost: 1300
}
]
So my question is: how can I do that? Because $addToSet nor $push allow $sum.
You can use aggregation operators to get expected output like following:
db.collection.aggregate({
"$group": {
"_id": {
"mainId": "$id",
"client": "$clientId"
},
"cost": {
"$sum": "$cost"
}
}
}, {
"$project": {
"mainId": "$_id.mainId",
"clients": {
"clientId": "$_id.client",
"cost": "$cost"
},
"_id": 0
}
}, {
"$group": {
"_id": "$mainId",
"clients": {
"$push": "$clients"
}
}
})
I have an "orders" collection which is like this:
{ typeID: 1, buyOrder: true, price: 100 },
{ typeID: 1, buyOrder: false, price: 120 },
{ typeID: 1, buyOrder: false, price: 130 },
{ typeID: 1, buyOrder: false, price: 250 },
{ typeID: 2, buyOrder: true, price: 500 },
{ typeID: 2, buyOrder: false, price: 610 },
{ typeID: 2, buyOrder: false, price: 690 },
{ typeID: 2, buyOrder: false, price: 590 }
and I want to aggregate this collection and find the best buy/sell price for each typeid.
The result should be:
{ typeID: 1, bestBuy: 100, bestSell: 120 }
{ typeID: 2, bestBuy: 500, bestSell: 610 }
Define bestBuy / bestSell
bestBuy = (buyOrder = true && max price)
bestSell = (buyOrder = false && min price)
This is what I have so far but I know that its wrong. Any ideas ?
db.orders.aggregate([
{ $sort : { typeID : 1 }},
{ $group:
{ _id: { typeID : "$typeID", buyOrder : "$buyOrder"},
price: { $max: "$price" },
}
},
{ $project:
{ _id: 0,
typeID: "$_id.typeID",
price: "$price",
buyOrder: "$_id.buyOrder",
}
}
])
Thanks for your time.
You may not yet be aware of the $cond operator which works as a ternary condition. So basically if a condition given a as a first argument is true then use the value in the next argument. If the condition evaluates to false then use the value in the last condition in the operator.
This turns out to be perfect as you already have an indicator of true or false to determine the field
db.orders.aggregate([
{ "$project": {
"typeID": 1,
"bestBuy": { "$cond": [
"$buyOrder",
"$price",
null
]},
"bestSell": { "$cond": [
"$buyOrder",
null,
"$price"
]}
}},
{ "$group": {
"_id": "$typeID",
"bestBuy": { "$max": "$bestBuy" },
"bestSell": { "$min": "$bestSell" }
}},
{ "$sort": { "_id": 1 } }
])
So the use of $max and $min here can negate the null values in the results where the condition was not met.
Maybe with a mapreduce you can achieve this with something like that :
var mapFunction1 = function() {
emit(this.typeID , this.buyOrder, this.price);
};
var reduceFunction1 = function(key, values) {
reducedValue = { bestBuy: 0, bestSell: 0 };
for (var idx = 0; idx < values.length; idx++) {
if(values[idx].buyOrder && reducedValue.bestBuy < values[idx].price) {
reducedValue.bestBuy = values[idx].price
}
if(!values[idx].buyOrder && reducedValue.bestSell > values[idx].price) {
reducedValue.bestSell = values[idx].price
}
}
return reducedValue;
};
db.orders.mapReduce(
mapFunction1,
reduceFunction1,
{ out: "your_result" }
)
Hope it helped