Sum function in mule dataweave takes too much time - sum

I have one ArrayList which contains more than 45,000 records. I want to create a new List which will contain Sum of all values when combination of ID and Name value present in the list. e.g. Below is my input List. I am creating this list by reading xls file and storing it in one variable called "myList" -
[
{ID=481, name =ABCD, value=100},
{ID=481, name =ABCD, value=50},
{ID=2053, name =XYZ, value=300}
]
My Code -
%dw 1.0
%output application/java
%function getIdname(ID, name) (ID ++ " - " ++ name)
%function addAllValues(ID, name) sum ((flowVars.myList.Data filter (getIdname($.ID,$.name) == getIdname(ID, name))).value)
---
{
Data: flowVars.myList.Data map ((payload01 , indexOfPayload01) -> {
ID: payload01.ID,
name: payload01.name,
finaValue : addAllValues(payload01.ID, payload01.name)
})
}
Output -
Data=[
{ID=481, name =ABCD, finalValue=150},
{ID=2053, name =XYZ, finalValue=300}
]
Here, I am getting desired output as above for a file with 5 or 10 records. But, if I am using actual file with more than 45,000 records it is taking too much time to execute my code and it is not creating any output. Also, I am not getting any exception. Can anyone suggest what I am doing wrong here. Why it is taking so much time to sum equal records. I have checked it for 40 mins, but still not received any output

You are filtering 45000 records in every single iteration of the map which is causing this delay (i.e. you are filtering 45000 times). You have to filter/group only once. Also your dataweave will not produce the output you want because there is no distinctBy used.
Instead try this:
%dw 1.0
%output application/java
%var dataByIdName = flowVars.myList.Data groupBy ($.ID ++ $.name)
---
{
Data: dataByIdName map {
ID: $[0].ID,
name: $[0].name,
finalValue: sum $.*value
}
}
This doesn't need distinctBy and also you only group once.

Related

Compare strings with Array of Strings Mule 4

I have to compare/exactly match one String against an array of String.
Input:
{
"country": "India",
"countries": "India,Russia,USA"
}
Output:
If country matches from the List present in countries then Return True, if not then return False.
It usually makes sense to separate your problem, in this case in two parts
Parse the list of countries as a list
Check whether your country is present or not
For 1 you can use something like splitBy. And then for 2 you can use contains.
So, you can do something like:
%dw 2.0
output application/json
fun check(p) = do {
var countries = splitBy(p.countries, ",")
---
contains(countries, p.country)
}
---
check(payload)
%dw 2.0
output application/json
payload.countries contains payload.country
contains could be used to check whether the substring (country) is present in available string (list of countries).
However, It would return true even if an incomplete substring like "Ind" is provided for country as technically the substring is present in the available countries string.
So, splitBy could be used to split the available string (countries) to individual substrings and checked with the country substring.
%dw 2.0
output application/json
---
payload.countries splitBy(",") contains payload.country

MuleSoft Transform Message that Modifies Content of String Field

How would I modify the following MuleSoft Transform Message to reverse the concatenation of the EMPLOYEE_CODE field?
Background: I am working with a MuleSoft process that (i) queries data from a database using a 'SELECT' component (ii) then transforms it into a specified .csv format using a series of 'Transform Message' components and (iii) deposits the .csv onto a SFTP server.
One of the data fields (EMPLOYEE_CODE) has data that comes from the DB in this format: 01-1111 (two characters before a '-' and four characters after). I need my MuleSoft Transform message to swap the order of this separation in the EMPLOYEE_CODE field to output it in this format: 1111-01 (latter four characters now before the '-' and the initial two characters after the '-') before generating the .csv file.
Current MuleSoft Transform Message:
%dw 2.0
output application/csv
quoteValues=true
---
payload map {
employeeID: $.EMPL_ID,
firstName: $.FIRST_NAME,
lastName: $.LAST_NAME,
employeeCode: $.EMPLOYEE_CODE
}
For references, here's an example Data Table that I'm receiving from DB:
EMPL_ID
FIRST_NAME
LAST_NAME
EMPLOYEE_CODE
0000001
John
Doe
01-1111
0000002
Rick
James
02-2222
0000003
John
Smith
03-3333
Transform Message needs to change this to (1111-01, 2222-02, and 3333-03).
For reference, here's the SQL Query in my Select:
SELECT
EMPL_ID
FIRST_NAME
LAST_NAME
EMPLOYEE_CODE
FROM DATABASE.TABLE
It looks you only need to update the field EMPLOYEE_CODE with a very basic string manipulation (concatenate substrings in a different order). I used the update operator to resolve it transforming the data before your script. The other fields are irrelevant for this solution.
%dw 2.0
output application/java
---
payload map (
$ update {
case code at .EMPLOYEE_CODE -> code[3 to -1] ++ "-" ++ code[0 to 1]
}
)
If you prefer to integrate with your script just replace this line:
employeeCode: $.EMPLOYEE_CODE[3 to -1] ++ "-" ++ $.EMPLOYEE_CODE[0 to 1]
Another approach with some inbuilt String functions:
%dw 2.0
import * from dw::core::Strings
output application/json
var inp = "01-1111"
---
substringAfter(inp,"-")++"-"++ substringBefore(inp,"-")
Alternate approach using pure string manipulation
%dw 2.0
import * from dw::core::Strings
output application/json
var inp = "01-1111"
---
rightPad(rightPad(substringAfter(inp,"-"),5,"-"),6,substringBefore(inp,"-"))
In either case you can replace inp with $.EMPLOYEE_CODE
A more generic approach:
StringUtils.dwl:
%dw 2.0
/**
* Reverse the tokens position of a delimited string.
*
* === Parameters
*
* [%header, cols="1,13"]
* |===
* | Name | Type | Description
* | `delimitedString` | `String \| Null` | An optional string that contains a delimiter.
* | `delimiter` | `String` | The delimiter.
* |===
*
* === Example
*
* This example reverse the string "01-1111" into "1111-01"
*
* ==== Source
*
* [source,DataWeave,linenums]
* ----
* %dw 2.0
* output application/json
* ---
* reverseTokens("01-1111", "-")
* ----
*
* ==== Output
*
* `Expression Output`
*
* [source,XML,linenums]
* ----
* "1111-01"
* ----
*/
fun reverseTokens(delimitedString : String | Null, delimiter : String) = do {
var tokens = (delimitedString splitBy delimiter ) default []
---
tokens[-1 to 0] joinBy delimiter
}
main.dwl:
%dw 2.0
output application/csv quoteValues=true
import StringUtils
---
payload map {
employeeID: $.EMPL_ID,
firstName: $.FIRST_NAME,
lastName: $.LAST_NAME,
employeeCode: StringUtils::reverseTokens($.EMPLOYEE_CODE, '-')
}

enrich one JSON array with data from another based on multiple conditions using dataweave 2.0

Recently, I asked this question about filter and merge two JSONs based on multiple conditions.
However, now I need mongo variable to be enriched with mysql variable data. If conditions
mongo.NUM_CONTROL = mysql.DOCNUM
mongo.NUM_ITEM = mysql.LIN_NUM_ITEM
do not match, each mongo element stays the same. But if they match, each mongo element must be enriched with mysql equivalent item.
You can use the following dataweave expression:
%dw 2.0
import * from dw::core::Arrays
output application/json
---
leftJoin(mongo, mysql, (m) -> m.NUM_CONTROL ++ "_" ++ m.NUM_ITEM, (s) -> s.DOCNUM ++ "_" ++ s.LIN_NUM_ITEM) map (item, index) ->
item.l ++ (if (item.r != null) item.r else {})
In order to left join two arrays, a common key field is needed. In this case, based on your scenario, the common keys corresponds to:
mongo: NUM_CONTROL and NUM_ITEM
mysql: DOCNUM and LIN_NUM_ITEM
So, concatenating mongo.NUM_CONTROL with mysql.NUM_ITEM will give the unique record key for mongo, and concatenating mysql.DOCNUM and mysql.LIN_NUM_ITEM will give the unique record key for mysql. Now those calculated keys can be used to left join the arrays. Using an underscore character (or whatever other non numeric character like a pipe, for example) as a separator will make sure that the correct records will be matched (if you have a mongo record with NUM_CONTROL = 1 and NUM_ITEM = 11 and a mysql record with DOCNUM = 11 and LIN_NUM_ITEM = 1, without the separator you would have the same calculated key value for mongo and mysql (111) and they would be joined incorrectly. With the separator, this won't happen as the mongo calculated key would be 1_11 and mysql calculated key 11_1).

Best way to manage and manipulate large sql queries in Mule

I have a requirement to run a large sql query in mule, but the query changes based on the payload. I have to modify the column names, the where and group by conditions etc based on the payload.
Currently I place a template query in resources folder say test.sql. And in the query I place some keyword to be replaced like "replaceColumn". And I use a set variable component to replace that keyword with required keyword like "Field1Column"
variable: formQuery
%dw 2.0
output application/java
var query = readUrl('classpath://test.sql','text/plain')
---
query replace "replaceColumn" with payload.Field1Column
In the DB select component I simply put #[vars.formQuery]
This solution works for me, but gets difficult to replace many parts of the query by nesting the replace operator.
What is a good way to do it?
You can do this recursive replace based on map with key/values as described here https://simpleflatservice.com/mule4/RecursiveReplaceByMap.html
%dw 2.0
var x="The quick brown fox jumps over the lazy dog"
var myMap={fox:'chicken', dog:"me"}
var keys=myMap pluck ($$)
fun changeMe ( value, index ) =
if ( index < sizeOf(keys) )
changeMe( (value replace ( keys[index] ) with ( myMap[keys[index]] ) ) , index+1 )
else value
output application/json
---
changeMe(x,0)
output
"The quick brown chicken jumps over the lazy me"

using regex group in dataweave mule

how can I use regex groups to extract data from a string in dataweave 1.0?
%var sampleString="3,2,0"
{
"groups":using(regexMatch= sampleString scan /^(?<grp1>\d{1}),(?<grp2>\d{1}),(?<grp3>\d{1})$/) {
"group1": regexMatch["grp1"] ????? Any way to get the grp1 value by group name,
"group2": regexMatch[0][2] //works,
"group3": regexMatch[0][3] //works
}
}
Unfortunately the scan function returns an array of arrays (doco), not an array of maps, so the only way you can interrogate it is via numeric index. Named capturing groups are treated as any other group in the end (i.e. the name is ignored).