After learning the ropes for the Map/Reduce script type in part 1, part 2, and part 3, we’re ready to conclude the series by studying a few Map/Reduce code examples.

Recap

The Map/Reduce script type is a very flexible tool for handling large amounts of data. If a simple automation is all that’s required, one can simply use the “Get Input Data” and “Map” functions. On the other hand, if data needs to be handled in an organized fashion, one has freedom to use the Map/Reduce script type to its fullest potential.

In order to get the big idea of this script type, we included a diagram in our previous post. I’d like to include that diagram once again for reference.

Now, let’s dig into the first of our Map/Reduce code examples. For illustration purposes, we will create a Map/Reduce script that averages all the price-level rates on an item to get an average price. The difficulty is that each price level can have multiple tiers. For example, if an order is made for 75 of an item, that item will be one price. But if an order comes through for 275 of that item, the price may be discounted because of the quantity. This tiered pricing can be dynamic based on the quantity, and our Map/Reduce script will need to take this multi-level pricing into account.

To handle these differing rates and calculate the average price, we’ll need to gather all the information, organize the data, and then log the organized and calculated final price.

The Get-Input-Data Stage

function getInputData(context) {
    var searchObject = search.create({
        type: "item",
        filters: [["internalid", "anyof", "43276"], "AND", ["pricing.currency", "anyof", "1"]],
        columns:
            [
                search.createColumn({ name: "pricelevel", join: "pricing" }),
                search.createColumn({ name: "unitprice", join: "pricing" })
            ]
    });

    return searchObject;
}

In this stage, we simply want to create and return a search object. For our example, I chose a random item (internal ID: “43276”) with a currency of USA (“1”).

In our results, we will receive the price level and the unit price for that tier. If there are multiple tiers on that price level, the search will return a result for each of those tiers within that price-level.

When we return this search object, NetSuite will run the search and feed each result asynchronously into the Map stage.

The Map Stage

function map(context) {
    log.debug('context', context);
    const searchResult = JSON.parse(context.value);

    context.write({
        key: searchResult.values['pricelevel.pricing'].value,
        value: searchResult.values['unitprice.pricing']
    });
}

First, it’s always a good idea to log the context so we know what we’re dealing with. It’s also important to parse the results with JSON.parse so we can handle the data easily in our script. Even after parsing, you may need to finagle the data a bit to get exactly what you need. Therefore, be sure to do lots of testing and logging to make sure you are correctly accessing the value.

Since we are wanting to gather together all the prices into one and calculate the average, we’ll want to pass on this piece of data to the Reduce stage. In order to do this, we write the key/value pair to the context ({price-level: price for tier}).

The Reduce Stage

function reduce(context) {
    log.debug('reduce context', context);

    // Calculate average of price-level
    var totalPriceOfTiers = 0;
    for (var i = 0; i < context.values.length; i++) {
        totalPriceOfTiers += parseInt(context.values[i]);
    }

    const averagePriceAcrossTiers = (totalPriceOfTiers / context.values.length);
    log.debug('averagePriceAcrossTiers', averagePriceAcrossTiers);


    // Write price-level average price to context for Summarize stage to handle
    context.write({
        key: context.key,
        value: averagePriceAcrossTiers
    });
}

In the Reduce stage, we will calculate the average price for each price-level. Then, after passing that data to the summarize stage, we will add up all those average prices on each price-level and calculate the total average for the item as a whole.

The Summarize Stage

function summarize(context) {
    // Log details about the script's execution.
    log.audit({
        title: 'Usage units consumed',
        details: context.usage
    });
    log.audit({
        title: 'Concurrency',
        details: context.concurrency
    });
    log.audit({
        title: 'Number of yields',
        details: context.yields
    });


    var totalOfAllPriceLevels = 0;
    var priceLevelCounter = 0;
    context.output.iterator().each(function (key, value) {
        log.debug('key, value', key + ' - ' + value);
        totalOfAllPriceLevels += parseInt(value);
        priceLevelCounter++;
        return true;
    });

    const averagePrice = (totalOfAllPriceLevels / priceLevelCounter).toFixed(2);
    log.debug('averagePrice', averagePrice);
}

At this point, the context parameter which NetSuite provides has robust information about the script. We are able to access and log details about the script’s performance. It also contains the information we wrote to the context in the Reduce stage. For more information on what is typically contained in the context parameter at this point, see NetSuite’s help article about it here.

To calculate the entire average of all the price levels, we need to loop over each key/value pair (containing the price-level ID and the average price for that price-level). Then for each key/value pair, we will add that price-level average price to a new variable, as well as count how many pairs we have (so we can calculate the average).

Finally, we’ll simply calculate the average, round the number, and log the final result to the script execution log. And we’re done!

A Word About Deployment

We can deploy our Map/Reduce script in a similar way we deploy any other SuiteScript. There are a few things to note, however.

First, if you would like your script to run faster and have higher concurrency, you can up the Concurrency Limit on the script deployment.

Secondly, if you desire to deploy the script right away (perhaps for testing purposes), you will actually need to save the deployment first and edit it again. Only after saving and returning to edit mode will the “Save and Execute” option be available.

Though it may sound like an oxymoron for the average person, “Save and Execute” is exactly what you’re looking for if you would like the script to run immediately.

Conclusion

We trust that these Map/Reduce code examples further solidified your understanding of how to use the Map/Reduce script type. And if you have made it through the entirety of our Map/Reduce tutorial series this far, congratulations! Although we have finished our tutorial of the Map/Reduce script, we have many more helpful SuiteScript development posts coming your way. Make sure to subscribe to our weekly email list to ensure you don’t miss out!