17

I want to count male, female and total students from Student table for a specific year specified. I wish if the result could be displayed in the form:

====================================
| Label    |  Value   |   Year     |
====================================
| Male     |   0      |   2013     |
| Female   |  23      |   2013     |
| Total    |  23      |   2013     |
====================================

The query should display 0 if there is no male/female matching for the specified year. Any idea how I can make this happen?

Thanks in advance

aby
  • 810
  • 6
  • 21
  • 36

14 Answers14

31

Consider the following query:

select
  max(registeredYear) as year,
  count(case when gender='Male' then 1 end) as male_cnt,
  count(case when gender='Female' then 1 end) as female_cnt,
  count(*) as total_cnt
from student
where registeredYear = 2013
group by registeredYear;

The result will be like this:

Year male_cnt female_cnt total_cnt
---- -------- ---------- ---------
2013        0         23        23

You can transform this result into the form you want. If you want to do it within a query, then you can do it like this:

with t as (
    select
      max(registeredYear) as year,
      count(case when gender='Male' then 1 end) as male_cnt,
      count(case when gender='Female' then 1 end) as female_cnt,
      count(*) as total_cnt
    from student
    where registeredYear = 2013
    group by registeredYear)
select 'Male', male_cnt as male, year from t
union all
select 'Female', female_cnt as male, year from t
union all
select 'Total', total_cnt as male, year from t
;
ntalbs
  • 28,700
  • 8
  • 66
  • 83
  • Thanks for the response. But, it won't return anything when I change the year to, say 2014, where there is no data. I was expecting 2014|0|0|0 – aby Jun 15 '13 at 01:08
  • @aby `count()` function will always return number. In the case that there's no data that satisfies the where conditions, then it will return `0`. But the year part will be empty... – ntalbs Jun 15 '13 at 01:20
  • @aby Perhaps you can use `coalesce()` function and bind variable like `max(coalesce(registeredYear, :inputYear))`... where `:input_year` is bind variable you should specify. I'm not sure how SQL Server uses bind variable, but you would know that. – ntalbs Jun 15 '13 at 01:27
5

You should use:

select name, COUNT(*)as tot, 
  COUNT(case when details.gender='male' then 1 end) as male,
  COUNT(case when details.gender='female' then 1 end) as female 
  from details  group by name
Marcin Nabiałek
  • 109,655
  • 42
  • 258
  • 291
Sudhagar VJ
  • 51
  • 1
  • 1
2

Since you shouldn't mix grid formatting with data retrieval

SELECT
  SUM(CASE WHEN gender = 'Male' THEN 1 ELSE 0 END) as MaleCount,
  SUM(CASE WHEN gender = 'Female' THEN 1 ELSE 0 END) as FemaleCount,
  COUNT(*) as TotalCount
FROM student
WHERE registeredYear = 2013
Community
  • 1
  • 1
Amy B
  • 108,202
  • 21
  • 135
  • 185
1

something like this:

select 'Male' as Label, count(gender) as Value from student where gender= 'Male'
union (
select 'Female' as Label, count(gender) as Value from student where gender= 'Female' )
union (
select 'Total' as Label, count(gender) as Value from student )
Axel Amthor
  • 10,980
  • 1
  • 25
  • 44
1

I believe this is about as efficient as you can get with just a single pass through the student table. Simply change the year in the year CTE as needed.

with
year as 
(
  select '2013' year
),
gender as (
  select 'Male' gender
  union all
  select 'Female' gender
)
select coalesce(g.gender,'Total') "Label", 
       count(s.gender) "Value", 
       y.year "Year"
  from gender g
  cross join year y
  left join student s
    on s.gender = g.gender
   and s.year = y.year
 group by grouping sets( (g.gender, y.year), (y.year) )
 order by case g.gender when 'Male' then 1 when 'Female' then 2 else 3 end
;

A fully normalized data model will likely have both a school year and gender table, so the CTEs would not be needed. (unless you really want to return rows for years that haven't any data)

Here is is a bare-bones sqlfiddle demonstration without student id and name as they are extraneous to the problem at hand.

dbenham
  • 127,446
  • 28
  • 251
  • 390
1

Your request seems very simple, but it has two complications. The first is that one row is a summary of the other two. This suggests using rollup or grouping sets in the query.

The second is the requirement to have values even when you have no data. This suggests the use of a "driver" subquery. Such a subquery defines all the rows in the output before assigning values. You use a driver table with left outer join.

An unstated requirement might be to only mention the year once.

The following approach to the query puts the final form together for the year. The then left joins the summary, pulling values from there if any:

with year as (
      select 2013 as Year
     )
select driver.label, coalesce(s.value, 0) as Value, driver.Year
from ((select 'Male' as label, year from year
      ) union all
      (select 'Female' as label, year from year
      ) union all
      (select 'Total' as label, year from year
      )
     ) driver left outer join
     (select coalesce(Gender, 'Total') as Gender, year.year, count(*) as value
      from Students cross join year
      group by Gender with Rollup
     ) s
     on driver.year = s.year;

This assumes that gender is represented as "Male" and "Female" and that there is a column called year in the data (without sample input or table formats one has to guess on column names and sample values).

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • I like your explanation, but your implementation is bugged and I think overly complicated. The "s" sub-query is missing year from group by and total is incorrectly including all years. The outer join condition driver.gender doesn't exist (should be driver.label), and Total row will never match on year (2013 vs null). See [my earlier answer](http://stackoverflow.com/a/17119899/1012053) for a simpler working query. – dbenham Jun 15 '13 at 12:22
  • @dbenham . . . When I looked through the answers, I was looking for one that had `union` and `rollup`, and must have missed yours because of the "grouping ses". It seemed that none of the answers were addressing the right concerns, which is why I answered (and then tried to simplify the answer resulting in the problems you note). I've duly upvoted your answer, because that is the appropriate approach. – Gordon Linoff Jun 15 '13 at 22:18
0

Just run this query...

SELECT 
     MAX(registeredYear) as Year
    ,SUM(CASE WHEN gender = 'Male' THEN 1 END) AS Male
    ,SUM(CASE WHEN gender = 'Female' THEN 1 END) AS Female
    ,SUM(CASE WHEN gender IS NOT NULL THEN 1 ELSE 0 END) AS Total
 FROM from student
 WHERE registeredYear = 2013
 GROUP BY registeredYear;
Kylie
  • 11,421
  • 11
  • 47
  • 78
0

Try this, assuming no nulls in Gender or RegisteredYear:

WITH AllYears AS
(
   SELECT RegisteredYear
   FROM Student
   GROUP BY RegisteredYear
)

, AllGenders AS
(
   SELECT Gender
   FROM Student
   GROUP BY Gender
)    

, AllGendersAndYears AS
(
   SELECT Gender, RegisteredYear
   FROM AllGenders, AllYears
)

SELECT Gender, RegisteredYear, CountForGenderAndYear
FROM AllGendersAndYears 
   CROSS APPLY 
   (
      SELECT COUNT(*) AS CountForGenderAndYear
      FROM Student
      WHERE Student.Gender = AllGendersAndYears.Gender
         AND Student.RegisteredYear = AllGendersAndYears.RegisteredYear
   ) countForGenderAndYear

UNION ALL

SELECT 'Total', AllYears.RegisteredYear, CountForYear
FROM AllYears
   CROSS APPLY 
   (
      SELECT COUNT(*) AS CountForYear
      FROM Student
      WHERE Student.RegisteredYear = AllYears.RegisteredYear
   ) countForYear
Mark Sowul
  • 10,244
  • 1
  • 45
  • 51
  • Oh, this gets a summary for each year in the table. You want a specific year. To do that, replace AllYears as `AllYears AS (SELECT 2014 AS RegisteredYear)` – Mark Sowul Jun 15 '13 at 01:17
  • Thanks, your solution is so close. But what should I change so that Female|0|2014, Male|0|2014, Total|0|2014 will be returned when there is no data for the selected year to be returned? – aby Jun 15 '13 at 01:26
  • Ah, I had earlier made the same mistake with the gender/year counts (it returned '1' if none) but I have now resolved it the same way. – Mark Sowul Jun 15 '13 at 01:42
0

Here's another variation, using UNPIVOT. This one specifically searches for only MALE and FEMALE so it's not as flexible as my other one (since you need to hardcode each gender). But it is probably the most efficient.

WITH AllYears (RegisteredYear) AS
(
    --SELECT DISTINCT RegisteredYear
    --FROM Student

     --...OR...

    SELECT 2014
)
, GenderAndYearCounts AS
(
    SELECT RegisteredYear
        , SUM(CASE Gender WHEN 'MALE' THEN 1 ELSE 0 END) MaleCount
        , SUM(CASE Gender WHEN 'FEMALE' THEN 1 ELSE 0 END) FemaleCount
        , COUNT(*) YearCount
    FROM Student
    GROUP BY RegisteredYear
)
, GenderAndYearCountsForAllYears AS
(
    SELECT AllYears.RegisteredYear
        , ISNULL(MaleCount, 0) AS MaleCount
        , ISNULL(FemaleCount, 0) AS FemaleCount
        , ISNULL(YearCount, 0) AS YearCount
    FROM AllYears
        LEFT JOIN GenderAndYearCounts ON GenderAndYearCounts.RegisteredYear = AllYears.RegisteredYear
)

SELECT Label, Value, RegisteredYear
FROM 
(
    SELECT RegisteredYear, MaleCount AS Male, FemaleCount AS Female, YearCount AS Total
    FROM GenderAndYearCountsForAllYears
) allCounts

UNPIVOT
(
    Value FOR Label IN (Male, Female, Total)
) unpivotted
Mark Sowul
  • 10,244
  • 1
  • 45
  • 51
0

All the genders, then all the years, then the counts:

declare @Year int
set @Year = 2014

select labels.label,
    counts.cnt,
    @Year as registeredYear
    from 
        (select 'Male' as label, 1 as sortOrder
        union all 
        select 'Female', 2
        union all
        select 'All', 3) as labels 
   left join
       (select gender, 
        count(1) cnt
        from student
        where registeredYear = @Year
        group by gender) as counts
    on labels.label = counts.gender     
    order by labels.sortOrder
JBrooks
  • 9,901
  • 2
  • 28
  • 32
0

This worked for me. But, still it couldn't display 0 for both M and F for years where there is no data :

Select * from 
(
SELECT isnull (SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END),0) as Male,
       isnull(SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END),0) as Female,
       registeredYear as 'year'

FROM student
WHERE registeredDate.Year = 2013 //could be a variable
group by registeredYear
 ) as s

 UNPIVOT
 ( 
value FOR gender IN (Male, Female) 
 ) Sub
aby
  • 810
  • 6
  • 21
  • 36
0
select sp.CLASS_Name , count(*) as total 
     , sum( case when si.STDNT_GENDER = 1   then 1 else 0 end )    as Male
     , sum( case when si.STDNT_GENDER = 0   then 1 else 0 end )    as Female
  from SCHOOL_PLANE sp  inner join STUDENT_INFO si 
on  sp.CLASS_ID=si.STDNT_CLASS_PLANE_ID group by sp.CLASS_Name

-------
select sp.CLASS_Name , count(*) as total 
     , sum( case si.STDNT_GENDER  when  1   then 1 else 0 end )    as Male
     , sum( case si.STDNT_GENDER  when  0   then 1 else 0 end )    as Female
  from SCHOOL_PLANE sp  inner join STUDENT_INFO si 
on  sp.CLASS_ID=si.STDNT_CLASS_PLANE_ID group by sp.CLASS_Name
------------
select sp.CLASS_Name , count(*) as total 
     , count( case when si.STDNT_GENDER = 1   then 1  end )    as Male
     , count( case when si.STDNT_GENDER = 0   then 1  end )    as Female
  from SCHOOL_PLANE sp  inner join STUDENT_INFO si 
on  sp.CLASS_ID=si.STDNT_CLASS_PLANE_ID group by sp.CLASS_Name
Rae Lee
  • 1,321
  • 10
  • 11
0
SELECT 
  (SELECT count(*) FROM patients WHERE gender='M') AS male_count, 
  (SELECT count(*) FROM patients WHERE gender='F') AS female_count;
buddemat
  • 4,552
  • 14
  • 29
  • 49
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 11 '22 at 14:45
-1

Select * from (select year, count(*) as total ,sum( case when gender ='M' then 1 else 0 end ) as male , sum ( case when gender ='F' then 1 else 0 end) as female from mytable where year=2013 group by gender,year)
unpivot ( income_component_value for income_component_type in ( male,female,total ) )

  • 2
    Welcome to Stack Overflow. Answering questions here is good but this is just a lump of unformatted code. A good answer should be neatly formatted and include an explanation of how its works and how it solves the original question. An answer to a question this old and with this many answers should also add new insights into the topic. Please [edit] your answer to improve it. – AdrianHHH Sep 09 '19 at 21:32