Grouping items between 2 numbers - mysql

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

Related

SQL display age range

I'm writing a query for age range, in which I want to show the count of people of all age ranges eg
AGE PEOPLE
"0-10" 0
"11-20" 2
"21-30" 5
"31-40" 0
"41-50" 1
I've tried using
SELECT SUM(CASE WHEN age < 10 THEN 1 ELSE 0 END) AS [Under 10],
SUM(CASE WHEN age BETWEEN 11 AND 20 THEN 1 ELSE 0 END) AS [11-20],
SUM(CASE WHEN age BETWEEN 21 AND 30 THEN 1 ELSE 0 END) AS [21-30]
FROM people
But it shows ranges as column names
0-10 11-20 21-30 31-40 41-50
0 2 5 0 1
which i dont want.
I have also tried GROUP BY but it didn't show the ranges in which the count was 0.
You can use UNION ALL:
SELECT '[Under 10]' as Age, SUM(CASE WHEN age < 10 THEN 1 ELSE 0 END) as People
FROM people
UNION ALL
SELECT '[11-20]', SUM(CASE WHEN age BETWEEN 11 AND 20 THEN 1 ELSE 0 END)
FROM people
UNION ALL
SELECT '[21-30]', SUM(CASE WHEN age BETWEEN 21 AND 30 THEN 1 ELSE 0 END)
FROM people;
you case when should be like below
CASE WHEN age < 10 then '0-10'
when age age BETWEEN 11 AND 20 then '11-20'
when age BETWEEN 21 AND 30 then '21-30'
..... end as agegroup,--put here more according to your need
count(*)
from table group by agegroup
You need to perform UNION All for this.
SELECT SUM(CASE WHEN age < 10 THEN 1 ELSE 0 END) AS PEOPLE, 'UNDER 10' AS AGE FROM people
UNION ALL
SELECT SUM(CASE WHEN age BETWEEN 11 AND 20 THEN 1 ELSE 0 END) AS PEOPLE, `11-20` FROM people
UNION ALL
SELECT SUM(CASE WHEN age BETWEEN 21 AND 30 THEN 1 ELSE 0 END) , `21-30` FROM people
You want to get the group of result in rows so need to perform UNION in this case.
Please find this link for more info on UNION in MYSQL.link
If you are going to use UNION, use UNION ALL and move the conditions to the WHERE clause:
SELECT '[Under 10]' as Age, COUNT(*)
FROM people
WHERE age < 10
UNION
SELECT '[11-20]', COUNT(*)
FROM people
WHERE BETWEEN 11 AND 20
UNION ALL
SELECT '[21-30]', COUNT(*)
FROM people
WHERE age BETWEEN 21 AND 30;
Filtering and UNION ALL both improve performance. (UNION incurs overhead for removing duplicates).
There are other approaches. For instance, you can unpivot your table:
SELECT grp.age,
(CASE grp
WHEN 1 THEN [Under 10]
WHEN 2 THEN [11-20]
WHEN 3 THEN [21-30]
END)
FROM (SELECT SUM(CASE WHEN age < 10 THEN 1 ELSE 0 END) AS [Under 10]
SUM(CASE WHEN age BETWEEN 11 AND 20 THEN 1 ELSE 0 END) AS [11-20],
SUM(CASE WHEN age BETWEEN 21 AND 30 THEN 1 ELSE 0 END) AS [21-30]
FROM people p
) p CROSS JOIN
(SELECT 1 as grp, '[Under 10]' as age UNION ALL
SELECT 2 as grp, '[11-20]' as age UNION ALL
SELECT 3, as grp, '[21-30]' as age
) grps;
Although this looks more complicated, it is much better from a performance perspective, because it only scans the original table once.
There are other variants as well that only touch the original table once.

Getting specific counts in Mysql using case / if

I am trying to device an incentive plan for my sales agents depending on number of orders, and which shift they worked in.
The sales table (mysql) has the following columns:
sale_id (int, primary, auto increment)
employee (varchar, 255)
sale_time (datetime, the time sale was placed)
sale_amount (float 10,2)
sales_channel (tinyint, with values call (1), walk_in (2),referral(3))
is_cancelled (tinyint, with values 1 for cancelled, and 0 as default)
(and a few other columns that are not relevant for this case)
I want to have write a query to fetch result with following columns (I use orders and sales interchangeably):
employee
total_orders (count excluding cancelled sales, i.e. is_cancelled!= 1)
orders_below_100dollars (orders count with sale_amount below 100 and is_cancelled = 0)
orders_above_100dollars (orders count with sale_amount above 100 and is_cancelled = 0)
orders_dayShift (orders count placed between 9am and before 10pm and is_cancelled = 0)
orders_nightShift (orders count placed after 10pm and before next day 9am and is_cancelled = 0)
cancelled_orders (orders count with is_cancelled = 1)
I understand that the query would have group by, case and perhaps if/else in the select, but can't frame it properly. Please help.
You are looking to use conditional aggregation -- here are a couple examples:
select employee,
sum(case when is_cancelled != 1 then 1 else 0 end) total_orders,
sum(case when sale_amount < 100 then 1 else 0 end) orders_below_100dollars,
sum(case when sale_amount >= 100 then 1 else 0 end) orders_above_100dollars,
...
from sales
group by employee
I'm not exactly sure what constitutes daytime vs nighttime, but it should be easy to add given the above.
You can use conditional aggregation for a lot of these. In other words, put the condition inside of a SUM() function to get the count of rows matching certain conditions:
SELECT employee,
SUM(is_cancelled <> 1) AS totalOrders,
SUM(sale_amount < 100 AND is_cancelled <> 1) AS orders_below_100,
SUM(sale_amount > 10 AND is_cancelled <> 1) AS orders_above_100,
SUM(sale_time < '22:00:00' AND is_cancelled <> 1) AS orders_dayshift,
SUM(sale_time > '22:00:00' AND is_cancelled <> 1) AS orders_nightshift,
SUM(is_cancelled = 1) AS totalCanceled
FROM sales
GROUP BY employee;

Calculate percentage and total after create categories mysql

I've this query
SELECT
trage,
CASE trage
WHEN '<18' THEN SUM(CASE WHEN AGE <18 THEN 1 ELSE 0 END)
WHEN '18-24' THEN SUM(CASE WHEN AGE >= 18 AND AGE <= 24 THEN 1 ELSE 0 END)
WHEN '25-34' THEN SUM(CASE WHEN AGE >= 25 AND AGE <= 34 THEN 1 ELSE 0 END)
WHEN '35-44' THEN SUM(CASE WHEN AGE >= 35 AND AGE <= 44 THEN 1 ELSE 0 END)
WHEN '45-54' THEN SUM(CASE WHEN AGE >= 45 AND AGE <= 54 THEN 1 ELSE 0 END)
WHEN '>=55' THEN SUM(CASE WHEN AGE >= 55 THEN 1 ELSE 0 END)
END Total
FROM
( SELECT
t_personne.pers_date_naissance,
t_personne.pers_date_inscription,
TIMESTAMPDIFF(Year, t_personne.pers_date_naissance, t_personne.pers_date_inscription)
- CASE
WHEN MONTH(t_personne.pers_date_naissance) > MONTH(t_personne.pers_date_inscription)
OR (MONTH(t_personne.pers_date_naissance) = MONTH(t_personne.pers_date_inscription)
AND DAY(t_personne.pers_date_naissance) > DAY(t_personne.pers_date_inscription))
THEN 1 ELSE 0
END AS AGE
FROM t_personne
) AS Total
CROSS JOIN
( SELECT '<18' trage UNION ALL
SELECT '18-24' UNION ALL
SELECT '25-34' UNION ALL
SELECT '35-44' UNION ALL
SELECT '45-54' UNION ALL
SELECT '>=55'
)a
GROUP BY trage
ORDER BY FIELD(trage, '<18', '18-24', '25-34', '35-44', '45-54', '>=55')
it give a table with two columns trage and Total for all categories
How to add a column percentage with a line TOTAL for the column Total and %
Thanks for your help
For the time being, you can't do this. To support this MySQL needs Window Function support which it still doesn't have. If you need functions like these I would recommend switching to PostgreSQL.
Also take a look at this question: MySql using correct syntax for the over clause

Dont select data from database with two tables using mysql

I have 2 tables sale and receipt.Table structure and result structure is shown below.
sale
date total sale_type
15-8-2014 50 credit
16-8-2014 100 credit
17-8-201 200 return
18-8-2014 300 return
receipt
date net_amount
15-8-2014 100
16-8-2014 200
17-8-2014 300
result
date sale receipt
15-8-2014 50 100
16-8-2014 100 200
17 -8-2014 200 300
18-8-2014 300
Using my query i got these result structure ,but i want to get also the sum total in the case sale_type='credit' and sale_type='return".Any body help me?
My query is
select date,total,net_amount from
(select date, total, null as net_amount, 2 as sort_col from sale union
all select date,
null as total, net_amount as net_amount, 1 as sort_col from receipt)
as a order by date desc, sort_col desc
Does the following query (using JOIN and CASE get you expected results?
SELECT
s.date,
s.total AS sale,
r.net_amount AS receipt,
SUM(CASE
WHEN s.sale_type = 'credit' THEN s.total
ELSE 0
END) AS sum_credit,
SUM(CASE
WHEN s.sale_type = 'return' THEN s.total
ELSE 0
END) AS sum_return
FROM sale s
LEFT JOIN receipt r
ON s.date = r.date
GROUP BY s.date, s.total, r.net_amount;

MYSQL - get a row for each year, with total sum for each month

I have a table of transactions for purchases. Each transaction has a timestamp and purchase amount (in USD).
I'm trying to create some stats from this. I'd like to extract a row for each year that contains the sum for each month in the year. (I'd like months with no transaction to sum to 0 - not omitted.)
I know I could just do a plain SELECT of everything and process it in PHP, but I was wondering if it was at all possible to make MySQL do the work and extract the data like I want it?
What I'd like to see is rows like:
Year, Total_Jan, Total_Feb, ... Total_Dec, Total_Year
I am able to get the total per year, but I can't work out how to get the total per month into the same row.
SELECT
YEAR(dt) as the_year,
SUM(mc_gross) AS sum_total
FROM
transactions
GROUP BY
the_year
SELECT
YEAR(dt) as the_year,
SUM(CASE WHEN MONTH(dt) = 1 THEN mc_gross ELSE 0 END) AS Total_Jan,
SUM(CASE WHEN MONTH(dt) = 2 THEN mc_gross ELSE 0 END) AS Total_Feb,
...
SUM(CASE WHEN MONTH(dt) = 12 THEN mc_gross ELSE 0 END) AS Total_Dec
FROM
transactions
GROUP BY
the_year;