How to Normalize Data in PostgreSQL

Galaxy Glossary

How do I normalize numeric columns in PostgreSQL?

Normalize data in PostgreSQL by scaling numeric columns to a common range (min-max) or distribution (z-score) for easier comparison and analysis.

Sign up for the latest in SQL knowledge from the Galaxy Team!
Welcome to the Galaxy, Guardian!
You'll be receiving a confirmation email

Follow us on twitter :)
Oops! Something went wrong while submitting the form.

Description

Table of Contents

Why normalize data in PostgreSQL?

Normalization rescales values so order totals, product prices, or customer spend share a comparable scale. This simplifies outlier detection, model training, and dashboard visuals.

When is normalization useful?

Use it before feeding ML models, during KPI comparisons across time, or when plotting metrics with vastly different units.

How do I apply min-max normalization?

WITH stats AS (
SELECT MIN(total_amount) AS min_val,
MAX(total_amount) AS max_val
FROM Orders
)
SELECT o.id,
(o.total_amount - s.min_val) / NULLIF(s.max_val - s.min_val, 0) AS total_amount_norm
FROM Orders o CROSS JOIN stats s;

The NULLIF guard prevents divide-by-zero if all totals are identical.

How do I compute z-scores?

WITH stats AS (
SELECT AVG(total_amount) AS avg_val,
STDDEV_POP(total_amount) AS std_val
FROM Orders
)
SELECT o.id,
(o.total_amount - s.avg_val) / NULLIF(s.std_val, 0) AS total_amount_z
FROM Orders o CROSS JOIN stats s;

Z-scores center data at 0 with a standard deviation of 1, highlighting outliers.

Can I update the table with normalized values?

ALTER TABLE Orders ADD COLUMN total_amount_norm numeric;

WITH stats AS (
SELECT MIN(total_amount) AS min_val,
MAX(total_amount) AS max_val
FROM Orders
)
UPDATE Orders o
SET total_amount_norm = (o.total_amount - s.min_val) /
NULLIF(s.max_val - s.min_val, 0)
FROM stats s;

Add a generated column for automatic updates in PostgreSQL 12+: ALTER TABLE Orders ADD COLUMN total_amount_norm numeric GENERATED ALWAYS AS ((total_amount - (SELECT MIN(total_amount) FROM Orders)) / NULLIF((SELECT MAX(total_amount) FROM Orders) - (SELECT MIN(total_amount) FROM Orders),0)) STORED;

What is the exact syntax?

Use window functions for one-pass computations:

SELECT id,
(total_amount - MIN(total_amount) OVER ()) /
NULLIF(MAX(total_amount) OVER () - MIN(total_amount) OVER (), 0) AS total_amount_norm
FROM Orders;

Parameters:
• Column to scale (e.g., total_amount)
• Aggregate function (MIN, MAX, AVG, STDDEV_POP)
• NULLIF divisor guard
• Optional PARTITION BY to normalize per customer or month.

Best practices for normalization

Store raw data separately; create views or generated columns for normalized values. Always handle zero variance with NULLIF. Document the scaling method in column comments.

Common mistakes to avoid

Divide-by-zero: Forgetting NULLIF when max = min. Fix: wrap the denominator with NULLIF(expr,0).

Mixed scales: Normalizing per-row instead of over the desired partition. Fix: use OVER () for global or OVER (PARTITION BY) for subgroup scaling.

FAQs

Does normalization change original data?

No. Unless you run an UPDATE, queries leave the raw figures untouched.

Is normalization reversible?

Yes—store min/max or mean/stddev and apply the inverse formula to recover original values.

Can I normalize only recent orders?

Absolutely—add a WHERE clause or normalize inside a date-filtered CTE.

Why How to Normalize Data in PostgreSQL is important

How to Normalize Data in PostgreSQL Example Usage


-- Compare normalized spend per customer
WITH per_customer AS (
  SELECT customer_id,
         SUM(total_amount) AS spend
  FROM Orders
  GROUP BY customer_id
), stats AS (
  SELECT MIN(spend) AS min_spend,
         MAX(spend) AS max_spend
  FROM per_customer
)
SELECT c.customer_id,
       (c.spend - s.min_spend) / NULLIF(s.max_spend - s.min_spend, 0) AS spend_norm
FROM per_customer c CROSS JOIN stats s
ORDER BY spend_norm DESC;

How to Normalize Data in PostgreSQL Syntax


-- Min-Max Normalization
SELECT id,
       (total_amount - MIN(total_amount) OVER (PARTITION BY customer_id)) /
       NULLIF(MAX(total_amount) OVER (PARTITION BY customer_id) -
              MIN(total_amount) OVER (PARTITION BY customer_id), 0) AS total_amount_norm_by_customer
FROM Orders;

-- Z-Score Normalization
SELECT id,
       (price - AVG(price) OVER ()) / NULLIF(STDDEV_POP(price) OVER (), 0) AS price_z
FROM Products;

Common Mistakes

Frequently Asked Questions (FAQs)

Does normalization slow down queries?

Window functions scan the data once, so performance is generally good. Indexes are not required because aggregates read all rows anyway.

Can I store normalized values in a view?

Yes—create a view that calculates normalized columns on the fly, keeping the base table unchanged.

Is normalization needed for all numeric columns?

Only scale columns used in comparative analytics or ML models. Operational fields such as IDs or counts rarely need normalization.

Want to learn about other SQL terms?

Trusted by top engineers on high-velocity teams
Aryeo Logo
Assort Health
Curri
Rubie Logo
Bauhealth Logo
Truvideo Logo
Welcome to the Galaxy, Guardian!
You'll be receiving a confirmation email

Follow us on twitter :)
Oops! Something went wrong while submitting the form.