566

Is it possible to specify a condition in Count()? I would like to count only the rows that have, for example, "Manager" in the Position column.

I want to do it in the count statement, not using WHERE; I'm asking about it because I need to count both Managers and Other in the same SELECT (something like Count(Position = Manager), Count(Position = Other)) so WHERE is no use for me in this example.

Michael
  • 8,362
  • 6
  • 61
  • 88
agnieszka
  • 14,897
  • 30
  • 95
  • 113
  • 6
    Boo to all the * users, use Count(SomeColumnInYourTable) where Position = 'Manager' – Mark Dickinson Sep 09 '09 at 14:31
  • 8
    @Mark: On all modern databases, this doesn't make **any** difference. – Philippe Leybaert Sep 09 '09 at 14:34
  • 1
    Please point us to some literature if you have some. Thanks Philippe – Mark Dickinson Sep 09 '09 at 14:35
  • 2
    You could also give one single example where it does make a difference. – Philippe Leybaert Sep 09 '09 at 14:36
  • 6
    @Mark & Philippe: Actually it can make a greate difference. If the field is nullable and not indexed, the query need to touch every record in the table, so using count(*) and count(field) can give differnet results and different performance. – Guffa Sep 09 '09 at 14:37
  • 1
    I'm under the impression that * is only excuseable in EXISTS queries, this is SQL Server that I'm talking about. There are lots of performance myths I accept, but I have worked on modern systems with large tables where it does make a difference. – Mark Dickinson Sep 09 '09 at 14:41
  • 1
    Sorry, I didn't mean to start a Sql internals war. @Phillipe sorry if I sounded big headed. – Mark Dickinson Sep 09 '09 at 14:50
  • 4
    I've analyzed execution plans for count(*) vs count(x) for years, and so far I haven't found a single one that showed a difference in performance. That's why I would really like to see an example of a query where there is a difference. – Philippe Leybaert Sep 09 '09 at 14:57
  • 3
    @Matthew: we're not talking about `SELECT *`, but `SELECT COUNT(*)`, which is a totally different beast. – Philippe Leybaert Sep 09 '09 at 14:58

13 Answers13

891

If you can't just limit the query itself with a where clause, you can use the fact that the count aggregate only counts the non-null values:

select count(case Position when 'Manager' then 1 else null end)
from ...

You can also use the sum aggregate in a similar way:

select sum(case Position when 'Manager' then 1 else 0 end)
from ...
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • what if my field is integer and I want to match null. it doesn't work this way select count(case IntegerField when 'NULL' then 1 else null end) from – Faizan Aug 31 '16 at 10:16
  • 2
    @Faizan `null` is special. Use `case when IntegerField is null then ...` – Peet Brits Dec 09 '16 at 10:08
  • When working with boolean fields you can use this : `SUM(CONVERT(int, IsManager))` – Simon_Weaver Apr 04 '17 at 23:09
  • 3
    SQL Server implies an `else null` for `case` statements, so the `count()` example can be 10 characters shorter (if you count the space). – Michael Feb 16 '18 at 22:05
  • For some curious reason the `sum` aggregate is somewhat slower than `case` in MSSQL. I did not observe any difference in PostgreSQL. – Marcel Nov 28 '22 at 12:04
252

Assuming you do not want to restrict the rows that are returned because you are aggregating other values as well, you can do it like this:

select count(case when Position = 'Manager' then 1 else null end) as ManagerCount
from ...

Let's say within the same column you had values of Manager, Supervisor, and Team Lead, you could get the counts of each like this:

select count(case when Position = 'Manager' then 1 else null end) as ManagerCount,
    count(case when Position = 'Supervisor' then 1 else null end) as SupervisorCount,
    count(case when Position = 'Team Lead' then 1 else null end) as TeamLeadCount,
from ...
D'Arcy Rittich
  • 167,292
  • 40
  • 290
  • 283
  • 4
    @RedFilter It's not even necessary to specify the `else` part, just `end` it right after the `1`. – Denis Valeev Sep 22 '10 at 14:05
  • 8
    @Denis: correct - I often leave the `else` in as it better documents the results of the case statement, especially for novie SQL developers. For brevity, it can be removed in this case. – D'Arcy Rittich Sep 22 '10 at 14:35
  • You can also chain conditions, i.e. `case when Position = 'Manager' OR Position = 'Team Lead' then 1 end`, nice! – Martin Braun Mar 14 '23 at 23:52
56

@Guffa 's answer is excellent, just point out that maybe is cleaner with an IF statement

select count(IIF(Position = 'Manager', 1, NULL)) as ManagerCount
from ...
Hivenfour
  • 651
  • 6
  • 6
43

Depends what you mean, but the other interpretation of the meaning is where you want to count rows with a certain value, but don't want to restrict the SELECT to JUST those rows...

You'd do it using SUM() with a clause in, like this instead of using COUNT(): e.g.

SELECT SUM(CASE WHEN Position = 'Manager' THEN 1 ELSE 0 END) AS ManagerCount,
    SUM(CASE WHEN Position = 'CEO' THEN 1 ELSE 0 END) AS CEOCount
FROM SomeTable
d219
  • 2,707
  • 5
  • 31
  • 36
AdaTheDev
  • 142,592
  • 28
  • 206
  • 200
24

If using Postgres or SQLite, you can use the Filter clause to improve readability:

SELECT
  COUNT(1) FILTER (WHERE POSITION = 'Manager') AS ManagerCount,
  COUNT(1) FILTER (WHERE POSITION = 'Other') AS OtherCount
FROM ...

BigQuery also has Countif - see the support across different SQL dialects for these features here: https://modern-sql.com/feature/filter

samjewell
  • 1,068
  • 11
  • 20
13

You can also use the Pivot Keyword if you are using SQL 2005 or above

more info and from Technet

SELECT *
FROM @Users
PIVOT (
    COUNT(Position)
    FOR Position
    IN (Manager, CEO, Employee)
) as p

Test Data Set

DECLARE @Users TABLE (Position VARCHAR(10))
INSERT INTO @Users (Position) VALUES('Manager')
INSERT INTO @Users (Position) VALUES('Manager')
INSERT INTO @Users (Position) VALUES('Manager')
INSERT INTO @Users (Position) VALUES('CEO')
INSERT INTO @Users (Position) VALUES('Employee')
INSERT INTO @Users (Position) VALUES('Employee')
INSERT INTO @Users (Position) VALUES('Employee')
INSERT INTO @Users (Position) VALUES('Employee')
INSERT INTO @Users (Position) VALUES('Employee')
INSERT INTO @Users (Position) VALUES('Employee')
Matthew Whited
  • 22,160
  • 4
  • 52
  • 69
7

Do you mean just this:

SELECT Count(*) FROM YourTable WHERE Position = 'Manager'

If so, then yup that works!

Dana
  • 32,083
  • 17
  • 62
  • 73
5

I know this is really old, but I like the NULLIF trick for such scenarios, and I found no downsides so far. Just see my copy&pasteable example, which is not very practical though, but demonstrates how to use it.

NULLIF might give you a small negative impact on performance, but I guess it should still be faster than subqueries.

DECLARE @tbl TABLE ( id [int] NOT NULL, field [varchar](50) NOT NULL)

INSERT INTO @tbl (id, field)
SELECT 1, 'Manager'
UNION SELECT 2, 'Manager'
UNION SELECT 3, 'Customer'
UNION SELECT 4, 'Boss'
UNION SELECT 5, 'Intern'
UNION SELECT 6, 'Customer'
UNION SELECT 7, 'Customer'
UNION SELECT 8, 'Wife'
UNION SELECT 9, 'Son'

SELECT * FROM @tbl

SELECT 
    COUNT(1) AS [total]
    ,COUNT(1) - COUNT(NULLIF([field], 'Manager')) AS [Managers]
    ,COUNT(NULLIF([field], 'Manager')) AS [NotManagers]
    ,(COUNT(1) - COUNT(NULLIF([field], 'Wife'))) + (COUNT(1) - COUNT(NULLIF([field], 'Son'))) AS [Family]
FROM @tbl

Comments appreciated :-)

z00l
  • 895
  • 11
  • 21
2

Here is what I did to get a data set that included both the total and the number that met the criteria, within each shipping container. That let me answer the question "How many shipping containers have more than X% items over size 51"

select
   Schedule,
   PackageNum,
   COUNT (UniqueID) as Total,
   SUM (
   case
      when
         Size > 51 
      then
         1 
      else
         0 
   end
) as NumOverSize 
from
   Inventory 
where
   customer like '%PEPSI%' 
group by
   Schedule, PackageNum
4b0
  • 21,981
  • 30
  • 95
  • 142
user3029478
  • 179
  • 1
  • 2
1

Note with PrestoDB SQL (from Facebook), there is a shortcut:

https://prestodb.io/docs/current/functions/aggregate.html

count_if(x) → bigint

Returns the number of TRUE input values. This function is equivalent to count(CASE WHEN x THEN 1 END)

Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
0
SELECT COUNT(*) FROM bla WHERE Position = 'Manager'
Peter
  • 37,042
  • 39
  • 142
  • 198
0

In MySQL, boolean expressions evaluate to 0 or 1, so the following aggregation works:

select sum(Position = 'Manager') as ManagerCount
from ...
Aristide
  • 3,606
  • 2
  • 30
  • 50
-1

I think you can use a simple WHERE clause to select only the count some record.

NawaMan
  • 901
  • 5
  • 12
  • Why do I get a down vote? After I answered (or may be the same time), many people answered the similar thing and they do not get any downvote. /:( – NawaMan Sep 09 '09 at 14:50
  • 4
    You get a downvote because the question is "specify condition in Count" NOT "Count values by condition". So you are answering the wrong question – Radon8472 Jul 08 '15 at 07:41
  • 3
    Its a bit unfair to downvote the answer, when the answer was written it was a correct solution to the question.... he added the extra text 4 minutes after this answer! – Peter Oct 20 '16 at 08:01