How value become value as an key in MonogoDB array? - arrays

Explanation :
I want to convert the value as part of the key, in document 1 "xyz" replace with v and in document 2, "abc" replace with v.
Note: there are multiple documents, which I don't know what could be the document 3 "sig_nam" value.
Doc No. 1
"sig_nam": "xyz",
"cont_name": "1",
"t_v": [
{
"t": "2020-11-20T03:00",
"v": 0
},
{
"t": "2020-10-25T03:00",
"v": 0
}
]
}
Doc No. 2
{
"sig_nam": "abc",
"cont_name": "1",
"t_v": [
{
"t": "2020-11-20T03:00",
"v": 0
},
{
"t": "2020-10-25T03:00",
"v": 0
}
]
}
Expected Output Doc No. 1
"sig_nam": "xyz",
"cont_name": "1",
"t_v": [
{
"t": "2020-11-20T03:00",
"xyz": 0
},
{
"t": "2020-10-25T03:00",
"xyz": 0
}
]
}
Expected Output Doc No. 2
{
"sig_nam": "abc",
"cont_name": "1",
"t_v": [
{
"t": "2020-11-20T03:00",
"abc": 0
},
{
"t": "2020-10-25T03:00",
"abc": 0
}
]
}

$map to iterate loop of t_v array
$arrayToObject, set k as sig_nam and v as count and same for t, wrap in to an array bracket, it will convert in to object
db.collection.aggregate([
{
$addFields: {
t_v: {
$map: {
input: "$t_v",
in: {
$arrayToObject: [[
{ k: "t", v: "$$this.t" },
{ k: "$sig_nam", v: "$$this.v" }
]]
}
}
}
}
}
])
Playground

Related

How to filter a nested array of dictionaries with multiple conditions from another array in Swift

sample json data is this:
{
"variations": [
{
"variation_id": 391,
"name": "Fruit Shake - Chocolate, S",
"price": 10,
"attribute": [
{
"attribute_key": "pa_flavor",
"name": "Flavor",
"option": "Chocolate"
},
{
"attribute_key": "pa_size",
"name": "Size",
"option": "S"
}
]
},
{
"variation_id": 385,
"name": "Fruit Shake - Banana, L",
"price": 18,
"attribute": [
{
"attribute_key": "pa_flavor",
"name": "Flavor",
"option": "Banana"
},
{
"attribute_key": "pa_size",
"name": "Size",
"option": "L"
}
]
},
{
"variation_id": 386,
"name": "Fruit Shake - Banana, M",
"price": 15,
"attribute": [
{
"attribute_key": "pa_flavor",
"name": "Flavor",
"option": "Banana"
},
{
"attribute_key": "pa_size",
"name": "Size",
"option": "M"
}
]
}
]
}
my problem is, getting the variation_id where 2 or more attributes matches the array of string.
for example, chosenProduct = ["Banana", "L"]
I tried filter and contains but theres no way to match the other item from chosenProduct.
If I added the next condition, it returns nil
You can try this:
let varID = product.variations?.filter { att in
var matchedAttributes = 0
for thisAttribute in att.attribute {
if chosenProduct.contains(where: {$0 == thisAttribute.option}) {
matchedAttributes += 1
}
}
if matchedAttributes >= 2 {
return true
}
return false
}
Let me know if there's any doubt.
You can try this :
var chosenProduct = ["Banana","L"]
var expectedIds : [Int] = [Int]()
datas.variations?.map{ val in
val.attribute.map({ val2 in
let filtered = val2.enumerated().filter({chosenProduct.contains($0.element.option!)})
if filtered.count == chosenProduct.count{
expectedIds.append(val.variation_id!)
}
})
}
print(expectedIds) // [385]
I put the id's in array because of if you want to change your chosenProcudt example "Banana" (count 1 ). This mapping must be return variation_id like [385,386]
You can a method to Variation to make things easier:
extension Variation {
func attributesMatching(options: [String]) -> [Attribute] {
attribute.filter { options.contains($0.option) }
}
}
Then, you can just write:
let filtered = product.variations.filter { $0.attributesMatching(options: chosenProductOptions).count >= 2 }
print(filtered.map { $0.variationID })

Mule 4 - How to combine arrays inside a nested array with the same id field into one

Suppose I have the following payload with nested array, how do I combine the array inside the nested array for the same externalId as well as some logic on certain field like
shipQty - this field will be sum or add up for records with the same externalId under fillingOrder
serialNumbers - all the records under serialNumbers will be display together if the externalId is same
Kindly refer below for the input and expected output
Json Payload Input
{
"Identifier": "9i098p-898j-67586k",
"transactionDate": "2019-09-08T10:01:00-04:00",
"order": [
{
"orderNumber": "123456789",
"CourierOrderId": "1300-88-2525",
"fillingOrder": [
{
"numberOfBoxes": 0,
"tracking": [
{
"carrier": "Orange",
"trackNum": "3333444",
"trackUrl": "https://www.orange.com/track/status",
"shipDate": "2019-09-08T10:01:00-04:00",
"SerialNumber": "00000123"
}
],
"row": [
{
"externalId": "1",
"unitNo": "OP04-123456-789",
"shipQty": 2,
"serialNumbers": [
{
"serialNumber": "USD333555",
"quantity": 1
},
{
"serialNumber": "USD235678",
"quantity": 1
}
]
}
]
},
{
"tracking": [
{
"carrier": "Apple",
"trackNum": "555666",
"trackUrl": "https://www.apple.com/track/status",
"shipDate": "2019-09-08T10:01:00-04:00",
"SerialNumber": "00000645"
}
],
"row": [
{
"externalId": "1",
"unitNo": "OP04-123456-789",
"shipQty": 3,
"serialNumbers": [
{
"serialNumber": "USD123456",
"quantity": 1
},
{
"serialNumber": "USD98765",
"quantity": 1
},
{
"serialNumber": "USD45689",
"quantity": 1
}
]
}
]
},
{
"tracking": [
{
"carrier": "banana",
"trackNum": "587390",
"trackUrl": "https://www.banana.com/track/status",
"shipDate": "2019-09-08T10:01:00-04:00",
"SerialNumber": "00000365"
}
],
"row": [
{
"externalId": "2",
"unitNo": "OP05-123456-111",
"shipQty": 2,
"serialNumbers": [
{
"serialNumber": "USD00045",
"quantity": 1
},
{
"serialNumber": "USD00046",
"quantity": 1
}
]
}
]
}
]
}
]
}
Expected Json Output
{
"row": [
{
"externalId": "1",
"unitNo": "OP04-123456-789",
"shipQty": 5, //the shipQty should be add up when the externalId is same
"serialNumbers": [ //the serialNumbers should display all the data inside the serialNumbers when the externalId is same
{
"serialNumber": "USD333555",
"quantity": 1
},
{
"serialNumber": "USD235678",
"quantity": 1
},
{
"serialNumber": "USD123456",
"quantity": 1
},
{
"serialNumber": "USD98765",
"quantity": 1
},
{
"serialNumber": "USD45689",
"quantity": 1
}
]
},
{
"externalId": "2",
"unitNo": "OP05-123456-111",
"shipQty": 2,
"serialNumbers": [
{
"serialNumber": "USD00045",
"quantity": 1
},
{
"serialNumber": "USD00046",
"quantity": 1
}
}
]
}
It looks like you only need the data of "row" inside the fillingOrder field of your payload. So first thing to simplicy the problem is to get all the rows as a single array. Once you have that them you just need to group that by external id and the problem will start to look smaller.
%dw 2.0
output application/json
//First get all rows since it looks like you only need them.
//If you find this confusing try to use flatten with some simpler payloads.
var allRows = flatten(flatten(payload.order.fillingOrder).row)
//Group them according to external id.
var groupedExtId = allRows groupBy $.externalId
---
{
row: groupedExtId pluck ((value, extId, index) -> do {
var sumShipQuant = sum(value.shipQty default [])
---
{
externalId: (extId), //the key after grouping is external id
unitNo: value.unitNo[0], //assuming it is same across diff external id
shipQty: sumShipQuant,
serialNumbers: flatten(value.serialNumbers) //Flatten because value is an array and it has multiple serielNumbers array
}
})
}
This should help. I took some inspiration from Harshank Bansal post
%dw 2.0
output application/json
var groupFlat = flatten(flatten (payload.order.fillingOrder).row) groupBy ($.externalId)
---
row: [groupFlat mapObject ((value, key, index) -> {
externalId: value.externalId[0],
unitNO: value.unitNo[0],
shipQty: sum(value.shipQty),
serialNumbers: flatten(value.serialNumbers)
})]
Try this:
%dw 2.0
output application/json
---
row:[ if (payload..order..row..externalId[0] == payload..order..row..externalId[1]) {
externalId : payload..order..row..externalId[0],
unitNo: payload..order..row..unitNo[0],
shipQty: payload..order..row..shipQty[0] + payload..order..row..shipQty[1],
serialNumbers: flatten (payload..order..row..serialNumbers)
}
else null]

Avoid empty array elements in mongo db

How to avoid empty array while filtering results while querying a collection in MongoDb
[
{
"_id": ObjectId("5d429786bd7b5f4ae4a64790"),
"extensions": {
"outcome": "success",
"docType": "ABC",
"Roll No": "1"
},
"data": [
{
"Page1": [
{
"heading": "LIST",
"content": [
{
"text": "<b>12345</b>"
},
],
}
],
"highlights": [
{
"name": "ABCD",
"text": "EFGH",
}
],
"marks": [
{
"revision": "revision 1",
"Score": [
{
"maths": "100",
"science": "40",
"history": "90"
},
{
"lab1": "25",
"lab2": "25"
}
],
"Result": "Pass"
},
{
"revision": "revision 1",
"Score": [
{
"maths": "100",
"science": "40"
},
{
"lab1": "25",
"lab2": "25"
}
],
"Result": "Pass"
}
]
}
]
}
]
I am looking for results that has only "history" marks in the score array.
I tried the following query (in mongo 3.6.10) but it returns empty score array as well the array that has history as well
db.getCollection('student_scores').find({
"data.marks.score.history": {
$not: {
$type: 10
},
$exists: true
}
},
{
"extensions.rollNo": 1,
"data.marks.score.history": 1
})
Desired output is
{
"extensions": {
"rollNo": "1"
},
"data": [
{
"marks": [
{
"Score": [
{
"history": "90"
}
]
}
]
}
]
}
I used something like the following;
db.getCollection('student_scores').aggregate([
{
$unwind: "$data"
},
{
$unwind: "$data.marks"
},
{
$unwind: "$data.marks.Score"
},
{
$match: {
"data.marks.Score.history": {
$exists: true,
$not: {
$type: 10
}
}
}
},
{
$project: {
"extensions.Roll No": 1,
"data.marks.Score.history": 1
}
},
{
$group: {
_id: "$extensions.Roll No",
history_grades: {
$push: "$data.marks.Score.history"
}
}
}
])
where I got the following result with your input (I think more readable than your expected output);
[
{
"_id": "1",
"history_grades": [
"90"
]
}
]
where _id represents "extensions.Roll No" value for any given data set.
What do you think?
check with a bigger input on mongoplayground
OK, so I still think the data design here with the Score array is a little off but here is solution that will ensure that a Score array contains only 1 entry and that entry is for a key of history. We use dotpath array diving as a trick to get to the value of history.
c = db.foo.aggregate([
{$unwind: "$data"}
,{$unwind: "$data.marks"}
,{$project: {
result: {$cond: [
{$and: [ // if
{$eq: [1, {$size: "$data.marks.Score"}]}, // Only 1 item...
// A little trick! $data.marks.Score.history will resolve to an *array*
// of the values associated with each object in $data.marks.Score (the parent
// array) having a key of history. BUT: As it resolves, if there is no
// field for that key, nothing is added to resolution vector -- not even a null.
// This means the resolved array could
// be **shorter** than the input. FOr example:
// > db.foo.insert({"x":[ {b:2}, {a:3,b:4}, {b:7}, {a:99} ]});
// WriteResult({ "nInserted" : 1 })
// > db.foo.aggregate([ {$project: {z: "$x.b", n: {$size: "$x.b"}} } ]);
// { "z" : [ 2, 4, 7 ], "n" : 3 }
// > db.foo.aggregate([ {$project: {z: "$x.a", n: {$size: "$x.a"}} } ]);
// { "z" : [ 3, 99 ], "n" : 2 }
//
// You must be careful about this.
// But we also know this resolved vector is of size 1 (see above) so we can go ahead and grab
// the 0th item and that becomes our output.
// Note that if we did not have the requirement of ONLY history, then we would not
// need the fancy $cond thing.
{$arrayElemAt: ["$data.marks.Score.history",0]}
]},
{$arrayElemAt: ["$data.marks.Score.history",0]}, // then (use value of history)
null ] } // else set null
,extensions: "$extensions" // just carry over extensions
}}
,{$match: {"result": {$ne: null} }} // only take good ones.

Calculate weight and skip a specific element Mongodb - Aggregation Framework

I have a collection with array elements containing A,B or C values. I have to calculate a weight of each element value.
The logic of this weight is sample :
We give 1.0 as weight of the last element (dfferente to C), and others 0
If the last element of the array is C, we give it to the previous last element (different to C).
if there is 1 element in the array (A,B or C) we give it 1 as a weight.
This is how my collection looking like :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C"
},
{
"value" : "A
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "A"
},
{
"value" : "A"
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"myArray" : [
{
"value" : "A"
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "C"
},
{
"value" : "C"
}
]
}
I did some aggregation to make it happen but I can here just to affect 1 to the last (different to C)
and (if the last is C I give 1 to the last one -1) so I have to fixe the case of having C in the last and last-1 and last -2 .. last-n
so I have to fix it to affect 1 to the last one different to C
db.col.aggregate([{'$addFields':{
'myArray_temp':{
'$switch':{
'branches':[{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$eq':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},2]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-2]},'weight':1}],
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':0}]
]}
},
{
'case':{'$eq':[{'$size':'$myArray'},1]},
'then':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
},
{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$ne':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},1]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':1}]
]}
}
],
'default':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
}
}
}}])
The results should be :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C",
"weight": 0
},
{
"value" : "A" ,
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 0
},
{
"value" : "A",
"weight": 0
},
{
"value" : "A",
"weight": 1
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"total" : 4.5,
"myArray" : [
{
"value" : "A",
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 1 // here we give 1 to the last differente to C
},
{
"value" : "C" ,
"weight": 0 // my code affect 1 here cause it find C in the last and affect to the last-1 element.
},
{
"value" : "C" ,
"weight": 0
}
]
}
We have to skip all the last (C) elements and give the 1 weight to the last not-C element.
Thank you in advance!
Longest aggregation in my career but seems to be working:
db.collection.aggregate([
{
$addFields : {
size: { $size: "$myArray" },
reversed: {
$reverseArray: "$myArray"
}
}
},
{
$addFields: {
otherThanC: {
$filter: {
input: "$reversed",
as: "item",
cond: { $ne: [ "$$item.value", "C" ] }
}
}
}
},
{
$addFields: {
firstOtherThanCIndex : {
$indexOfArray: [ "$reversed", { $arrayElemAt: [ "$otherThanC", 0 ] } ]
}
}
},
{
$unwind: {
path: "$reversed",
includeArrayIndex: "arrayIndex"
}
},
{
$addFields: {
weight: {
$switch: {
branches: [
{ case: { $eq: [ "$size", 1 ] }, then: 1 },
{ case: { $and: [ { $eq: [ "$arrayIndex", 0 ] }, { $eq: [ { $size: "$otherThanC" }, 0 ] } ] } , then: 1},
{ case: { $eq: [ "$arrayIndex", "$firstOtherThanCIndex" ] }, then: 1 }
],
default: 0
}
}
}
},
{
$group: {
_id: "$_id",
myArrayReversed: {
$push: {
value: "$reversed.value",
weight: "$weight"
}
}
}
},
{
$project: {
_id: 1,
myArray: { $reverseArray: "$myArrayReversed" }
}
}
])
Brief description of each pipeline stage:
We need to add two extra fields: $size of and array and second array with reversed items
Second and third steps are to find first (last) item not equal to C using $filter and $arrayElemAt which returns first matching index
Then we can $unwind our reversed array using special syntax which adds index of array when unwinding
That is the moment when we can calculate weight using $switch - simply setting 1 if array has one element or indexes are matching and zero otherwise
Then we just need to reshape the data: grouping back by _id and reversing the array

Mongodb: find documents with array where all elements exist in query array, but document array can be smaller

I have a Collection in my database where most documents have an array-field. These arrays contain exactly 2 elements. Now i want to find all documents where all of those array elements are elements of my query array.
Example Documents:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] },
{ a:["1","4"] }
Query array:
["1","2","3"]
The query should find the first 3 documents, but not the last one, since there is no "4" in my query array.
Expected Result:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] }
Looking forward to a helpful answer :).
Since the size is static, you can just check that both elements are in [1,2,3];
db.test.find(
{ $and: [ { "a.0": {$in: ["1","2","3"] } },
{ "a.1": {$in: ["1","2","3"] } } ] },
{ _id: 0, a: 1 }
)
>>> { "a" : [ "1", "2" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "3" ] }
EDIT: Doing it dynamically is a bit more hairy, I can't think of a way without the aggregation framework. Just count matches as 0 and non matches as 1, and finally remove all groups that have a sum != 0;
db.test.aggregate(
{ $unwind: "$a" },
{ $group: { _id: "$_id",
a: { $push: "$a" },
fail: { $sum: {$cond: { if: { $or: [ { $eq:["$a", "1"] },
{ $eq:["$a", "2"] },
{ $eq:["$a", "3"] }]
},
then: 0,
else: 1 } } } } },
{ $match: { fail: 0 } },
{ $project:{ _id: 0, a: 1 } }
)
>>> { "a" : [ "1", "3" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "2" ] }
I also think, that it's impossible without the aggregation framework (if elements count is dynamic).
But I found out more universal way of doing that:
db.tests.aggregate({
$redact: {
$cond: {
if: {$eq: [ {$setIsSubset: [ '$a', [ "1", "2", "3" ] ]}]},
then: '$$KEEP',
else: '$$PRUNE'
}
}
})
I believe the answer to your problem is to use
$in
(from the docs:)
Consider the following example:
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
This query selects all documents in the inventory collection where the qty field value is either 5 or 15. Although you can express this query using the $or operator, choose the $in operator rather than the $or operator when performing equality checks on the same field.
You can also do more complex stuff using arrays. Checkout:
http://docs.mongodb.org/manual/reference/operator/query/in/

Resources