Using case when mysql - mysql

I would like to query a total marks column using case and get the marks respective grades and store it as a separate column. I have been trying for a while now, but it keeps giving me errors.
SELECT gradecalc.StudentId,gradecalc.CourseCode, Total,
CASE TOTAL
WHEN >90 THEN 'AA'
WHEN <90 AND >85 THEN 'BA'
WHEN <85 AND >=80 THEN 'BB'
WHEN <80 AND >=75 THEN 'CB'
WHEN <75 AND >=70 THEN 'CC'
WHEN <70 AND >=65 THEN 'DC'
WHEN <65 AND >=60 THEN 'DD'
ELSE 'FF'
END AS Grade
FROM GRADECALC GROUP BY gradecalc.StudentId,gradecalc.CourseCode

The case <column> when <value> syntax can only handle equality checks. For other operators, your'd have to use a slightly different syntax:
SELECT gradecalc.StudentId,gradecalc.CourseCode, Total,
CASE
WHEN total > 90 THEN 'AA'
WHEN total < 90 AND total >= 85 THEN 'BA'
WHEN total < 85 AND total >= 80 THEN 'BB'
WHEN total < 80 AND total >=75 THEN 'CB'
WHEN total < 75 AND total >=70 THEN 'CC'
WHEN total < 70 AND total >=65 THEN 'DC'
WHEN total < 65 AND total >=60 THEN 'DD'
ELSE 'FF'
END AS Grade
FROM GRADECALC GROUP BY gradecalc.StudentId,gradecalc.CourseCode

You need to put the full expression in the case when. Your syntax of case <variable> when <constant> only works for constants.
Now, you can also simplify the expressions, because case is processed in order. So this is equivalent logic:
SELECT gc.StudentId, gc.CourseCode, gc.Total,
(CASE WHEN TOTAL > 90 THEN 'AA'
WHEN TOTAL > 85 THEN 'BA'
WHEN TOTAL >= 80 THEN 'BB'
WHEN TOTAL >= 75 THEN 'CB'
WHEN TOTAL >= 70 THEN 'CC'
WHEN TOTAL >= 65 THEN 'DC'
WHEN TOTAL >= 60 THEN 'DD'
ELSE 'FF'
END) AS Grade
FROM GRADECALC gc
GROUP BY gc.StudentId, gc.CourseCode ;
I don't think the GROUP BY is necessary, unless you have multiple rows for a given student and course. If you do, then you probably want AVG(Total) or something like that in the case statement.
With this formulation, you can readily see that "90" and "85" are suspicious. In fact, your original logic would give students with these marks an "FF", which I'm guessing is not the intention.
So:
SELECT gc.StudentId, gc.CourseCode, gc.Total,
(CASE WHEN TOTAL >= 90 THEN 'AA'
WHEN TOTAL >= 85 THEN 'BA'
WHEN TOTAL >= 80 THEN 'BB'
WHEN TOTAL >= 75 THEN 'CB'
WHEN TOTAL >= 70 THEN 'CC'
WHEN TOTAL >= 65 THEN 'DC'
WHEN TOTAL >= 60 THEN 'DD'
ELSE 'FF'
END) AS Grade
FROM GRADECALC gc;

You can try this, mate:
SELECT
gc.StudentId,
gc.CourseCode,
gc.Total,
CASE
WHEN gc.Total > 90 THEN 'AA'
WHEN gc.Total BETWEEN 85 AND 90 THEN 'BA'
WHEN gc.Total BETWEEN 80 AND 85 THEN 'BB'
WHEN gc.Total BETWEEN 75 AND 80 THEN 'CB'
WHEN gc.Total BETWEEN 70 AND 75 THEN 'CC'
WHEN gc.Total BETWEEN 65 AND 70 THEN 'DC'
WHEN gc.Total BETWEEN 60 AND 65 THEN 'DD'
ELSE 'FF'
END AS Grade
FROM
gradecalc gc
GROUP BY
gc.StudentId, gc.CourseCode;

Related

MySQL getting percentage of count

My Data would like this
Name Similar_Percentage
A 15
B 20
C 65
I want like this
Similar_Percentage count(*)
Less than 20 2
Less than 70 1
How to write a query for this.
I know having make this but it show one by one data.
use case when
select case when Similar_Percentage<20 then 'Less than 20'
when (Similar_Percentage<70) then 'Less than 70' end as Percentage,count(*)
from table group by case when Similar_Percentage<20 then 'Less than 20'
when (Similar_Percentage<70) then 'Less than 70' end
with cte as (
select 15 as Similar_Percentage
union all
select 20
union all
select 65
)select case when Similar_Percentage<=20 then 'Less than 20'
when (Similar_Percentage<70) then 'Less than 70' end as Percentage,count(*)
from cte group by case when Similar_Percentage<=20 then 'Less than 20'
when (Similar_Percentage<70) then 'Less than 70' end
demo link
Percentage count(*)
Less than 20 2
Less than 70 1
Another approach can be like following.
SELECT 'Less than 20' Percentage,
Count(*) [Count(*)]
FROM tablename
WHERE similar_percentage <= 20
UNION ALL
SELECT 'Less than 70' Percentage,
Count(*) [Count(*)]
FROM tablename
WHERE similar_percentage <= 70
AND similar_percentage > 20
Use a case expression in a derived table (the subquery) to do the classification. GROUP BY its result:
select percentage, count(*)
from
(
select case when Similar_Percentage <= 20 then 'Less than 20'
when Similar_Percentage <= 70 then 'Less than 70'
else 'More than 70'
end as percentage
from tablename
) dt
group by percentage
MySQL allows you to use column aliases in the group by. This simplifies the query.
I also recommend an explicit order by, so you get the results in the order you want:
select (case when Similar_Percentage <= 20 then 'Less than 20'
when Similar_Percentage < 70 then 'Less than 70'
end) as Percentage,
count(*)
from cte
group by Percentage
order by min(Similar_Percentage)

How to use multiple group by conditions in mysql

I have a customer_master table. In that table I have two columns called customer_id and date_of_birth.
what I want is get count of customers group by their age ranger. Something like this.
So far this is the only query I could try.
select COUNT(customer_id) AS count FROM customer_master
WHERE (DATEDIFF( CURDATE(),date_of_birth) / 365.25)<40
Please help me out with this. Thank you.
With everyone's help I found a perfect answer than you all.
SELECT CASE
WHEN (DATEDIFF( CURDATE(),STR_TO_DATE(date_of_birth, '%Y-%m-%d')) / 365) <= 20 THEN 'Below 20'
WHEN(DATEDIFF( CURDATE(),STR_TO_DATE(date_of_birth, '%Y-%m-%d')) / 365) <= 30 THEN 'Below 30'
WHEN (DATEDIFF( CURDATE(),STR_TO_DATE(date_of_birth, '%Y-%m-%d')) / 365) <= 40 THEN 'Below 40'
WHEN (DATEDIFF( CURDATE(),STR_TO_DATE(date_of_birth, '%Y-%m-%d')) / 365) <= 50 THEN 'Below 50'
ELSE 'Over 50'
END as age_group,
COUNT(customer_id)
FROM customer_master
GROUP BY age_group;
You can use the CASE operator.
SELECT CASE
WHEN (DATEDIFF( CURDATE(),date_of_birth) / 365.25) < 40 THEN 'Below 40'
ELSE 'Over 40'
END as age_group,
COUNT(customer_id)
FROM customer_master
GROUP BY age_group;
Sorry for poor formatting, it is my first answer
One solution would be to use cascading values with CASE within a subquery:
select age_group, count(customer_id) as 'count' from
(select customer_id,
year(curdate())-year(date_of_birth) as 'age',
case when (year(curdate())-year(date_of_birth)) < 20, "Below 20"
when (year(curdate())-year(date_of_birth)) < 30, "Between 20 and 29"
when (year(curdate())-year(date_of_birth)) < 40, "Between 30 and 39"
else "40 or Greater" end as 'age_group'
FROM customer_master) x
group by age_group
SELECT
(year(curdate())-year(date_of_birth)) div 20 as age_group,
COUNT(customer_id)
FROM
customer_master
GROUP BY age_group
Something like this will give you number of customers in every 20. If you want different size of group just change the number you divide by.
That is assuming you want each group to be same size e.g
1 - 20
21 - 40
41 - 60
...
If you want different sizes go with CASE solution as other have suggested.

Two different date types in column

I am trying to create a age group array in mysql.
SELECT
COUNT(*),
CASE
when age < 60 THEN '<60'
when age >= 61 AND age <= 65 then '61-65'
when age >= 66 AND age <= 70 then '66-70'
when age >= 71 AND age <= 75 then '71-75'
when age >= 76 AND age <= 80 then '76-80'
when age > 81 then '>81'
END as age_group
FROM(
SELECT YEAR(current_time()) - Year(DateBorn) AS age
FROM custs
WHERE FDID = 'ANGL01'
) as custs2
GROUP BY age_group
When i ran this query, it worked fine, except that there were 2013 null results. It turns out that there are 2 data formats in the column.
The first on is just the year: 'yyyy'
The second on includes the day and month: 'dd/mm/yyyy'
How can I modify this query to take both data formats into account?
Ideally you should store dates using the DATE data type.
Given your current schema, assuming the dates are always either in yyyy or dd/mm/yyyy format then the year will always be the 4 rightmost characters, so you can use RIGHT(DateBorn,4), like this:
SELECT
COUNT(*),
CASE
when age < 60 THEN '<60'
when age >= 61 AND age <= 65 then '61-65'
when age >= 66 AND age <= 70 then '66-70'
when age >= 71 AND age <= 75 then '71-75'
when age >= 76 AND age <= 80 then '76-80'
when age > 81 then '>81'
END as age_group
FROM(
SELECT YEAR(current_date()) - CAST(RIGHT(DateBorn,4) AS UNSIGNED) AS age
FROM custs
WHERE FDID = 'ANGL01'
) as custs2
GROUP BY age_group
You should check for values that don't match your expected date formats. A query like this will give you a sample of non-conformant rows:
select DateBorn
from custs
where DateBorn not regexp '^[0-9][0-9][0-9][0-9]$'
and DateBorn not regexp '^[0-3][0-9]\/[0-1][0-9]\/[0-9][0-9][0-9][0-9]$'
limit 25
How about MySQL's STR_TO_DATE?
SELECT STR_TO_DATE(DATE_FORMAT(YourField, '%d/%m/%Y'),'%Y')...
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_str-to-date
Using this you can manipulate other occurrences. I have inherited legacy data you cannot change, so this function will accommodate everything you can mask, if your data is fairly consistent.
I may have it backwards. Try this, if needed.
SELECT date_format( str_to_date( dt3, '%m-%d-%Y' ) , '%m/%d/%Y' ) AS my_date FROM dt_tb3
http://www.plus2net.com/sql_tutorial/date-string.php
Assuming that DateBorn is a text column, try this:
SELECT YEAR(now()) - Year(str_to_date(DateBorn,
CASE WHEN char_length(DateBorn) = 4
THEN '01/01/%Y'
ELSE '%d/%m/%Y'
END
)) AS age
current_time() is an alias for curtime() which returns HH:MM:SS. So YEAR(current_time()) may result in some odd results. You might want to try YEAR(NOW()) instead.
You would also want to use STR_TO_DATE(str,format) to parse your varchar as a datetime. (Of course, it is better to fix the schema than patch it like this) Make sure to update the format as you have it in your table.
SELECT
COUNT(*),
CASE
when age < 60 THEN '<60'
when age >= 61 AND age <= 65 then '61-65'
when age >= 66 AND age <= 70 then '66-70'
when age >= 71 AND age <= 75 then '71-75'
when age >= 76 AND age <= 80 then '76-80'
when age > 81 then '>81'
END as age_group
FROM(
SELECT YEAR(NOW()) - Year(str_to_date(DateBorn, '%d/%m/%Y')) AS age
FROM custs
WHERE FDID = 'ANGL01'
) as custs2
GROUP BY age_group

Grouping items between 2 numbers

I have a query that looks like this:
select
price,
item_id,
sum(price),
count(item_id)
from transactions
group by
(price <= 20),
(price between 21 and 30),
(price between 31 and 40),
(price between 41 and 50),
(price > 50)
I have never done a group like this before when I wrote it I was just guessing to see if the query was even valid, and it was. But my question is, is it really getting me what I want?
I want all transactions grouped by:
Items that cost less than or equal to $20
Items that cost between $21 and $30
Items that cost between $31 and $40
Items that cost between $41 and $50
Items that cost more than $50
So, is that query doing what I am asking?
The way to do this in standard SQL (and MySQL) is to use the case statement. Also, I put the definition in a subquery like this:
select pricegrp, sum(price), count(item_id)
from (select t.*,
(case when price <= 20 then '00-20'
when price between 21 and 30 then '21-30'
when price between 31 and 40 then '31-40'
when price between 41 and 50 then '41-50'
when price > 50 then '50+'
end) as pricegrp
from transactions t
) t
group by pricegrp
Also, do you want to group by item_id as well? Or are you just trying to return one arbitrary item? Based on what you want, I'm removing the item_id from the select clause. It doesn't seem necessary.
Your query actually does work in MySQL, in the sense that it runs. It is going to produce one row for each group that you want, so in that sense it "works". However, within each group, it is going to choose an arbitrary price and item_id. These are not explicitly mentioned in the group by clause, so you are using a MySQL (mis)feature called Hidden Columns. Different runs of the query or slight changes to the data or slight changes to the query can change the values of price and item_id returned for each group.
I strongly suggest that you actually name the group. This makes the query and the output much clearer.
Also, I recommend that you get in the habit of putting all columns in the select in the group by clause. There are a few cases where hidden columns are actually useful, but I think, in general, you should depend on them sparingly.
If the price is not stored as an integer, then correct logic is:
select pricegrp, sum(price), count(item_id)
from (select t.*,
(case when price <= 20 then '00-20'
when price <= 30 then '21-30'
when price <= 40 then '31-40'
when price <= 50 then '41-50'
when price > 50 then '50+'
end) as pricegrp
from transactions t
) t
group by pricegrp
SELECT
price,
item_id,
sum(price),
count(item_id),
IF(price<=20,0,IF(price<=30,1,IF(price<=40,2,IF(price<=50,3,4)))) AS pricegroup
FROM transactions
GROUP BY pricegroup
or even
SELECT
price,
item_id,
sum(price),
count(item_id)
FROM transactions
GROUP BY
IF(price<=20,0,IF(price<=30,1,IF(price<=40,2,IF(price<=50,3,4))))
SELECT price,
item_id,
SUM(CASE WHEN price <= 20 THEN price ELSE 0 END) `(price <= 20) SUM`,
SUM(CASE WHEN price <= 20 THEN 1 ELSE 0 END) `(price <= 20) COUNT`,
SUM(CASE WHEN price between 21 and 30 THEN price ELSE 0 END) `(price <= 20) SUM`,
SUM(CASE WHEN price between 21 and 30 THEN 1 ELSE 0 END) `(price <= 20) COUNT`,
SUM(CASE WHEN price between 31 and 40 THEN price ELSE 0 END) `price between 31 and 40 SUM`,
SUM(CASE WHEN price between 31 and 40 THEN 1 ELSE 0 END) `price between 31 and 40 COUNT`,
SUM(CASE WHEN price between 41 and 50 THEN price ELSE 0 END) `price between 41 and 50 SUM`,
SUM(CASE WHEN price between 41 and 50 THEN 1 ELSE 0 END) `price between 41 and 50 COUNT`,
SUM(CASE WHEN price > 50 THEN price ELSE 0 END) `price > 50 SUM`,
SUM(CASE WHEN price > 50 THEN 1 ELSE 0 END) `price > 50 COUNT`
FROM transactions
GROUP BY price, item_id

MySQL GROUP BY age range including null ranges

I'm trying to count the number of people by age ranges, and I can almost do it with 2 problems:
If there are no people in a given age range (NULL), then that age range does not appear in the results. For example, in my data there's no entries for "Over 80" so that date range does not appear. Basically, it looks like a mistake in the programming when there are missing date ranges.
I'd like to order the results in a specific way. In the query below, because the ORDER BY is by age_range, the results for '20 - 29' come before the results for 'Under 20'.
Here's a sample of the db table "inquiries":
inquiry_id birth_date
1 1960-02-01
2 1962-03-04
3 1970-03-08
4 1980-03-02
5 1990-02-08
Here's the query:
SELECT
CASE
WHEN age < 20 THEN 'Under 20'
WHEN age BETWEEN 20 and 29 THEN '20 - 29'
WHEN age BETWEEN 30 and 39 THEN '30 - 39'
WHEN age BETWEEN 40 and 49 THEN '40 - 49'
WHEN age BETWEEN 50 and 59 THEN '50 - 59'
WHEN age BETWEEN 60 and 69 THEN '60 - 69'
WHEN age BETWEEN 70 and 79 THEN '70 - 79'
WHEN age >= 80 THEN 'Over 80'
WHEN age IS NULL THEN 'Not Filled In (NULL)'
END as age_range,
COUNT(*) AS count
FROM (SELECT TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age FROM inquiries) as derived
GROUP BY age_range
ORDER BY age_range
Here's a simple solution based on the suggestion by Wrikken:
SELECT
SUM(IF(age < 20,1,0)) as 'Under 20',
SUM(IF(age BETWEEN 20 and 29,1,0)) as '20 - 29',
SUM(IF(age BETWEEN 30 and 39,1,0)) as '30 - 39',
SUM(IF(age BETWEEN 40 and 49,1,0)) as '40 - 49',
SUM(IF(age BETWEEN 50 and 59,1,0)) as '50 - 59',
SUM(IF(age BETWEEN 60 and 69,1,0)) as '60 - 69',
SUM(IF(age BETWEEN 70 and 79,1,0)) as '70 - 79',
SUM(IF(age >=80, 1, 0)) as 'Over 80',
SUM(IF(age IS NULL, 1, 0)) as 'Not Filled In (NULL)'
FROM (SELECT TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age FROM inquiries) as derived
An alternative to the range table (which has my preference), a single-row answer could be:
SELECT
SUM(IF(age < 20,1,0)) as 'Under 20',
SUM(IF(age BETWEEN 20 and 29,1,0)) as '20 - 29',
SUM(IF(age BETWEEN 30 and 39,1,0)) as '30 - 39',
SUM(IF(age BETWEEN 40 and 49,1,0)) as '40 - 49',
...etc.
FROM inquiries;
One way of ordering the results would be introducing a column in the select statement and giving it a rank value of the way you want your results to be ordered with the rest and then order by that row, for example
SELECT
CASE
WHEN age < 20 THEN 'Under 20'
WHEN age BETWEEN 20 and 29 THEN '20 - 29'
WHEN age BETWEEN 30 and 39 THEN '30 - 39'
WHEN age BETWEEN 40 and 49 THEN '40 - 49'
WHEN age BETWEEN 50 and 59 THEN '50 - 59'
WHEN age BETWEEN 60 and 69 THEN '60 - 69'
WHEN age BETWEEN 70 and 79 THEN '70 - 79'
WHEN age >= 80 THEN 'Over 80'
WHEN age IS NULL THEN 'Not Filled In (NULL)'
END as age_range,
COUNT(*) AS count,
CASE
WHEN age < 20 THEN 1
WHEN age BETWEEN 20 and 29 THEN 2
WHEN age BETWEEN 30 and 39 THEN 3
WHEN age BETWEEN 40 and 49 THEN 4
WHEN age BETWEEN 50 and 59 THEN 5
WHEN age BETWEEN 60 and 69 THEN 6
WHEN age BETWEEN 70 and 79 THEN 7
WHEN age >= 80 THEN 8
WHEN age IS NULL THEN 9
END as ordinal
FROM (SELECT TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age FROM inquiries) as derived
GROUP BY age_range
ORDER BY ordinal
Create a table that contains all ranges and use outer join.
Order by numeric value in another column of that table
SELECT range, ....
FROM ranges
LEFT JOIN (Your subquery) ON (ranges.range = your_range)
...
ORDER BY range.year ASC