Return counts for multiple ranges in a single SELECT statement

Just use conditional SUM() statements per column for each number range. The total can be summed by just using SUM(1), assuming all of the data in the table is within one of the ranges - if not, just restrict it as with the others.

select sum(case when score between 0 and 3 then 1 else 0 end) as minrange,
       sum(case when score between 4 and 6 then 1 else 0 end) as midrange,
       sum(case when score between 7 and 10 then 1 else 0 end) as maxrange,
       sum(1) as total
from foo;

SQL Fiddle link.


Aggregate FILTER clause in Postgres 9.4+

Since Postgres 9.4 there is a clean and fast (SQL standard) way:

SELECT count(*) FILTER (WHERE score BETWEEN 0 AND 3)  AS low
     , count(*) FILTER (WHERE score BETWEEN 4 AND 7)  AS mid
     , count(*) FILTER (WHERE score BETWEEN 8 AND 10) AS high
     , count(*)                                       AS total
FROM   foo;

total adds up low, mid and high, unless NULL or other values are involved.

Links:

Also read below.

Postgres 9.3-

There are a couple of techniques:

  • For absolute performance, is SUM faster or COUNT?

@Phil provided the standard way with a CASE statement (except for sum(1), which isn't the standard way). I like to use a shorter form:

SELECT count(score BETWEEN 0 AND 3  OR NULL) AS low
     , count(score BETWEEN 4 AND 6  OR NULL) AS mid
     , count(score BETWEEN 7 AND 10 OR NULL) AS high
     , count(*)                              AS total
FROM   foo;

If your values are as defined in your question (only 0 - 10 possible), simplify further:

SELECT count(score < 4 OR NULL)             AS low
     , count(score BETWEEN 4 AND 6 OR NULL) AS mid
     , count(score > 6 OR NULL)             AS high
     , count(*)                             AS total
FROM   foo;

A bit shorter, barely faster.

Subtle differences

There are subtle differences when compared to sum() in Phil's answer:

  • Most importantly, per documentation:

It should be noted that except for count, these functions return a null value when no rows are selected. In particular, sum of no rows returns null, not zero as one might expect, ...

  • count(*) is the standard way and a bit faster than sum(1). Again, null vs. 0 applies.

Either of these queries (including Phil's) counts null values for total. If that's not desirable, use instead:

count(score) AS total_not_null

db<>fiddle here in pg 10.
sqlfiddle in pg 9.3.