COUNT, SUM, AVG in SQL Server: Turning Rows Into Insight

COUNT, SUM, AVG in SQL Server: Turning Rows Into Insight

Everything so far has returned individual rows. Aggregate functions are the first tool that collapses many rows into one summary value — the foundation of every report, dashboard, and “how many / how much / on average” question you’ll ever answer with SQL.

Turning Rows Into Insight(many rows → one number)COUNT(*)3 rows — always a real numberSUM(funding_usd)$45,000,000 totalAVG(employees)65 — mean team sizeCOUNT(*) vs COUNT(column) — not the same question:COUNT(*)counts every row — NULLs includedacquired_by is NULL? still +1COUNT(acquired_by)only counts non-NULL valueshere: just 1 (PayFlow)SUM / AVG / MIN / MAX return NULLon an empty group — not 0. COUNT(*)is the one exception.

Setup — a small startup funding dataset to follow along:

CREATE TABLE dbo.Startup (
    startup_id    INT IDENTITY(1,1) PRIMARY KEY,
    name          NVARCHAR(100) NOT NULL,
    industry      NVARCHAR(50)  NOT NULL,
    founded_year  INT           NOT NULL,
    funding_usd   DECIMAL(15,2) NOT NULL,
    employees     INT           NOT NULL
);
INSERT INTO dbo.Startup (name, industry, founded_year, funding_usd, employees) VALUES
('DataForge', 'AI', 2019, 12000000, 45),
('CloudNest', 'Cloud Infra', 2018, 25000000, 120),
('PayFlow', 'Fintech', 2020, 8000000, 30);

The Five Core Aggregate Functions

SELECT COUNT(*) AS total_startups FROM dbo.Startup;
SELECT SUM(funding_usd) AS total_funding FROM dbo.Startup;
SELECT AVG(employees) AS avg_team_size FROM dbo.Startup;
SELECT MIN(founded_year) AS oldest, MAX(founded_year) AS newest FROM dbo.Startup;

COUNT(*) vs COUNT(column): Not the Same Thing

COUNT(*) Counts every row, regardless of NULLs COUNT(column) Only counts rows where that column is NOT NULL

-- Add a nullable column to see the difference for real
ALTER TABLE dbo.Startup ADD acquired_by NVARCHAR(100) NULL;
UPDATE dbo.Startup SET acquired_by = 'BigTech Corp' WHERE name = 'PayFlow';

SELECT COUNT(*) AS total_rows,               -- 3 (every row counts)
       COUNT(acquired_by) AS acquired_count  -- 1 (only non-NULL values count)
FROM dbo.Startup;

These can give genuinely different answers on the same table. Always be deliberate about which one you actually mean — “how many rows” and “how many rows have a value here” are different questions, and mixing them up silently produces a wrong number, not an error.

COUNT(DISTINCT …): A Third Variant

SELECT COUNT(DISTINCT industry) AS unique_industries FROM dbo.Startup;

Counts how many distinct non-NULL values appear, not how many rows — useful for “how many different X do we have,” as opposed to “how many rows mention X.”

Aggregates and NULL: The Result Isn’t Always 0

-- On an empty result set (e.g. after a WHERE that matches nothing):
SELECT SUM(funding_usd) FROM dbo.Startup WHERE industry = 'Biotech'; -- returns NULL, not 0
SELECT COUNT(*) FROM dbo.Startup WHERE industry = 'Biotech';         -- returns 0, correctly
Common mistake: Assuming SUM/AVG/MIN/MAX return 0 when there’s nothing to aggregate. They return NULL — which then silently propagates into any further arithmetic (NULL + 100 is still NULL). Wrap the result in ISNULL(SUM(funding_usd), 0) if a report genuinely needs to show zero rather than blank for an empty group. COUNT(*) is the one exception — it always returns a real number, since “count of nothing” is meaningfully zero.

Combining Multiple Aggregates in One Query

SELECT
    COUNT(*) AS total_startups,
    SUM(funding_usd) AS total_funding,
    AVG(funding_usd) AS avg_funding,
    MAX(funding_usd) AS biggest_round,
    MIN(founded_year) AS oldest_founding_year
FROM dbo.Startup;

Every aggregate in a single SELECT (with no GROUP BY) computes over the same full set of matching rows — this is a common, efficient way to build a one-row “summary card” for a dashboard in a single round trip.

Key Takeaways

  • COUNT, SUM, AVG, MIN, MAX collapse many rows into one summary value
  • COUNT(*) counts rows; COUNT(column) counts non-NULL values in that column specifically; COUNT(DISTINCT column) counts unique non-NULL values
  • Aggregate functions (other than COUNT(*)) return NULL, not 0, when there’s nothing to aggregate — wrap in ISNULL/COALESCE if you need a real zero
Practice tip: Add two more startups to the table with different industries, then write a single query returning the total count, total funding, and average team size — from memory, without re-reading the examples above.

Enjoyed this?

Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server Fundamentals, coming soon on this site.