00:00

MySQL sum() Function

Last updated:

SUM() adds up the values in a column: total revenue, total hours, total stock. Together with GROUP BY, conditions and window functions it answers most "how much?" questions.

1. Basic SUM

Table: bills (6 rows)

bill_idoutletwaiterbill_dateitemsamounttippayment
1KoregaonAjay2026-03-0231200.00100.00card
2KoregaonBina2026-03-032800.00NULLcash
3AundhChetan2026-03-0451500.00150.00upi
4KoregaonAjay2026-03-051600.0050.00cash
5AundhChetan2026-03-064900.00NULLupi
6AundhDeepa2026-03-062450.5020.00card
query.sql
SELECT SUM(amount) AS revenue, SUM(items) AS items_sold, SUM(tip) AS tips FROM bills;

Result: 1 row

revenueitems_soldtips
5450.5017320.00

Tips include NULLs, which SUM simply skips.

2. SUM with WHERE and GROUP BY

query.sql
SELECT SUM(amount) AS aundh_revenue FROM bills WHERE outlet = 'Aundh';

Result: 1 row

aundh_revenue
2850.50
query.sql
SELECT outlet, SUM(amount) AS revenue, SUM(items) AS items FROM bills GROUP BY outlet ORDER BY revenue DESC;

Result: 2 rows

outletrevenueitems
Aundh2850.5011
Koregaon2600.006
query.sql
SELECT waiter, SUM(amount) AS sales FROM bills GROUP BY waiter HAVING SUM(amount) > 1000;

Result: 2 rows

waitersales
Ajay1800.00
Chetan2400.00

3. NULLs and Empty Results

SUM of no rows (or only NULLs) is NULL, not 0. Wrap it in COALESCE for reports:

query.sql
SELECT SUM(amount) AS raw_sum, COALESCE(SUM(amount), 0) AS safe_sum FROM bills WHERE outlet = 'Baner';

Result: 1 row

raw_sumsafe_sum
NULL0.00
query.sql
SELECT SUM(tip) AS tips_bina FROM bills WHERE waiter = 'Bina';   -- only NULL tips

Result: 1 row

tips_bina
NULL

4. Summing Expressions

query.sql
SELECT SUM(amount + COALESCE(tip, 0)) AS amount_with_tips, SUM(amount * 0.05) AS gst_5pct FROM bills;

Result: 1 row

amount_with_tipsgst_5pct
5770.50272.5250
SUM(amount + tip) without COALESCE would skip whole bills that have a NULL tip, because amount + NULL is NULL.
query.sql
SELECT SUM(amount + tip) AS wrong, SUM(amount + COALESCE(tip, 0)) AS right_total FROM bills;

Result: 1 row

wrongright_total
4070.505770.50

5. Conditional Sums (Pivot)

Sum only the rows that match a condition, several at once. This turns rows into columns (a pivot):

pivot.sql
SELECT outlet,
       SUM(CASE WHEN payment = 'cash' THEN amount ELSE 0 END) AS cash,
       SUM(CASE WHEN payment = 'card' THEN amount ELSE 0 END) AS card,
       SUM(CASE WHEN payment = 'upi'  THEN amount ELSE 0 END) AS upi,
       SUM(amount) AS total
FROM bills
GROUP BY outlet;

Result: 2 rows

outletcashcardupitotal
Koregaon1400.001200.000.002600.00
Aundh0.00450.502400.002850.50

A shorter MySQL form: SUM(IF(payment = 'cash', amount, 0)).

6. SUM(DISTINCT)

SUM(DISTINCT col) adds each different value once. It's rarely what you want for money (two bills of ₹900 are both real), so use it with care:

query.sql
SELECT SUM(items) AS items_total, SUM(DISTINCT items) AS distinct_item_counts FROM bills;

Result: 1 row

items_totaldistinct_item_counts
1715

7. Running Totals with SUM() OVER

Total versus running totalFive daily sales 1200, 800, 1500, 600 and 900. SUM gives a single total 5000. SUM OVER ORDER BY day gives a running total on each row: 1200, 2000, 3500, 4100 and 5000.SUM(amount) OVER (ORDER BY day)dayamountrunningMon12001200Tue8002000Wed15003500Thu6004100Fri9005000SUM(amount) = 5000one row: the grand totalthe last running value equals the total
Figure 1: a plain SUM collapses rows into one total; SUM as a window function keeps every row and accumulates.
running.sql
SELECT bill_date, outlet, amount,
       SUM(amount) OVER (ORDER BY bill_date, bill_id)                      AS running_total,
       SUM(amount) OVER (PARTITION BY outlet ORDER BY bill_date, bill_id)  AS outlet_running,
       ROUND(100 * amount / SUM(amount) OVER (), 1)                        AS pct_of_all
FROM bills
ORDER BY bill_date, bill_id;

Result: 6 rows

bill_dateoutletamountrunning_totaloutlet_runningpct_of_all
2026-03-02Koregaon1200.001200.001200.0022.0
2026-03-03Koregaon800.002000.002000.0014.7
2026-03-04Aundh1500.003500.001500.0027.5
2026-03-05Koregaon600.004100.002600.0011.0
2026-03-06Aundh900.005000.002400.0016.5
2026-03-06Aundh450.505450.502850.508.3

8. Result Types and Precision

  • SUM of integers or DECIMAL returns DECIMAL, so totals stay exact.
  • SUM of FLOAT/DOUBLE returns DOUBLE and may show tiny rounding errors; store money as DECIMAL.
  • If a DECIMAL total exceeds 65 digits, MySQL raises an out-of-range error.
query.sql
SELECT 0.1 + 0.2 AS decimal_math, CAST(0.1 AS DOUBLE) + CAST(0.2 AS DOUBLE) AS double_math;

Result: 1 row

decimal_mathdouble_math
0.30.30000000000000004

9. Practice Exercises

Exercise 1: Revenue per day, including a day total of tips (0 if none).
solution.sql
SELECT bill_date, SUM(amount) AS revenue, COALESCE(SUM(tip), 0) AS tips
FROM bills GROUP BY bill_date ORDER BY bill_date;

Result: 5 rows

bill_daterevenuetips
2026-03-021200.00100.00
2026-03-03800.000.00
2026-03-041500.00150.00
2026-03-05600.0050.00
2026-03-061350.5020.00
Exercise 2: Each waiter's share of total revenue as a percentage.
solution.sql
SELECT waiter, SUM(amount) AS sales,
       ROUND(100 * SUM(amount) / (SELECT SUM(amount) FROM bills), 1) AS pct
FROM bills GROUP BY waiter ORDER BY sales DESC;

Result: 4 rows

waitersalespct
Chetan2400.0044.0
Ajay1800.0033.0
Bina800.0014.7
Deepa450.508.3

10. Summary

  • SUM(col) adds up non-NULL values; SUM of nothing is NULL, so use COALESCE.
  • Combine with WHERE, GROUP BY and HAVING for filtered and grouped totals.
  • Watch NULLs inside expressions; COALESCE them first.
  • SUM(CASE …) builds pivots; SUM() OVER (ORDER BY …) builds running totals.
  • Use DECIMAL for exact money totals.