How to explain Javascript .filter() .map() and .reduce() to SQL users

I found using .filter() .map() and .reduce() Array methods could help to reduce code complexity and simplify many typical array data processing tasks. Looking at http://kangax.github.io/compat-table/es5/#test-Array_methods, you can confirm these methods are compatible with almost any modern desktop/mobile browser.

A good approach to understand how to use .filter() .map() and .reduce() is to compare each one to SQL data operations in SELECT statements.

Consider the following SQL query:

SELECT
category, SUM(amount) as total
FROM transactions
WHERE status = 'active'
GROUP BY category

A common SQL command could have three type of data operations: map  or select fields to display ( category, total ), filter data using conditions (status = ‘active’), and reduce the results generating summary data using an aggregate function (SUM) specifying some aggregate groups (category). In Javascript, these operations could be performed using .filter() .map() and .reduce()

Given the following data set in a Javascript array of objects, I will explain and show you some usage examples for each array method:

var transactions = [
{ id: 4534, date: "2019–01–08", product: 112, price: 21, quantity: 2, taxes: 1.12, declined: false },
{ id: 4535, date: "2019–01–08", product: 232, price: 32, quantity: 3, taxes: 2.19, declined: false },
{ id: 4536, date: "2019–01–08", product: 554, price: 7, quantity: 100, taxes: 10.55, declined: true },
{ id: 4537, date: "2019–01–08", product: 433, price: 21, quantity: 2, taxes: 1.12, declined: false },
{ id: 4538, date: "2019–01–08", product: 112, price: 21, quantity: 4, taxes: 2.24, declined: false }
];

filter()

We use filter() as the first operation to perform, because it reduces the amount of rows to process information.

result = myArray.filter( filterFunc )

filterFunc takes one element of the array each time, and must return true to include (or false to exclude) that item from the result.

For example:

var activeTransactions = transactions.filter(function(item){
  return !item.declined
})

Now activeTransactions will only contain transactions where declined is true. In this example, 4 out of 5. This is the equivalent of the following SQL query:

SELECT * FROM transactions WHERE NOT declined

map()

We can use map() to select which fields will be part of the result.

result = myArray.map( mapFunc )

mapFunc takes one element of myArray each time, and must return a value or object mapping element attributes.

var totals = transactions.map(function(item){
  return { 
    productId: item.product, 
    totalAmount : item.price * item.quantity + item.taxes 
  }
})

Now totals will contain an array of objects, each one with only 2  fields: productId (alias of .product) and totalAmount (calculated from .price .quantity and .taxes). This is equivalent to the following SQL query:

SELECT product as productId, price*quantity+taxes as totalAmount 
FROM transactions

reduce()

Finally, we can get a summary of values (or just a single final value) from the dataset, reducing rows to a single element.

result = myArray.reduce( reduceFunc, initialAccum )

reduceFunc takes an accumulator and each element of myArray, accumulate values, and return the final summary.initialAccum is the initial value of your result, starting with values to be accumulated (for example, starting with zero).

var total = transactions.reduce(function(accum, item){
  return { 
    total: accum.total + item.quantity*item.price+item.taxes,
    count: accum.count + 1
  }
}, {total: 0, count: 0} )

Now total will contain a grand total amount total and how many transactions (count) were processed. This is equivalent to the following SQL query:

SELECT 
SUM(price*quantity+taxes) as 'total', count(1) as 'count' 
FROM transactions

Sometimes, you only need to return a single final value, in this case your accumulator must be a single value (0 in the next example):

var total = transactions.reduce(function(accum, item){
  return accum + item.quantity*item.price+item.taxes
}, 0 )

Mixing all together

Finally, because .filter() .map() and .reduce() return arrays, you can put everything together in a chain of calls to make a final result like this:

// Total amount of transactions not declined
var total = 
   transactions
   .filter(function(item){ 
     return !item.declined 
   })
   .map(function(item){ 
     return { 
       totalAmount : item.price * item.quantity + item.taxes 
     }
   })
   .reduce(function(accum, item){
     return accum + item.totalAmount
   }, 0 )

This is equivalent to the result of the following SQL query:

SELECT 
SUM(price*quantity+taxes) 
FROM transactions
WHERE NOT declined

Do it yourself

You can try for yourself running and modifying the following Javascript demo with more examples:

https://jsfiddle.net/FranIg/3pn4rs5c/