Aggregation functions allow you to compute statistics and summaries from your data. ServiceSQL provides five core aggregation methods inspired by SQL: COUNT, SUM, AVG, MAX, and MIN.
Available Aggregation Methods
count() Count the number of records
sum() Calculate the total sum of a column
avg() Calculate the average value
max() Find the maximum value
min() Find the minimum value
exists() Check if any records exist
COUNT - Counting Records
Basic Count
const db = APPSQL . init ({ spreadsheetId: "YOUR_ID" });
// Count all users
const totalUsers = db . table ( "Users" ). count ();
Logger . log ( `Total users: ${ totalUsers } ` );
Count with Conditions
// Count active users
const activeCount = db . table ( "Users" )
. where ( "status" , "active" )
. count ();
Logger . log ( `Active users: ${ activeCount } ` );
// Count users by age range
const adultsCount = db . table ( "Users" )
. where ( "age" , ">=" , 18 )
. where ( "age" , "<" , 65 )
. count ();
Logger . log ( `Adults: ${ adultsCount } ` );
Shorthand on Table
The Table class provides a direct count() method:
const users = db . table ( "Users" );
const total = users . count (); // Counts all records
SUM - Calculating Totals
The sum() method calculates the total of a numeric column:
// Total revenue from completed orders
const totalRevenue = db . table ( "Orders" )
. where ( "status" , "completed" )
. sum ( "amount" );
Logger . log ( `Total revenue: $ ${ totalRevenue } ` );
// Sum with multiple conditions
const monthlyRevenue = db . table ( "Orders" )
. where ( "status" , "completed" )
. where ( "created_at" , ">=" , "2026-03-01" )
. where ( "created_at" , "<" , "2026-04-01" )
. sum ( "amount" );
Logger . log ( `March revenue: $ ${ monthlyRevenue } ` );
Non-numeric values are treated as 0 in the sum calculation. Make sure your data is properly formatted.
AVG - Computing Averages
The avg() method calculates the average value:
// Average user age
const avgAge = db . table ( "Users" )
. where ( "status" , "active" )
. avg ( "age" );
Logger . log ( `Average age: ${ avgAge . toFixed ( 1 ) } years` );
// Average order value
const avgOrderValue = db . table ( "Orders" )
. where ( "status" , "completed" )
. avg ( "amount" );
Logger . log ( `Average order: $ ${ avgOrderValue . toFixed ( 2 ) } ` );
// Average for specific segment
const premiumAvg = db . table ( "Users" )
. where ( "subscription" , "premium" )
. avg ( "lifetime_value" );
MAX - Finding Maximum Values
// Highest product price
const maxPrice = db . table ( "Products" ). max ( "price" );
Logger . log ( `Most expensive product: $ ${ maxPrice } ` );
// Highest score this month
const topScore = db . table ( "GameScores" )
. where ( "created_at" , ">=" , "2026-03-01" )
. max ( "score" );
// Latest timestamp
const latestActivity = db . table ( "UserActivity" )
. max ( "last_seen" );
max() works with any comparable values: numbers, strings (alphabetically), and dates.
MIN - Finding Minimum Values
// Lowest product price
const minPrice = db . table ( "Products" )
. where ( "stock" , ">" , 0 )
. min ( "price" );
Logger . log ( `Cheapest in-stock product: $ ${ minPrice } ` );
// Earliest registration date
const firstUser = db . table ( "Users" ). min ( "created_at" );
Logger . log ( `First user registered: ${ firstUser } ` );
// Minimum order value (exclude zeros)
const minOrderValue = db . table ( "Orders" )
. where ( "amount" , ">" , 0 )
. min ( "amount" );
EXISTS - Checking for Records
The exists() method returns true if any records match:
// Check if there are any admins
const hasAdmins = db . table ( "Users" )
. where ( "role" , "admin" )
. exists ();
if ( hasAdmins ) {
Logger . log ( "System has administrators" );
} else {
Logger . log ( "⚠️ No administrators found!" );
}
// Check for pending orders
const hasPendingOrders = db . table ( "Orders" )
. where ( "status" , "pending" )
. exists ();
if ( hasPendingOrders ) {
sendPendingOrdersNotification ();
}
exists() is more efficient than count() > 0 because it stops searching as soon as it finds one match.
Combining Aggregations
Dashboard Statistics
function getDashboardStats () {
const db = APPSQL . init ({ spreadsheetId: "YOUR_ID" });
return {
users: {
total: db . table ( "Users" ). count (),
active: db . table ( "Users" ). where ( "status" , "active" ). count (),
avgAge: db . table ( "Users" ). avg ( "age" )
},
orders: {
total: db . table ( "Orders" ). count (),
completed: db . table ( "Orders" ). where ( "status" , "completed" ). count (),
revenue: db . table ( "Orders" ). where ( "status" , "completed" ). sum ( "amount" ),
avgOrderValue: db . table ( "Orders" ). where ( "status" , "completed" ). avg ( "amount" )
},
products: {
total: db . table ( "Products" ). count (),
inStock: db . table ( "Products" ). where ( "stock" , ">" , 0 ). count (),
maxPrice: db . table ( "Products" ). max ( "price" ),
minPrice: db . table ( "Products" ). min ( "price" )
}
};
}
const stats = getDashboardStats ();
Logger . log ( JSON . stringify ( stats , null , 2 ));
Sales Report
function getSalesReport ( startDate , endDate ) {
const db = APPSQL . init ({ spreadsheetId: "YOUR_ID" });
const ordersQuery = db . table ( "Orders" )
. where ( "status" , "completed" )
. where ( "created_at" , ">=" , startDate )
. where ( "created_at" , "<=" , endDate );
const totalOrders = ordersQuery . count ();
const totalRevenue = ordersQuery . sum ( "amount" );
const avgOrderValue = ordersQuery . avg ( "amount" );
const largestOrder = ordersQuery . max ( "amount" );
const smallestOrder = ordersQuery . min ( "amount" );
return {
period: { start: startDate , end: endDate },
orders: totalOrders ,
revenue: totalRevenue ,
averageOrder: avgOrderValue ,
largestOrder: largestOrder ,
smallestOrder: smallestOrder ,
// Calculate growth
revenuePerDay: totalRevenue / daysBetween ( startDate , endDate )
};
}
Aggregations with GROUP BY
When using groupBy(), each group includes a count property:
const db = APPSQL . init ({ spreadsheetId: "YOUR_ID" });
// Count orders by country
const byCountry = db . table ( "Orders" )
. groupBy ( "country" )
. get ();
byCountry . forEach ( group => {
Logger . log ( ` ${ group . keys . country } : ${ group . count } orders` );
// Calculate sum for this group
const groupTotal = group . items . reduce (( sum , item ) => sum + item . amount , 0 );
Logger . log ( ` Total: $ ${ groupTotal } ` );
});
Filtering Groups with HAVING
Use having() to filter groups by their count:
// Countries with more than 10 orders
const popularCountries = db . table ( "Orders" )
. groupBy ( "country" )
. having ( "count" , ">" , 10 )
. get ();
popularCountries . forEach ( group => {
Logger . log ( ` ${ group . keys . country } : ${ group . count } orders` );
});
// Multiple grouping columns
const stats = db . table ( "Orders" )
. groupBy ( "country" , "status" )
. having ( "count" , ">=" , 5 )
. get ();
having() only works with groupBy(). Use where() to filter individual records before grouping.
Aggregations with Collections
After retrieving data, you can use Collection methods for aggregations:
const users = db . table ( "Users" )
. where ( "status" , "active" )
. get (); // Returns Collection
// Count using collection
const count = users . count ();
// Sum using collection
const totalPoints = users . sum ( "points" );
// Average using collection
const avgAge = users . avg ( "age" );
// Max/Min using collection
const maxPoints = users . max ( "points" );
const minPoints = users . min ( "points" );
Use QueryBuilder aggregations (like .sum()) when you only need the result. Use Collections when you need both the data and aggregations.
Real-World Examples
User Analytics
function getUserAnalytics () {
const db = APPSQL . init ({ spreadsheetId: "YOUR_ID" });
const usersTable = db . table ( "Users" );
return {
overview: {
total: usersTable . count (),
active: usersTable . where ( "status" , "active" ). count (),
verified: usersTable . where ( "verified" , true ). count (),
premium: usersTable . where ( "subscription" , "premium" ). count ()
},
demographics: {
avgAge: usersTable . avg ( "age" ),
youngestAge: usersTable . min ( "age" ),
oldestAge: usersTable . max ( "age" )
},
engagement: {
avgLoginCount: usersTable . avg ( "login_count" ),
mostActiveUser: usersTable . max ( "login_count" ),
totalPoints: usersTable . sum ( "points" )
},
growth: {
thisMonth: usersTable
. where ( "created_at" , ">=" , getStartOfMonth ())
. count (),
lastMonth: usersTable
. where ( "created_at" , ">=" , getStartOfLastMonth ())
. where ( "created_at" , "<" , getStartOfMonth ())
. count ()
}
};
}
E-commerce Metrics
function getEcommerceMetrics () {
const db = APPSQL . init ({ spreadsheetId: "YOUR_ID" });
// Filter to completed orders only
const completedOrders = db . table ( "Orders" )
. where ( "status" , "completed" );
const totalRevenue = completedOrders . sum ( "amount" );
const orderCount = completedOrders . count ();
const avgOrderValue = completedOrders . avg ( "amount" );
return {
revenue: {
total: totalRevenue ,
average: avgOrderValue ,
largest: completedOrders . max ( "amount" ),
smallest: completedOrders . min ( "amount" )
},
orders: {
completed: orderCount ,
pending: db . table ( "Orders" ). where ( "status" , "pending" ). count (),
cancelled: db . table ( "Orders" ). where ( "status" , "cancelled" ). count ()
},
customers: {
total: db . table ( "Customers" ). count (),
withOrders: db . table ( "Customers" )
. join ( "Orders" , "Customers.id" , "Orders.customer_id" )
. distinct ()
. count (),
avgLifetimeValue: db . table ( "Customers" ). avg ( "lifetime_value" )
},
products: {
total: db . table ( "Products" ). count (),
inStock: db . table ( "Products" ). where ( "stock" , ">" , 0 ). count (),
outOfStock: db . table ( "Products" ). where ( "stock" , 0 ). count (),
avgPrice: db . table ( "Products" ). avg ( "price" )
}
};
}
function trackPerformance ( userId , period = 30 ) {
const db = APPSQL . init ({ spreadsheetId: "YOUR_ID" });
const cutoffDate = new Date ();
cutoffDate . setDate ( cutoffDate . getDate () - period );
const userActivities = db . table ( "Activities" )
. where ( "user_id" , userId )
. where ( "created_at" , ">=" , cutoffDate . toISOString ());
return {
period: `Last ${ period } days` ,
activities: {
total: userActivities . count (),
avgPerDay: userActivities . count () / period
},
points: {
total: userActivities . sum ( "points_earned" ),
average: userActivities . avg ( "points_earned" ),
best: userActivities . max ( "points_earned" )
},
time: {
totalMinutes: userActivities . sum ( "duration_minutes" ),
avgSession: userActivities . avg ( "duration_minutes" ),
longestSession: userActivities . max ( "duration_minutes" )
}
};
}
Use Aggregations Instead of Loading All Data
Good Practice // ✅ Efficient - only computes the sum
const total = db . table ( "Orders" )
. where ( "status" , "completed" )
. sum ( "amount" );
Avoid // ❌ Inefficient - loads all records into memory
const orders = db . table ( "Orders" )
. where ( "status" , "completed" )
. get ();
const total = orders . reduce (( sum , o ) => sum + o . amount , 0 );
Filter Before Aggregating
// ✅ Filter first, then aggregate
const activeUserCount = db . table ( "Users" )
. where ( "status" , "active" ) // Filter first
. where ( "verified" , true ) // More filters
. count (); // Then count
// The aggregation only processes filtered records
// ✅ Reuse the query for multiple aggregations
const ordersQuery = db . table ( "Orders" )
. where ( "status" , "completed" )
. where ( "created_at" , ">=" , startDate );
const stats = {
count: ordersQuery . count (),
total: ordersQuery . sum ( "amount" ),
average: ordersQuery . avg ( "amount" ),
max: ordersQuery . max ( "amount" )
};
Each aggregation method executes a separate query. Store intermediate queries in variables when computing multiple aggregations on the same dataset.
Summary
ServiceSQL provides powerful aggregation methods:
count() - Count records matching conditions
sum(column) - Calculate totals
avg(column) - Compute averages
max(column) - Find maximum values
min(column) - Find minimum values
exists() - Check if records exist
All aggregation methods:
Work with where() conditions
Can be combined with groupBy() and having()
Are available on both QueryBuilder and Collections
Return computed values, not full records
For related topics, see: