How to add the index number from the array in MongoDB - arrays

Explanation: How I can add the index of the array. please check the expected output, I have that array 10000+ elemets
[
{
"data": [
[
1000
],
[
20
],
[
300
],
[
40
]
]
}
]
The expected output will be with the index number.
{
"data": [{
"value": 1000,
"Index": 1
},
{
"value": 20,
"Index": 2
},
{
"value": 300,
"Index": 3
},
{
"value": 40,
"Index": 4
}
]
}

Would be this one:
db.collection.aggregate([
{
$set: {
data: {
$map: {
input: { $range: [0, { $size: "$data" }] },
as: "idx",
in: {
index: { $add: ["$$idx", 1] },
value: { $first: { $arrayElemAt: ["$data", "$$idx"] } }
}
}
}
}
}
])
Mongo Playground

Related

group base on array element mongoDB

Explanation: how we can do the group based on array elements, I want to do a group based on 0 element of both arrays.
{
"st": [
[
"2011-01-04T12:18:41Z",
0
],
[
"2011-01-04T15:00:00Z",
0
]
],
"en": [
[
"2011-01-04T14:59:50Z",
1
],
[
"2011-01-04T15:05:00Z",
4
]
]
}
The expected output document looks like. for example
[
{
"st": "2011-01-04T12:18:41Z",
"en": "2011-01-04T14:59:50Z",
"st_val": 0,
"en_val": 1,
"total_index_count": 2
},
{
"st": "2011-01-04T15:00:00Z",
"en": "2011-01-04T15:05:00Z",
"st_val": 0,
"en_val": 4,
"total_index_count": 2
}
]
You can do this in several different ways, here is an approach utilizing the $map and $arrayElemAt operators.
db.collection.aggregate([
{
"$project": {
elements: {
$map: {
input: {
$range: [
0,
{
$size: "$st"
}
]
},
in: {
st_val: {
"$arrayElemAt": [
{
"$arrayElemAt": [
"$st",
"$$this"
]
},
0
]
},
st: {
"$arrayElemAt": [
{
"$arrayElemAt": [
"$st",
"$$this"
]
},
1
]
},
en_val: {
"$arrayElemAt": [
{
"$arrayElemAt": [
"$en",
"$$this"
]
},
0
]
},
en: {
"$arrayElemAt": [
{
"$arrayElemAt": [
"$en",
"$$this"
]
},
1
]
},
}
}
}
}
},
{
$unwind: "$elements"
},
{
$replaceRoot: {
newRoot: "$elements"
}
}
])
Mongo Playground

remove 0 values if 0 is followed by >=1 from arrays in MongoDB

In data_start if 0 is followed by >=1 in the array then remove the 0 from the data_start , also remove the element from time_start array accordingly.
{
"data_start": [
1,
1,
0,
0,
1,
1,
0
],
"time_start": [
"2021-09-04T12:18:42Z",
"2021-09-04T14:59:50Z",
"2021-09-04T14:59:59Z",
"2021-09-04T15:00:00Z",
"2021-09-04T15:00:01Z",
"2021-09-04T15:05:00Z",
"2021-09-04T15:05:01Z"
]
}
Output Document will be :
{
"data_start": [
1,
1,
0,
1,
1,
0
],
"time_start": [
"2021-09-04T12:18:42Z",
"2021-09-04T14:59:50Z",
"2021-09-04T14:59:59Z",
"2021-09-04T15:00:01Z",
"2021-09-04T15:05:00Z",
"2021-09-04T15:05:01Z"
]
}
One approach is this one:
db.collection.aggregate([
{
$set: {
data: {
$let: {
vars: {
val: "$data_start",
prev: { $concatArrays: [[null], "$data_start"] } // shift values by one element
},
in: {
$map: {
input: { $range: [0, { $size: "$$val" }] },
as: "idx",
in: {
$cond: {
if: {
$and: [
{ $eq: [{ $arrayElemAt: ["$$val", "$$idx"] }, 0] }, // is 0
{ $eq: [{ $arrayElemAt: ["$$val", "$$idx"] }, { $arrayElemAt: ["$$prev", "$$idx"] }] } // is equal to previous
]
},
then: null,
else: {
data_start: { $arrayElemAt: ["$$val", "$$idx"] },
time_start: { $arrayElemAt: ["$time_start", "$$idx"] }
}
}
}
}
}
}
}
}
},
{
$set: {
data: {
$filter: {
input: "$data",
cond: "$$this"// -> removes null's from array
}
}
}
}
])
Maybe you have to fine-tune the condition and/or reverse the loop, i.e. input: { $range: [{ $size: "$$val" }, 0, -1] }
Mongo playground
This is a little ugly in Mongo but possible, the strategy for us would be to first iterate over the array and calculate the indexes of the items we need to remove, once we have those we will iterate over the two arrays again and update them and finally we remove the temporary calculated values, like so:
Note that to do this purely in Mongo you will need to be on v4.2+ as we need to use pipelined updates if you're using a lesser version then you will have execute this logic in code.
db.collection.updateOne(
{},
[
{
$set: {
tmpField: {
$reduce: {
input: {
$zip: {
"inputs": [
{
$reverseArray: "$data_start"
},
{
$range: [
{
$subtract: [
{
$size: "$data_start"
},
1
]
},
0,
-1
]
}
]
}
},
initialValue: {
prev: null,
indexesToRemove: []
},
in: {
prev: {
$arrayElemAt: [
"$$this",
0
]
},
indexesToRemove: {
$concatArrays: [
"$$value.indexesToRemove",
{
$cond: [
{
$and: [
{
$eq: [
"$$value.prev",
1
]
},
{
$eq: [
{
$arrayElemAt: [
"$$this",
0
]
},
0
]
}
]
},
[
{
$arrayElemAt: [
"$$this",
1
]
}
],
[]
]
}
]
}
}
}
}
}
},
{
$addFields: {
data_start: {
$reduce: {
input: {
$zip: {
"inputs": [
"$data_start",
{
$range: [
0,
{
$size: "$data_start"
}
]
}
]
}
},
initialValue: [],
in: {
$cond: [
{
$in: [
{
$arrayElemAt: [
"$$this",
1
]
},
"$tmpField.indexesToRemove"
]
},
"$$value",
{
$concatArrays: [
"$$value",
[
{
$arrayElemAt: [
"$$this",
0
]
}
]
]
}
]
}
}
},
time_start: {
$reduce: {
input: {
$zip: {
"inputs": [
"$time_start",
{
$range: [
0,
{
$size: "$time_start"
}
]
}
]
}
},
initialValue: [],
in: {
$cond: [
{
$in: [
{
$arrayElemAt: [
"$$this",
1
]
},
"$tmpField.indexesToRemove"
]
},
"$$value",
{
$concatArrays: [
"$$value",
[
{
$arrayElemAt: [
"$$this",
0
]
}
]
]
}
]
}
}
}
}
},
{
$unset: "tmpField"
}
])
Mongo Playground

merge multiple documents into one document with both document fields in MongoDB

I have multiple documents In MobgoDB How to do to the group on "abc" and "xyz" and get one document. Please see the "Output Document".
need to do the union with ( Document 1 U Document 2 ) and (Document 1 U Document 3) .
U= Union
Document 1
{
"data": {
"Inside_data": {
"project": {
"abc": {
"alpha": 4,
"beta" : 45
},
"xyz": {
"alpha": 214,
"beta" : 431
}
}
}
}
}
Document 2
"Deal": {
"name": "abc",
"url" : "www.abc.com,
"email": [ "abc#gmail.com"],
"total": 2
}
Document 3
"Deal": {
"name": "xyz",
"url" : "www.googl.com,
"email": [ "xyz#gmail.com"],
"total": 25
}
Expected Output.
{
{
"name": "abc",
"url" : "www.abc.com,
"email": "abc#gmail.com",
"total": 2,
"alpha": 4,
"beta" : 45
},
{
"name": "xyz",
"url" : "www.googl.com,
"email": "xyz#gmail.com",
"total": 25,
"alpha": 214,
"beta" : 431
}
}
db.collection.aggregate([
{
$match: {
Deal: {
$exists: true
}
}
},
{
$lookup: {
from: "collection",
let: {
name: "$Deal.name"
},
pipeline: [
{
$match: {
data: {
$exists: true
}
}
},
{
$project: {
data: {
$reduce: {
input: {
$objectToArray: "$data.Inside_data.project"
},
initialValue: {},
in: {
$cond: [
{
$eq: [
"$$this.k",
"$$name"
]
},
"$$this.v",
"$$value"
]
}
}
}
}
},
{
$project: {
_id: 0,
alpha: "$data.alpha",
beta: "$data.beta"
}
}
],
as: "Deal.data"
}
},
{
$unwind: "$Deal.data"
}
])
Answer by #turivishal

Multiply Varying number of Values Inside array: Mongodb

I have arranged my data so that the documents belonging to the same customer id are aggregated into a single collection. The data format is as follows.
{
"items": [
{
"stock_code": [
"22617",
"22768",
"20749"
],
"description": [
"DESIGN",
"FAMILY PHOTO FRAME",
"ASSORTED CASES"
],
"quantity": [
18,
12,
84
],
"unit_price": [
4.95,
9.95,
6.35
]
}
],
"_id": 581485,
"customer_id": 17389,
"country": "United Kingdom"
}
I need to multiply the values of array quantity with corresponding unit_price and get a total for multiple documents in a new field. I have tried using the $reduce function and $map function to get the output but both of them result in "error"
Multiply only supports numeric types, and not arrays
Could you please suggest how should i go about accomplishing this.
Codes tried:
"$addFields": {"order_total" :
{
"$sum": {
"$map": {
"input": "$items",
"as": "items",
"in": { "$multiply": [
{ "$ifNull": [ "$$items.quantity", 0 ] },
{ "$ifNull": [ "$$items.unit_price", 0 ] }
]}
}
}
}
}
Second:
"order_total" : {
"$reduce" : {
"input" : "$items",
"initialValue" : Decimal128("0.00"),
"in": {
"$sum" : [
"$$value",
{"$multiply" : [ "$$this.quantity", "$$this.unit_price" ] }
]}
}
}
The expected result needs to add a new field of "total" by multiplying the corresponding entries of unit_price with quantity. The error message is that of multiply only supports numeric types and not arrays.
I would decompose the problem into solvable chunks beginning with the smallest unit and start with the two arrays quantity and unit_price.
So given just a document with the structure
{
"quantity": [ 18, 12, 84 ],
"unit_price": [ 4.95, 9.95, 6.35 ]
}
We can add another field with the totals for each element in both arrays i.e.
{
"quantity": [ 18, 12, 84 ],
"unit_price": [ 4.95, 9.95, 6.35 ],
"total": [ 89.1, 119.4, 533.4 ]
}
This field can be computed using $range within $map as
{
"total": {
"$map": {
"input": { "$range": [ 0, { "$size": "$quantity" } ] },
"as": "idx",
"in": {
"$let": {
"vars": {
"qty": { "$arrayElemAt": [ "$quantity", "$$idx" ] },
"price": {
"$ifNull": [
{ "$arrayElemAt": [ "$unit_price", "$$idx" ] },
0
]
}
},
"in": { "$multiply": [ "$$qty", "$$price" ] }
}
}
}
}
}
This then becomes basis for calculating the order_total field with two pipeline stages for clarity (although can be composed into a single pipeline with $reduce for brevity)
var totalMapExpression = {
"$map": {
"input": { "$range": [ 0, { "$size": "$$item.quantity" } ] },
"as": "idx",
"in": {
"$let": {
"vars": {
"qty": { "$arrayElemAt": [ "$$item.quantity", "$$idx" ] },
"price": {
"$ifNull": [
{ "$arrayElemAt": [ "$$item.unit_price", "$$idx" ] },
0
]
}
},
"in": { "$multiply": [ "$$qty", "$$price" ] }
}
}
}
};
db.collection.aggregate([
{ "$addFields": {
"items": {
"$map": {
"input": "$items",
"as": "item",
"in": {
"quantity": "$$item.quantity",
"unit_price": "$$item.unit_price",
"stock_code": "$$item.stock_code",
"description": "$$item.description",
"total": { "$sum": totalMapExpression }
}
}
}
} },
{ "$addFields": {
"order_total": { "$sum": "$items.total" }
} }
])
Try the below query:
db.collection.aggregate(
[{ $unwind: { path: "$items",} },
{ $unwind: { path: "$items.quantity",} },
{ $unwind: { path: "$items.unit_price",} },
{ $addFields: { 'total': {$multiply: ["$items.quantity", "$items.unit_price"] }} }])

Make the sum of booleans as integers with MongoDB

I'm trying to sum booleans (where true means 1 and false -1) in an array for each document in my collection and then sort it.
I'm using MongoDB aggregation pipeline with $addFields, $sum and $cond.
Here's the playground : https://play.db-ai.co/m/XQLKqbkkgAABTFVm
The pipeline :
[
{
"$addFields": {
"score": {
"$sum": {
"$cond": [
"$votes.value",
1,
-1
]
}
}
}
},
{
"$sort": {
"score": -1
}
}
]
The collection :
[
{
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": true
},
{
"value": false
}
]
},
{
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": false
},
{
"value": false
}
]
}
]
Actual results :
[
{
"_id": ObjectId("000000000000000000000000"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": true
},
{
"value": false
}
],
"score": 1
},
{
"_id": ObjectId("000000000000000000000001"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": false
},
{
"value": false
}
],
"score": 1
}
]
What I want :
[{
"_id": ObjectId("000000000000000000000000"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": true
},
{
"value": false
}
],
"score": 2
}, {
"_id": ObjectId("000000000000000000000001"),
"votes": [
{
"value": true
},
{
"value": true
},
{
"value": false
},
{
"value": false
}
],
"score": 0
}]
I got it to work by unwinding the array and then grouping by _id again.
See Playground: https://play.db-ai.co/m/XQMFlZAtYAABLHtL
[
{
"$unwind": {
"path": "$votes"
}
},
{
"$group": {
"_id": "$_id",
"votes": {
"$push": "$votes"
},
"score": {
"$sum": {
"$cond": [
"$votes.value",
1,
-1
]
}
}
}
},
{
"$sort": {
"score": -1
}
}
]
To solve my problem I used $map multiple times. The solution of #Plancke is working but I had issues using a $match afterwards (it was always giving no results).
[
{
$addFields: {
scoresInBoolean: {
$map: {
input: '$votes',
as: 'vote',
in: '$$vote.value',
},
},
},
}, {
$addFields: {
scoresInInteger: {
$map: {
input: '$scoresInBoolean',
as: 'scoreInBoolean',
in: {
$cond: [
'$$scoreInBoolean',
1,
-1,
],
},
},
},
},
}, {
$addFields: {
score: {
$sum: '$scoresInInteger',
},
},
}
]

Resources