SQL Count on Multiple Fields - mysql

I'm not even sure if this is possible or whether I just need to write multiple queries.
I have a database table as follows:-
USER
university_id
gender
The output I'd like to achieve is as follows
| Uni | Total Users | Male | Female
| Uni1 | 30 | 15 | 15
| Uni2 | 40 | 25 | 15
I would be grateful if you could let me know if this is possible to achieve in a single queries, or whether I should just use different queries for each result.
Thank you in advance.
Phill

Here's how you can do it in a single query:
select
UniversityId,
count(*) as `Total Users`,
sum(case when gender = 'male' then 1 else 0 end) as Male,
sum(case when gender = 'female' then 1 else 0 end) as Female
from User
group by UniversityId
Demo: http://www.sqlfiddle.com/#!2/e7d16a/4

Related

Outputting four rows based on two categories each with a boolean

If I have a table of users:
USER ID | GENDER | VOTE
----------------------------------
1 Male Yes
2 Female No
3 Male No
4 Male Yes
5 Female Yes
How can I write a query that outputs the genders by their vote breakdown:
GENDER | YES | NO
----------------------------------
Male 2 1
Female 1 1
Is this possible to do it in SQL or should I use PHP to build these totals?
Any help is appreciated! :)
This type of transformation of data is known as pivot. MySQL doesn't have a PIVOT function but you can replicate the function by using an aggregate function with a CASE expression:
select gender,
sum(case when vote = 'Yes' then 1 else 0 end) Yes,
sum(case when vote = 'No' then 1 else 0 end) No
from yourtable
group by gender;
See SQL Fiddle with Demo. Since you are using MySQL you could also use the IF() control-flow operator:
select gender,
sum(if(vote = 'Yes', 1, 0)) Yes,
sum(if(vote = 'No', 1, 0)) No
from yourtable
group by gender;
See SQL Fiddle with Demo. These both give a result:
| GENDER | YES | NO |
|--------|-----|----|
| Female | 1 | 1 |
| Male | 2 | 1 |

How to pivot a table in MySQL using case statements?

I am trying to pivot a table in MySQL using case statements. This question has been asked many times here, and I have studied all of those answers, but I am looking for a solution that:
1. Uses case statements. Not self joins, subqueries, or unions.
2. Uses just SQL. Not Excel or shell scripts.
3. Works on MySQL.
Here is the table:
create table client (
name varchar(10),
revenue int(11),
expense int(11)
);
insert into client (name, revenue, expense) values ("Joe", 100, 200);
insert into client (name, revenue, expense) values ("Bill", 300, 400);
insert into client (name, revenue, expense) values ("Tim", 500, 600);
mysql> select * from client;
+------+---------+---------+
| name | revenue | expense |
+------+---------+---------+
| Joe | 100 | 200 |
| Bill | 300 | 400 |
| Tim | 500 | 600 |
+------+---------+---------+
I would like to pivot the table to this:
+-----+------+-----+
| Joe | Bill | Tim |
| 100 | 300 | 500 |
| 200 | 400 | 600 |
+-----+------+-----+
How can I accomplish this?
I have already seen the solutions at artfulsoftware dot com and buysql dot com, but those solutions are not working for my table.
see fiddle demo here
select
sum(case when name='Joe' then revenue else 0 end) as JOE,
sum(case when name='Bill' then revenue else 0 end) as Bill,
sum(case when name='Tim' then revenue else 0 end) as TIM
from client
union
select
sum(case when name='Joe' then expense else 0 end) as JOE,
sum(case when name='Bill' then expense else 0 end) as Bill,
sum(case when name='Tim' then expense else 0 end) as TIM
from client

mysql sum() two joined tables, multiplies result

i have two tables; invoices & invoiceitems.
invoiceitems contains the items on each invoice
eg:
invoices
----------------------------------
| id |status| net | tax | total |
----------------------------------
| 72 |paid | 100 | 120 | 220 |
| 73 |unpaid| 50 | 5 | 55 |
| 74 |paid | 400 | 45 | 445 |
| 75 |paid | 250 | 67 | 317 |
invoiceitems
-------------------------------
| invoiceid |itemdescription |
-------------------------------
| 72 | apples |
| 72 | pears |
| 72 | oranges |
| 73 | lemons |
| 73 | oranges |
as you can see, in the example invoice number 72 has 3 items
i want to search my invoices for certain things, and display a count of certain fields.
but my problem is that the sum value seems to get multiplied by the number of fields there are in the second table.
$sql = "SELECT COUNT(DISTINCT invoices.id) AS num,
SUM(CASE invoices.status WHEN 'Paid' THEN 1 ELSE 0 END) AS numpaid,
SUM(CASE invoices.status WHEN 'Paid' THEN invoices.total ELSE 0 END) AS sumtotal,
FROM invoices
LEFT JOIN invoiceitems ON invoices.id=invoiceitems.invoiceid
WHERE invoices.id LIKE :invoiceid
AND IFNULL(opcinvoiceitems.itemdescription, '') LIKE :itemdescription
AND invoices.net LIKE :net
AND invoices.tax LIKE :tax
AND invoices.total LIKE :total
AND ......"
so using the above, the total for invoice 72 would be multiplied by 3
i'm really sorry, i know this is really badly explained but i cant explain it any other way, been searching for ages but cant find a solution. hope someone can help. thanks
One way to do what you want is to pre-aggregate the invoiceItems table before joining:
SELECT COUNT(i.id) AS num,
SUM(CASE i.status WHEN 'Paid' THEN 1 ELSE 0 END) AS numpaid,
SUM(CASE i.status WHEN 'Paid' THEN i.total ELSE 0 END) AS sumtotal,
FROM invoices i LEFT JOIN
(select ii.invoiceid, sum(. . .) as . . .
from invoiceitems ii
where IFNULL(ii.itemdescription, '') LIKE :itemdescription AND
group by ii.invoiceid
) ii
ON i.id = ii.invoiceid
WHERE i.id LIKE :invoiceid AND
i.net LIKE :net AND
i.tax LIKE :tax AND
i.total LIKE :total AND .....
Your query doesn't actually use invoiceitems in the from clause, so it is hard to provide a more detailed example.
When you do a join, you produce records created by matching up ones from the original tables. Thus, you will have 3 records for invoice #72, each created by matching up the single invoices record for #72 with each of the invoice items for #72. Each combined record will have the same total (in this case, 220), and thus the sum would be 3 times that.
It sounds like you just want total, then; you could just use total directly, or you could take your sum and divide it by the count (which you appear to also be computing).

mysql 3 queries on 2 tables in one

I have 2 tables that I need to query
**tbl_jobs**
jobid | description | someinfo
1 foo bar
2 fuu buu
**tbl_invlog**
idinv | jobid | type | ammount
1 1 add 100
2 1 rem 50
3 1 rem 15
4 1 add 8
5 2 add 42
the result should be to make a sum of the inventory "add" and "rem" and give a total of sum(add)-sum(rem) for each jobid, including the rest of the job information.
jobid | description | someinfo | amountadd | amountrem | totaladdrem
1 | foo | bar | 108 | 65 | 43
2 | fuu | buu | 42 | 0 | 42
i have made a quadruple select statement with select * from (select .... ) without using joins or other cool stuff. which is terribly slow. I am quite new to mysql.
I would be glad to an idea on how to solve this.
thanks in advance
This is a query that requires a join and conditional aggregation:
select j.jobid, j.description, j.someinfo,
sum(case when il."type" = 'add' then amount else 0 end) as AmountAdd,
sum(case when il."type" = 'rem' then amount else 0 end) as AmountRem,
(sum(case when il."type" = 'add' then amount else 0 end) -
sum(case when il."type" = 'rem' then amount else 0 end)
) as totaladdrem
from tbl_jobs j left outer join
tbl_invlog il
on j.jobid = il.jobid
group by j.jobid, j.description, j.someinfo;
Note some things. First, the tables have table aliases, defined in the from clause. This allows you to say which table the columns come from. Second, the table aliases are always used for all columns in the query.
MySQL would allow you to just do group by j.jobid, using a feature called "hidden columns". I think this is a bad habit (except in a few cases), so this aggregates by all the columns in the jobs table.
The conditional aggregation is done by putting a condition in the sum() statement.

Getting conditional counts on to the same row in MySQL / SQL

Suppose I have a table, Foo, that looks like this:
ID | Name | Gender | Team
1 | Bob | Male | A
2 | Amy | Female | A
3 | Cat | Female | B
4 | Dave | Male | B
5 | Evan | Male | B
If I wanted to get a list of the number of males and females per team on the same row, how would I do that?
I know I could do SELECT COUNT(Name) as "#", Team, Gender FROM foo GROUP BY Team, Gender, and that's fine for most purpose.
But that would give me 2 rows per team, like below, and that can be a pain.
# Team Gender
1 | A | Male
1 | A | Female
1 | B | Female
2 | B | Male
How could I structure the query such that they appear on the same row?
ie,
Team | Males | Females
A | 1 | 1
B | 2 | 1
select team,
SUM(case when gender='Male' then 1 else 0 end) Male,
SUM(case when gender='Female' then 1 else 0 end) Female
from tbl
group by team
For the comment
Imagine now that each row has an arbitrary numeric score associated with it, in a Score column. How would I have 'Male points' and 'Female points? Would that be SUM(case when gender="male" then select points else 0 end) "Male Points"
You're close. The answer is
select team,
SUM(case when gender='Male' then 1 else 0 end) Male,
SUM(case when gender='Male' then points else 0 end) `Male Points`,
SUM(case when gender='Female' then 1 else 0 end) Female,
SUM(case when gender='Female' then points else 0 end) `Female Points`
from tbl
group by team
The pattern I was looking for was a Self-Join; the syntax and logic is, in my mind, more elegant then the CASE pattern.
Specifically,
SELECT Males.Team, COUNT(Males.id) as "Males", SUM(Males.Points) as "Male Points",
COUNT(Females.id) as "Females", SUM(Females.Points) as "Female Points"
FROM scores as Males
LEFT JOIN scores as Females ON Males.Team=Females.Team AND Females.Gender="Female"
WHERE Males.Gender="Male"
GROUP BY Team
Instead of case statements, the groupings I want in different columns get split into their own copies of the same table. You then join the table of Male players with a Table of Female players on the Team, and then group by the Team.