Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT

Column Calculations — Arithmetic & Expressions

Do math directly inside SQL — operators, precedence, integer division, ROUND, MOD, and building computed columns that power real business analytics

30 min April 2026

// Part 01

Why SQL Needs to Do Math

Databases store raw numbers — unit prices, quantities, salaries, order totals. But the numbers that drive business decisions are almost always derived — profit margin, GST-inclusive price, discount amount, days since order, salary as a percentage of the highest salary in the team. These are not stored anywhere. They are calculated on demand from the raw values.

SQL lets you perform these calculations directly in your SELECT statement. The result is a computed column that exists only in the query output — the underlying table is never changed. This is one of SQL's most powerful features: the database handles all the math, and you get clean, calculated results without exporting data to Excel and doing it manually.

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

// Part 02

The Five Arithmetic Operators

SQL supports five arithmetic operators for numeric calculations. They work on INTEGER, DECIMAL, FLOAT, and BIGINT columns — and on literal numbers you type directly in the query.

OperatorNameExampleResultFreshMart use
+Addition100 + 50150unit_price + tax_amount
-Subtraction100 - 3070unit_price - cost_price (profit)
*Multiplication100 * 1.18118unit_price * quantity (line total)
/Division100 / 425(profit / unit_price) * 100 (margin %)
%Modulo10 % 31order_id % 2 (split into even/odd batches)

Addition and subtraction

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

Multiplication

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

Division and modulo

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

// Part 03

Operator Precedence — The Order Calculations Run

SQL follows the same arithmetic precedence rules as mathematics. Multiplication and division run before addition and subtraction. Modulo has the same precedence as multiplication and division. Parentheses override everything.

Operator precedence — same as standard mathematics
-- Precedence order (highest to lowest):
--   1. Parentheses ()
--   2. Multiplication *, Division /, Modulo %
--   3. Addition +, Subtraction -

-- Example — what does this calculate?
10 + 5 * 2         -- = 10 + 10 = 20 (multiplication first)
(10 + 5) * 2       -- = 15 * 2 = 30 (parentheses first)
100 / 4 + 5 * 2    -- = 25 + 10 = 35
100 / (4 + 5) * 2  -- = 100 / 9 * 2 = 22.22...

The margin percentage mistake

This is the most common arithmetic precedence bug in SQL analytics. Getting the margin formula wrong produces silently incorrect results — the query runs fine but the numbers are wrong.

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
⚠️ Important
When in doubt about precedence, always use parentheses. A query with explicit parentheses around every sub-expression is slower to write but immediately readable and impossible to get wrong. In finance and analytics, a wrong number caused by a missing parenthesis can have real consequences — always verify calculated results against known reference values.

// Part 04

Integer Division — The Silent Truncation Bug

This is one of the most dangerous arithmetic bugs in SQL and it produces no error — just wrong answers. When you divide two integers in PostgreSQL and MySQL, the result is also an integer — the decimal part is silently discarded (truncated, not rounded).

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

The fix — force decimal division

Three ways to force the database to treat integer division as decimal division:

Three ways to avoid integer division truncation
-- Method 1: multiply by 1.0 (promotes to decimal)
(unit_price - cost_price) * 1.0 / unit_price * 100

-- Method 2: use a decimal literal
(unit_price - cost_price) / 1.0 / unit_price * 100

-- Method 3: CAST to DECIMAL explicitly (most explicit, recommended)
CAST(unit_price - cost_price AS DECIMAL) / unit_price * 100

-- Method 4: ROUND() also forces decimal in some databases
ROUND((unit_price - cost_price) / unit_price, 4) * 100
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

// Part 05

ROUND, CEIL, FLOOR, ABS — Controlling Numeric Output

Raw division results often have many decimal places. SQL provides built-in functions to control the precision of numeric output.

ROUND — round to N decimal places

ROUND syntax
ROUND(number, decimal_places)
ROUND(3.14159, 2)   -- 3.14
ROUND(3.14159, 0)   -- 3.0
ROUND(3.5, 0)       -- 4  (rounds up at .5)
ROUND(3.45, 1)      -- 3.5
ROUND(1234.567, -2) -- 1200 (negative rounds to left of decimal)
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

CEIL — round up to the nearest integer

CEIL / CEILING
CEIL(4.1)    -- 5 (always rounds UP)
CEIL(4.9)    -- 5
CEIL(4.0)    -- 4 (already an integer)
CEIL(-4.1)   -- -4 (rounds towards zero for negatives)

-- PostgreSQL uses CEIL, MySQL/DuckDB support both CEIL and CEILING
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

FLOOR — round down to the nearest integer

FLOOR
FLOOR(4.9)   -- 4 (always rounds DOWN)
FLOOR(4.1)   -- 4
FLOOR(-4.1)  -- -5 (rounds away from zero for negatives)

-- Common use: discount tiers, age buckets, score bands
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

ABS — absolute value

ABS
ABS(100)    -- 100
ABS(-100)   -- 100 (removes the negative sign)
ABS(-3.14)  -- 3.14

-- Common use: distance between values, deviation from target
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

// Part 06

Arithmetic in WHERE — Filtering on Calculated Values

You can use arithmetic expressions in WHERE conditions — not just in SELECT. This lets you filter rows based on calculated values without needing a subquery.

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
⚠️ Important
Arithmetic on column values in WHERE prevents index usage — the same way function calls do. WHERE unit_price * 1.18 > 200 forces the database to calculate unit_price * 1.18 for every row before comparing. Rewrite as WHERE unit_price > 200 / 1.18 to put the arithmetic on the literal side — this lets the database use an index on unit_price. Move calculations to the value side of comparisons whenever possible.

// Part 07

Arithmetic with Dates — Calculating Time Differences

Date arithmetic is one of the most frequent calculations in real analytics. How long did delivery take? How many days since a customer joined? How old is this record? SQL handles date arithmetic, though the syntax varies slightly between databases.

Date subtraction — days between two dates

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

Dialect differences for date arithmetic

Date arithmetic across databases
-- PostgreSQL and DuckDB (this playground):
-- Subtracting two DATE columns returns an integer (number of days)
delivery_date - order_date       -- returns integer days

-- MySQL:
-- Use DATEDIFF() function
DATEDIFF(delivery_date, order_date)  -- returns integer days

-- Adding days to a date:
-- PostgreSQL: date + INTEGER  or  date + INTERVAL '7 days'
order_date + 7                       -- PostgreSQL: adds 7 days
order_date + INTERVAL '7 days'       -- more explicit

-- MySQL:
DATE_ADD(order_date, INTERVAL 7 DAY)

-- SQL Server:
DATEADD(day, 7, order_date)

// Part 08

Building Computed Columns — Real Business Analytics

The real power of arithmetic in SQL is building complex computed columns that answer business questions in a single query. Here are complete real-world examples.

Product profitability report

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

Order value analysis

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

Employee compensation analysis

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

// Part 09

Arithmetic Across Multiple Tables — Using Joins

The most powerful arithmetic queries combine data from multiple tables. You will learn JOINs in depth from Module 30, but here is a preview of how arithmetic works across joined tables — building revenue calculations that span orders, items, and products.

Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…

// Part 10

What This Looks Like at Work

You are a business analyst at Nykaa. The category management team wants a weekly SKU-level profitability report that their buyers use to make restocking decisions. The report needs to show each product's margin, GST-inclusive price, a margin band category, and a restock recommendation based on stock status and margin.

2:00 PM
Requirements briefing
The buyer explains: show all products with their margin percentage, GST price (18% added), a margin band (Premium above 40%, Standard 25–40%, Review below 25%), and a restocking flag — restock immediately if margin is above 30% and item is out of stock, otherwise normal replenishment.
2:20 PM
You build the query
Every piece of this report is arithmetic. No data exists in the database for margin_pct, gst_price, margin_band, or restock_flag — all four are computed columns.
Loading FreshMart DB…
Ctrl + Enter to run
Loading FreshMart database in your browser…
2:45 PM
Delivered and verified
The query runs in under 1 second and produces 25 rows — one per product. The buyer immediately spots that Parle-G Biscuits have a 30% margin and are out of stock — restock flag fires correctly. Head & Shoulders Shampoo has a 23% margin and is in stock — margin_band shows "Review" for the buyer to investigate cost reduction. The entire report that used to take 30 minutes of Excel work now takes 3 seconds to run and is always accurate.

🎯 Pro Tip

The most valuable thing about arithmetic in SQL is that it is reproducible and documentable. Every calculation is visible in the query. A colleague can review it, modify the margin threshold from 0.30 to 0.35, rerun, and get updated results instantly. An Excel formula hidden in a cell is none of these things. When building reports for business teams, prefer SQL calculations over post-export Excel calculations — they are auditable, versionable, and shareable.

// Part 11

Interview Prep — 5 Questions With Complete Answers

Q: What arithmetic operators does SQL support and what is their precedence order?

SQL supports five arithmetic operators: addition (+), subtraction (-), multiplication (*), division (/), and modulo (%). Modulo returns the remainder after integer division — 10 % 3 returns 1 because 10 divided by 3 is 3 with a remainder of 1.

The precedence order mirrors standard mathematics: multiplication, division, and modulo are evaluated first (left to right when they appear together), then addition and subtraction (left to right). Parentheses override all precedence rules — expressions inside parentheses are always evaluated first, regardless of what operators are inside or outside them.

A practical precedence example: 10 + 5 * 2 evaluates as 10 + 10 = 20, not 15 * 2 = 30. To get 30, write (10 + 5) * 2. For margin percentage: unit_price - cost_price / unit_price * 100 is evaluated as unit_price - (cost_price / unit_price * 100), which is wrong. The correct formula requires parentheses: (unit_price - cost_price) / unit_price * 100. Precedence bugs in financial calculations are particularly dangerous because the query runs without error but produces incorrect numbers that may be used in business decisions.

Q: What is integer division and how do you avoid truncation errors?

Integer division occurs when both operands in a division expression are integers — the database returns an integer result, discarding the decimal portion. The result is truncated (not rounded): 7 / 2 returns 3 in PostgreSQL and MySQL, not 3.5. This is mathematically correct in integer arithmetic but almost never what SQL analysts intend when calculating ratios or percentages.

The danger is that no error occurs — the query runs successfully and returns results that appear plausible but are wrong. If a products table has INTEGER columns for unit_price and cost_price, the margin calculation (unit_price - cost_price) / unit_price would return 0 for every product where profit is less than the selling price — which is virtually every product. The analyst would see 0% margin across the board and have no indication that the division was truncated.

Three reliable solutions: multiply by 1.0 before dividing — (unit_price - cost_price) * 1.0 / unit_price — which promotes the result to decimal. Use CAST to explicitly convert to a decimal type — CAST(unit_price - cost_price AS DECIMAL) / unit_price. Or use a decimal literal in the numerator or denominator — (unit_price - cost_price) / 1.0. The CAST approach is most explicit about intent and is recommended in production code where the data types of columns may not be immediately obvious to someone reading the query later.

Q: What is the difference between ROUND, CEIL, and FLOOR?

All three convert a decimal number to a controlled precision, but they round in different directions. ROUND(number, places) rounds to the specified number of decimal places using standard rounding — values ending in .5 or higher round up, below .5 round down. ROUND(3.45, 1) returns 3.5. ROUND(3.44, 1) returns 3.4. ROUND with a negative places argument rounds to the left of the decimal: ROUND(1234, -2) returns 1200.

CEIL (or CEILING) always rounds up to the nearest integer — away from zero for positive numbers. CEIL(4.1) returns 5. CEIL(4.9) returns 5. CEIL(4.0) returns 4. CEIL is used for "how many containers do I need?" problems — if you have 22 items and containers hold 10, you need CEIL(22/10) = 3 containers, not 2.2.

FLOOR always rounds down to the nearest integer — towards zero for positive numbers. FLOOR(4.9) returns 4. FLOOR(4.1) returns 4. FLOOR(4.0) returns 4. FLOOR is used for bucketing and banding — FLOOR(salary / 10000) * 10000 gives the bottom of each ₹10,000 salary band. The trio covers three essential rounding patterns: nearest value (ROUND), always up (CEIL), always down (FLOOR).

Q: Why is it more efficient to put arithmetic on the literal side of a WHERE comparison rather than the column side?

When arithmetic is applied to a column in a WHERE condition — WHERE unit_price * 1.18 >200 — the database must calculate unit_price * 1.18 for every single row in the table before it can determine whether the row qualifies. This prevents the database from using an index on unit_price, because the index stores raw unit_price values, not the result of unit_price * 1.18. The result is a full table scan even when the unit_price column has an index.

When the arithmetic is moved to the literal side — WHERE unit_price > 200 / 1.18 — the database evaluates 200 / 1.18 once (it is a constant, independent of any row), producing approximately 169.49. The WHERE condition becomes WHERE unit_price > 169.49, which the database can evaluate using an index on unit_price directly. Instead of a full table scan, the database does an index range scan — a dramatically more efficient operation on large tables.

This principle is called SARGability (Search ARGument Ability). A WHERE condition is SARGable if the database can use an index to satisfy it. Conditions that apply functions or arithmetic to columns are not SARGable. Conditions that compare raw column values to constants (even computed constants) are SARGable. The rule: whenever possible, isolate the column in the WHERE condition — put all transformations on the literal side. This applies to arithmetic, functions (LOWER, YEAR, MONTH), and any other operation that wraps a column value.

Q: How does date arithmetic work in SQL and where do the dialects differ?

Date arithmetic in SQL calculates intervals between dates or adds/subtracts time from a date. The concept is universal — every SQL database supports it — but the syntax differs significantly across databases, which is one of the most common portability issues when moving SQL between systems.

In PostgreSQL and DuckDB (used in this playground), subtracting two DATE values returns an integer representing the number of days: delivery_date - order_date returns 2 if delivery was 2 days after the order. Adding an integer to a date adds that many days: order_date + 7 returns the date 7 days later. PostgreSQL also supports INTERVAL arithmetic: order_date + INTERVAL '1 month' adds exactly one month. In MySQL, date subtraction is done with the DATEDIFF function: DATEDIFF(delivery_date, order_date). Date addition uses DATE_ADD: DATE_ADD(order_date, INTERVAL 7 DAY). In SQL Server, DATEDIFF(day, order_date, delivery_date) for difference and DATEADD(day, 7, order_date) for addition.

The safest cross-database approach is to use explicit functions rather than operator overloading. DATEDIFF is supported in MySQL and SQL Server (with different argument order). PostgreSQL's date subtraction operator is elegant but PostgreSQL-specific. When writing SQL that must run on multiple database types, document which database it targets or use a database abstraction layer that normalises these differences. In practice, most organisations standardise on one database per workload, making dialect differences a concern primarily when migrating systems or working with multiple clients on different stacks.

// Part 12

Errors You Will Hit — And Exactly Why They Happen

Result shows 0 for every row — margin calculation returns 0 instead of a percentage

Cause: Integer division truncation. Both sides of the division are integers (or the result of integer subtraction is an integer), and dividing one integer by another returns an integer in PostgreSQL and MySQL. Since the profit (unit_price - cost_price) is always less than unit_price for any product sold at a markup, integer division always returns 0. The query runs without error and returns 0 for every product — a silent data quality failure.

Fix: Force decimal division by casting: CAST(unit_price - cost_price AS DECIMAL) / unit_price * 100. Or multiply by 1.0 first: (unit_price - cost_price) * 1.0 / unit_price * 100. Verify by checking the column data types: SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'products'. If unit_price is INTEGER, DECIMAL division must be forced explicitly.

ERROR: division by zero — calculated expression fails on some rows

Cause: One or more rows have a zero value in the denominator of a division expression. This is most common when dividing by a column that should theoretically be non-zero but has zero values due to data entry errors, default values, or products added with a unit_price of 0. In PostgreSQL, division by zero raises an error and aborts the entire query. In MySQL, division by zero returns NULL (not an error) — which can mask the issue.

Fix: Wrap the denominator in NULLIF to convert zero to NULL before dividing: (profit / NULLIF(unit_price, 0)) * 100. Dividing by NULL returns NULL instead of raising an error. The affected rows show NULL in the result, clearly indicating the calculation is not applicable. Also investigate why zero values exist in the denominator column — add a CHECK constraint to prevent unit_price = 0 if that is invalid data: CHECK (unit_price > 0).

Calculation gives unexpected result — wrong number but no error

Cause: Operator precedence bug. The calculation runs without error but evaluates in a different order than intended. For example, unit_price - cost_price / unit_price * 100 is evaluated as unit_price - ((cost_price / unit_price) * 100) — not (unit_price - cost_price) / unit_price * 100. The result looks plausible but is mathematically wrong. This is the hardest type of error to catch because the query succeeds and returns numbers.

Fix: Add parentheses around every sub-expression to make the evaluation order explicit: (unit_price - cost_price) / unit_price * 100. Then verify against a known reference value: for Amul Butter with unit_price=56 and cost_price=44, the correct margin is (56-44)/56*100 = 21.4%. Run the query for that specific product and confirm the output matches. Any time a calculated number looks wrong but the query succeeded, check precedence first.

ROUND() returns a different number than expected — 3.455 rounds to 3.45 instead of 3.46

Cause: Floating-point representation error. Numbers like 3.455 cannot be represented exactly in binary floating-point (the same way 1/3 cannot be written as a finite decimal). The actual stored value might be 3.4549999... instead of 3.455, so ROUND to 2 places produces 3.45 instead of 3.46. This happens with FLOAT and DOUBLE columns. DECIMAL columns store values as exact decimal digits and do not have this problem.

Fix: Use DECIMAL data types for all monetary and percentage values — never FLOAT or DOUBLE. For existing FLOAT columns, cast to DECIMAL before rounding: ROUND(CAST(float_column AS DECIMAL(10,4)), 2). If you cannot change the column type, be aware that ROUND on FLOAT values may behave unexpectedly at the boundary and test edge cases explicitly. For financial applications, this distinction is not optional — FLOAT rounding errors in currency accumulate across millions of transactions and cause accounting discrepancies.

Date arithmetic returns wrong interval — adding months gives wrong date

Cause: Adding an integer to a date adds days, not months. In PostgreSQL, order_date + 1 adds 1 day. order_date + 30 adds 30 days — not one month. February has 28 or 29 days, months have 28-31 days, so 30 days from January 31 lands in March, not on February 28. If you intend to add one calendar month, adding 30 days is incorrect for dates near the end of a month.

Fix: Use INTERVAL for calendar-aware arithmetic: order_date + INTERVAL '1 month' in PostgreSQL correctly adds one calendar month — January 31 + 1 month = February 28/29 (adjusts to the last day of the month). In MySQL: DATE_ADD(order_date, INTERVAL 1 MONTH). For adding days: order_date + 30 or order_date + INTERVAL '30 days' — both add exactly 30 days and are equivalent. Use integers for day addition, INTERVAL for month/year addition.

Try It Yourself

The FreshMart procurement team needs a reorder priority report. Write a query on the products table that returns: product_name, category, unit_price, cost_price, a profit column (unit_price minus cost_price), a margin_pct column (profit as a percentage of unit_price, rounded to 1 decimal), a gst_price column (unit_price multiplied by 1.18, rounded to 2 decimal places), and a priority column using CASE: 'Urgent Restock' if out of stock and margin above 25%, 'Restock' if out of stock and margin 25% or below, 'OK' if in stock. Sort by margin_pct descending.

🎯 Key Takeaways

  • SQL supports five arithmetic operators: + (add), - (subtract), * (multiply), / (divide), % (modulo). Arithmetic in SELECT creates computed columns — the table is never modified.
  • Operator precedence: multiplication, division, and modulo before addition and subtraction. Parentheses always override precedence. Use explicit parentheses for any non-trivial expression.
  • Integer division truncates the decimal portion silently — 7/2 returns 3, not 3.5, in PostgreSQL and MySQL. Force decimal division with CAST(value AS DECIMAL), multiply by 1.0, or use a decimal literal.
  • NULL propagates through arithmetic — any expression involving NULL returns NULL. Use COALESCE to substitute defaults before calculations: COALESCE(discount_pct, 0).
  • ROUND(number, places) rounds to N decimal places. CEIL always rounds up. FLOOR always rounds down. ABS returns the absolute value (removes negative sign).
  • Arithmetic in WHERE works but applying calculations to the column side (WHERE col * 1.18 > 200) prevents index usage. Move calculations to the literal side (WHERE col > 200 / 1.18) to keep queries SARGable.
  • Date subtraction returns days between two dates in PostgreSQL/DuckDB. Use DATEDIFF in MySQL. Use INTERVAL for calendar-aware month/year arithmetic — adding 30 days is not the same as adding one month.
  • Division by zero raises an error in PostgreSQL. Prevent it with NULLIF on the denominator: value / NULLIF(denominator, 0) — returns NULL instead of crashing when the denominator is zero.
  • Always use DECIMAL data types for monetary values — never FLOAT or DOUBLE. Floating-point representation errors in ROUND() accumulate into accounting discrepancies at scale.
  • Computed columns in SQL are reproducible, documentable, and reviewable. They are always preferable to post-export Excel calculations for business reporting.

What comes next

In Module 13, you learn aliases — giving columns and tables readable names with the AS keyword. Aliases make complex queries readable and are the foundation of every clean, maintainable SQL query you will ever write.

Module 13 → Renaming Columns — AS (Aliases)
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...