Is it possible to have custom GROUP BY for MySQL query? - mysql

I have been trying to find the simplest way to group the two age groups in my query. Is it possible for something like this to work?
SELECT age,
sum(case when age < '20' then 1 else 0 end),
sum(case when age > '20' then 1 else 0 end)
FROM Contact
GROUP BY ...."custom group one"......"custom group one".....??
I know you should group on a column usually, but in my case I that doesn't work. Any suggestions? Thx!
Table: Desired Query Result:
Name Age 0 1
John 18 Under 20 2
Harry 22 Over 20 2
Mary 17
Megan 27
SOLVED:
SELECT CASE
WHEN age = '21' THEN 'young'
WHEN age BETWEEN '22' AND '60' THEN 'middle'
ELSE 'old'
END, Count(id)
FROM Contact
GROUP BY CASE
WHEN age = '21' THEN 'young'
WHEN age BETWEEN '22' AND '60' THEN 'middle'
ELSE 'old'
END
Note: AS can be used to assign alias to grouping conditions in SELECT statement and hence avoid repeating conditions twice, i.e.
SELECT CASE
WHEN age = '21' THEN 'young'
WHEN age BETWEEN '22' AND '60' THEN 'middle'
ELSE 'old'
END AS age_range, Count(id)
FROM Contact
GROUP BY age_range

So that will be:
SELECT
COUNT(1),
age>20 AS Above20
FROM t
GROUP BY age>20
-check this fiddle.
Or, alternatively, with SUM() and with column view:
SELECT
SUM(IF(age>20, 1, 0)) AS Above20,
SUM(IF(age<=20, 1, 0)) AS Below20
FROM
t
-check this fiddle.

Related

MySQL subtract from SUM in CASE statement

I have an SQL code.
SELECT DISTINCT SQL_CALC_FOUND_ROWS
sales.code as code,
SUM(CASE WHEN sales.type = 51 THEN sales.amount ELSE 0 END) AS amount,
sales.type as type
FROM sales
Now the thing is that I need to subtract the sales.amount from the total SUM IF the sales.type != 51 AND sales.amount > 0. How could I do it? I guess I need another CASE statement inside the first one after ELSE statement, but I don't know how this works.
As example if the type is not 51 and it has sales.amount bigger than 0, then I should subtract it from the amount.
Thanks.
Where is this select distinct coming from? Use GROUP BY:
SELECT s.code as code,
SUM(CASE WHEN s.type = 51 THEN s.amount ELSE 0 END) AS amount
FROM sales s
GROUP BY s.code;
Then the answer to your question is to change the ELSE clause:
SELECT s.code as code,
SUM(CASE WHEN s.type = 51 THEN s.amount ELSE - s.amount
END) AS amount
FROM sales s
GROUP BY s.code;

Return 0 for SQL count when CASE does not exist

I have two tables Log and Player. Where Log stores each game play log with a playerId and date. and the Player table has the Players info as Age and Gender ..etc. I'm writing an SQL stored procedure that takes two dates and will count the LogId and group by age range and gender between the two dates. but when i run the SQL procedure, it doesn't show all the Age_Range/Gender entries when no player exists in that period.I'm trying to get all Age_Range/Gender entries with the their actual count or count= 0 if they don't exist.
I've even tried changing
count(L.LogId) as Count,
To
count(IFNULL(L.LogId, 0)) as Count,
My SQL procedure is :
CREATE PROCEDURE `SHOW_AGE_RANGE` (IN date1 CHAR(10), IN date2 CHAR(10))
BEGIN
SELECT
count(L.LogId) as Count,
CASE
WHEN P.age BETWEEN 13 AND 18 THEN '13-18'
WHEN P.age BETWEEN 19 AND 25 THEN '19-25'
WHEN P.age BETWEEN 26 AND 39 THEN '26-39'
WHEN P.age BETWEEN 40 AND 59 THEN '40-59'
WHEN P.age > 59 THEN '60+'
END as Age_Range,
CASE
WHEN P.gender = 0 then 'Female'
WHEN P.gender = 1 then 'Male'
END as Gender
FROM Log L
LEFT JOIN Player P ON L.playerId = P.playerId
WHERE CAST(L.createdDate AS DATE) BETWEEN CAST(date1 AS DATE) AND CAST(date2 AS DATE)
GROUP BY Age_Range, Gender;
END
Output of the SQL Procedure:
Count Age_Range Gender
----------------------------------
'1' '13-18' 'Male'
'1' '19-25' 'Female'
'3' '26-39' 'Female'
'2' '40-59' 'Male'
'1' '60+' 'Female'
The Expected Output
Count Age_Range Gender
----------------------------------
'0' '13-18' 'Female'
'1' '13-18' 'Male'
'1' '19-25' 'Female'
'0' '19-25' 'Male'
'3' '26-39' 'Female'
'0' '26-39' 'Male'
'0' '40-59' 'Female'
'2' '40-59' 'Male'
'1' '60+' 'Female'
'0' '60+' 'Male'
You need to start out with your age ranges and genders. Really, you're querying off of those and the player data is just something that you're adding to them. Since you don't already have the age ranges in a table (you should probably consider adding that) then you'll need to create a virtual table for those as a subquery.
SELECT
COUNT(L.playerId) AS cnt,
AG.age_range,
G.gender
FROM
(
SELECT 13 AS min_age, 18 AS max_age, '13-18' AS age_range
UNION ALL
SELECT 19 AS min_age, 25 AS max_age, '19-' AS age_range
UNION ALL
SELECT 26 AS min_age, 39 AS max_age, '26-39' AS age_range
UNION ALL
SELECT 40 AS min_age, 59 AS max_age, '40-59' AS age_range
UNION ALL
SELECT 60 AS min_age, 999 AS max_age, '60+' AS age_range
) AS AG
CROSS JOIN
(
SELECT 0 AS gender_value, 'Female' AS gender
UNION ALL
SELECT 1 AS gender_value, 'Male' AS gender
) AS G
LEFT JOIN Player P ON
P.age BETWEEN AG.min_age AND AG.max_age AND
P.gender = G.gender_value
LEFT OUTER JOIN Log L ON
L.playerId = P.playerId AND
CAST(L.createdDate AS DATE) BETWEEN CAST(date1 AS DATE) AND CAST(date2 AS DATE)
GROUP BY
AG.age_range, G.gender
It looks like you want the outer join to the Log table. Swap the Player and Log tables, and relocate the predicate on the createdDate column to the ON clause.
Like this:
FROM Player P
LEFT
JOIN Log L
ON L.playerId = P.playerId
AND CAST(L.createdDate AS DATE) BETWEEN CAST(date1 AS DATE) AND CAST(date2 AS DATE)
GROUP BY Age_Range, Gender;

SQL using CASE in count and group by

I'm using CASE to categorize data in the table and count them but the results aren't accurate
live demo [here]
select DATE(date) as day, count(*),
count(distinct case when name = 'fruit' then 1 else 0 end) as fruits,
count(distinct case when name = 'vege' then 1 else 0 end) as vege,
count(distinct case when name = 'sweets' then 1 else 0 end) as sweets
from food
group by day
with rollup
I'm not sure if the issue is with CASE or in the string matching = because there's no 'sweets' still it counts 1?
any pointers I'd be grateful
Your problem is that COUNT counts every result that is not NULL. In your case you are using:
COUNT(distinct case when name = 'sweets' then 1 else 0 end)
So, when the name is not sweets, it counts the 0. Furthermore, since you are using DISTINCT, it counts just one or two values. You should either use SUM or remove the DISTINCT and the ELSE 0:
SELECT DATE(date) as day,
COUNT(*),
SUM(CASE WHEN name = 'fruit' THEN 1 ELSE 0 END) as fruits,
SUM(CASE WHEN name = 'vege' THEN 1 ELSE 0 END) as vege,
SUM(CASE WHEN name = 'sweets' THEN 1 ELSE 0 END) as sweets
FROM food
GROUP BY DAY
WITH ROLLUP
Or:
SELECT DATE(date) as day,
COUNT(*),
COUNT(CASE WHEN name = 'fruit' THEN 1 ELSE NULL END) as fruits,
COUNT(CASE WHEN name = 'vege' THEN 1 ELSE NULL END) as vege,
COUNT(CASE WHEN name = 'sweets' THEN 1 ELSE NULL END) as sweets
FROM food
GROUP BY DAY
WITH ROLLUP
Here is a modified sqlfiddle.
You can't group by an alias. You have to group by the expression.
group by date(date)
You can group on an Alias:
SELECT
FROM_UNIXTIME(UnixTimeField, '%Y') AS 'Year'
,FROM_UNIXTIME(UnixTimeField, '%m') AS 'Month'
FROM table p
GROUP BY Year, Month

Mysql case when and order by issue

Have this query:
SELECT
count(*) as Total,
SUM(CASE WHEN gender = 1 then 1 ELSE 0 END) Male,
SUM(CASE WHEN gender = 2 then 1 ELSE 0 END) Female,
SUM(CASE WHEN gender = 0 then 1 ELSE 0 END) Unknown,
CASE
WHEN age>2 AND age<15 THEN '2-15'
WHEN age>18 AND age<25 THEN '18-25'
END AS var
FROM
persons
WHERE
1=1
AND `date` > '2012-01-10'
AND `date` < '2013-01-07'
GROUP BY
CASE
WHEN age>2 AND age<15 THEN '2-15'
WHEN age>18 AND age<25 THEN '18-25'
END
And is resulting this:
Total Male Female Unknown var
29 17 12 0 NULL
7 0 7 0 18-25
3 0 3 0 2-15
1st question: Why is this resulting that NULL ? What could be done to only show results with values?
2nd question: mysql is ordering my var column with 18-25 before 2-15, migth be because of number 1 cames first then number 2. But the point is order that as numbers, and 2 came first then 18.
Cheers :)
1st answer:
It is NULL because it does not satisfy any of your CASE conditions for the age. Adding a clause to the WHERE like this should do it:
WHERE (age > 2 AND age < 15) OR (age > 18 AND age < 25)
2nd answer:
You are correct, it is ordering them by strings (because that is what they are). Just change the direction of the sort by doing ORDER ASC or ORDER DESC
This is because all CASE expression has an (implied, default) ELSE NULL part. SO, any age value that is not caught by either the age>2 AND age<15or the age>18 AND age<25 condition, results in the NULL value being grouped.
Solution is to add one more restriction at the WHERE clause:
WHERE 1=1
AND `date` > '2012-01-10' AND `date` < '2013-01-07'
AND ( (age>2 AND age<15) OR (age>18 AND age<25) ) -- this
For the second question, you can use a function on age to avoid the comparison being made on the var (which is a string):
ORDER BY MIN(age)
or just:
ORDER BY age
None of the above is by the SQL standard but it works in MySQL, under the default non-ANSI settings. If you want to be 100% by the book, you can change slightly the var:
SELECT count(*) as Total,
SUM(CASE WHEN gender = 1 then 1 ELSE 0 END) Male,
SUM(CASE WHEN gender = 2 then 1 ELSE 0 END) Female,
SUM(CASE WHEN gender = 0 then 1 ELSE 0 END) Unknown,
CASE
WHEN age>2 AND age<15 THEN '02-15' -- this was changed
WHEN age>18 AND age<25 THEN '18-25'
END AS var
FROM persons
WHERE 1=1
AND `date` > '2012-01-10' AND `date` < '2013-01-07'
AND ( (age>2 AND age<15) OR (age>18 AND age<25) )
GROUP BY
CASE
WHEN age>2 AND age<15 THEN '02-15'
WHEN age>18 AND age<25 THEN '18-25'
END
ORDER BY var ;
you are getting NULL
because it doesnt meet your CASE
CASE
WHEN age>2 AND age<15 THEN '2-15' // U HAVE BETWEEN 2-15
WHEN age>18 AND age<25 THEN '18-25' // u have between 18-25
// but u dont have between 15-18
//and u get null because your value is between 15-18
so try to add other case in that range.
second question because they are strings , not numbers.
try order them by age

get count of two table fields in one query

I am trying to get the count of females and males in the gender field of a table.
Is there a way to get the count of each in one query?
Something like:
select * from table count(where gender = 'm') as total_males, count(where gender = 'f') as total_females;
or will it require two queries?
select count(*) from table where gender = 'm';
select count(*) from table where gender = 'f';
This is basically a PIVOT. MySQL does not have a pivot so you can use an aggregate function with a CASE statement to perform this:
select
sum(case when gender = 'm' then 1 else 0 end) Total_Male,
sum(case when gender = 'f' then 1 else 0 end) Total_Female
from yourtable
See SQL Fiddle with Demo
Or using COUNT:
select
count(case when gender = 'm' then 1 else null end) Total_Male,
count(case when gender = 'f' then 1 else null end) Total_Female
from yourtable;
See SQL Fiddle with Demo
Something like this will work:
SELECT SUM(IF(t.gender='m',1,0)) AS total_males
, SUM(IF(t.gender='f',1,0)) AS total_females
FROM mytable t
The "trick" here is that we are using a conditional test to return either a 0 or a 1 for each row, and then adding up the 0's and 1's. To make this a little more clear, I am using the SUM aggregate function rather than COUNT, although COUNT could be used just as easily, though we'd need to return a NULL in place of the zero.
SELECT COUNT(IF(t.gender='m',1,NULL)) AS total_males
, COUNT(IF(t.gender='f',1,NULL)) AS total_females
FROM mytable t
Consider that the two expressions in the SELECT list of this query:
SELECT COUNT(1)
, SUM(1)
FROM mytable t
Will return the same value.
If you want to avoid the MySQL IF function, this can also be done using the ANSI SQL CASE expression:
SELECT SUM( CASE WHEN t.gender = 'm' THEN 1 ELSE 0 END )) AS total_males
, SUM( CASE WHEN t.gender = 'f' THEN 1 ELSE 0 END )) AS total_females
FROM mytable t
select sum(case when gender='m' then 1 else null end) as total_males, sum(case when gender='f' then 1 else null end) as total_females from ...
Should work just fine!
If your only issue is to avoid two queries, you can always write two queries as subselects of one query.
Select (select 1 from dual) as one, (select 2 from dual) as two from dual
This would work for your scenario, too.