get top and bottom 25th percentile average - mysql

I have a table with list of employees and the number of units that they have sold.
I want to get the top 25 percentile Avg units sold and Bottom 25 percentile Avg units sold.
I have created a representation of my data SLQ Fiddle
I really have no idea how to start on this? All the examples i see are for SQL Server and not MySQL. Here is what i am thinking.
I want 25 percentile and cant limit to 25 items. Basically it would involve:
1) #_of_employees = The number of total employees.
2) #_of_employees_in_25_percentile = #_of_employees*0.25
3) Calculate the sum of the units sold by the top/bottom 25 percentile (limit #_of_employees_in_25_percentile)
4) Divide the sum by #_of_employees_in_25_percentile to get the average.
How can all this be done efficiently in MySQL?

This is a solution that uses a devious trick I learned from this question.
SELECT id, unit_sold, n * 100 / #total AS percentile
FROM (
SELECT id, unit_sold, #total := #total + unit_sold AS n
FROM mydata, (SELECT #total := 0) AS total
ORDER BY unit_sold ASC
) AS t
SQL Fiddle.

What about this?
SELECT
SUM(unit_sold) AS sum_tot, SUM(unit_sold)/count(id) AS average,
SUM(CASE WHEN percentile<25 THEN unit_sold ELSE 0 END) AS sum_top25,
SUM(CASE WHEN percentile<25 THEN 1 ELSE 0 END) AS count_top25,
SUM(CASE WHEN percentile<25 THEN unit_sold ELSE 0 END)/SUM(CASE WHEN percentile<25 THEN 1 ELSE 0 END) AS average_top25,
SUM(CASE WHEN percentile>75 THEN unit_sold ELSE 0 END) AS sum_bottom25,
SUM(CASE WHEN percentile>75 THEN 1 ELSE 0 END) AS count_bottom25,
SUM(CASE WHEN percentile>75 THEN unit_sold ELSE 0 END)/SUM(CASE WHEN percentile>75 THEN 1 ELSE 0 END) AS average_bottom25
FROM
(SELECT
id, unit_sold, c * 100 / #counter AS percentile
FROM
(SELECT
m.*, #counter:=#counter+1 AS c
FROM
(SELECT #counter:=0) AS initvar, mydata AS m
ORDER BY unit_sold desc
) AS t
WHERE
c <= (25/100 * #counter)
OR c >= (75/100 * #counter)
) AS t2
Output:
SUM_TOT AVERAGE SUM_TOP25 COUNT_TOP25 AVERAGE_TOP25 SUM_BOTTOM25 COUNT_BOTTOM25 AVERAGE_BOTTOM25
850 283.3333 500 1 500 350 2 175
See SQL Fiddle.
The idea is to use the MySQL: LIMIT by a percentage of the amount of records? solution to get the percentiles. Based on that (and on pdw answer) we create an output in which we just show the top 25% and bottom 75%.
Finally, we count and sum to get the values you requested.
Note this runs on top of the command:
SELECT
id, unit_sold, c * 100 / #counter AS percentile
FROM
(SELECT
m.*, #counter:=#counter+1 AS c
FROM
(SELECT #counter:=0) AS initvar, mydata AS m
ORDER BY unit_sold desc
) AS t
WHERE
c <= (25/100 * #counter)
OR c >= (75/100 * #counter)
Whose output is:
ID UNIT_SOLD PERCENTILE
d 500 20
a 250 80
e 100 100

How about going with this logic:
Select all, order by percentile (DESC), limit to 25
Select all, order by percentile (ASC), limit to 25
Is this the type of logic you're looking for ?
Sample queries:
$q1 = mysql_query(SELECT * FROM table_name ORDER BY percentile DESC LIMIT 25)
$q2 = mysql_query(SELECT * FROM table_name ORDER BY percentile ASC LIMIT 25)

Related

MySQL: Rank the total total amount with COUNT()

Edit: I want to rank with COUNT()
I've made a View with every order a customer made. In the next step I wrote a query to calculate the total amount a customer purchased.
Now I want to rank the customers, based on their total purchase.
I wrote this query:
SELECT u.m_name, SUM(u.num * u.price) AS total,
(SELECT COUNT(*)
FROM v_sales AS x
WHERE x.m_id = u.m_id
AND (SELECT SUM(s1.num * s1.price) FROM v_sales AS s1 WHERE s1.m_id = x.m_id)
>
(SELECT SUM(s2.num * s2.price) FROM v_sales AS s2 WHERE s2.m_id = x.m_id)
) + 1 AS Rank
FROM v_sales AS u
GROUP BY u.m_id;
But the results are not the expected ones:
# m_name total Rank
川島智弘 2620 1
河田英毅 0 1
山田忠明 15420 1
永峰弘万 500 1
永山智広 380 1
I need the following output:
# m_name total Rank
川島智弘 2620 2
河田英毅 0 5
山田忠明 15420 1
永峰弘万 500 3
永山智広 380 4
Has someone an idea what I did wrong? It would be also helpful if someone could explain why my query doesn't work.
Here is a Fiddle
Thank you
You can use the RANK function in MariaDB 10.4.
SELECT m_name, SUM(num * price) AS total,
RANK() OVER(ORDER BY SUM(num * price) DESC)
FROM v_sales
GROUP BY m_id;
Fiddle
No window function:
SELECT t1.m_name,MAX(t1.total),COUNT(t2.m_name)+1 as RANK
FROM
(SELECT m_name, SUM(num * price) AS total FROM
v_sales
GROUP BY m_id) t1
LEFT JOIN
(SELECT m_name, SUM(num * price) AS total FROM
v_sales
GROUP BY m_id) t2
ON t1.total<t2.total
GROUP BY t1.m_name
ORDER BY 3
Fiddle

Mysql sort by range

I have a database which looks like this :
NUM / CNT
3 / 1
5 / 0
100 / 1
300 / 0
320 / 1
And I am looking for the query that will allow me to sort them by range and make sum of their count so I will have something like this:
NUM / CNT
0-100 / 2
100-400 / 1
I am wondering if this is possible using mysql querys .
Use SUM with conditional aggregation on the value of each NUM:
SELECT '0-100' AS NUM,
SUM(CASE WHEN NUM BETWEEN 0 AND 100 THEN CNT ELSE 0 END) AS CNT
FROM yourTable
UNION
SELECT '100-400' AS NUM,
SUM(CASE WHEN NUM BETWEEN 100 AND 400 THEN CNT ELSE 0 END) AS CNT
FROM yourTable
try this it will work same as you want.
SELECT '0-100' AS NUM,
SUM(CASE WHEN NUM <= 100 THEN CNT ELSE 0 END) AS CNT
FROM tableName
UNION
SELECT '100-400' AS NUM,
SUM(CASE WHEN NUM > 100 THEN CNT ELSE 0 END) AS CNT
FROM tableName

current average for each row of data

I have a table output with as-Date Output
1-Jan 20
2-Jan 40
3-Jan 30
4-Jan 100
5-Jan 120
6-Jan 10
7-Jan 90
8-Jan 80
9-Jan 60till
31-Dec 120
I need to query the average of each date where the average is the culmilative average of values from 1st date to current date as below-
Date Output Average
1-Jan 20 20
2-Jan 40 30
3-Jan 30 30
4-Jan 100 47.5
5-Jan 120 62
6-Jan 10 53.5
Any one can help please?
SELECT `date`, `output`,
(SELECT avg(`output`) from Table1 where Table1.`date` <= b.`date`)
as `average` FROM Table1 b
sqlfiddle here
Axel's answer works, alternatively, you can do it in a single query, with variables:
set #count := 0;
set #total := 0;
select case when ((#count := #count + 1) and ((#total := #total + output) or 1))
then #total / #count
end rolling_average,
`date`,
`output`
from data
order by `date` asc
http://sqlfiddle.com/#!9/2e006/14
This avoids the dependent subquery, which depending on the size of your data may result in better performance.
Pala's idea is a good idea. In addition to lacking the order by, it also fails if the cumulative sum were ever zero or if output where ever NULL. This can easily be fixed:
select `date`, `output`,
if((#count := #count + 1) is not null,
if((#total := #total + coalesce(output, 0)) is not null,
#total/#count, 0
), 0
) as running_average
from data cross join
(select #count := 0, #total := 0) init
order by date;
Here's another way, although Pala's method scales better...
SELECT x.*
, AVG(y.output) avg
FROM output x
JOIN output y
ON y.date <= x.date
GROUP
BY x.date
ORDER
BY x.date;
The order by clause is apparently necessary post version 5.5/5.6

DataBase-Finding total scores

I've 15 levels, and each level consists of 2 rounds. Now I've to find the total score by finding the max score the user earned in round1 and round2 and calculate it. Likewise the same method up to how many levels he played. And, finally, the total result by calculating scores from all levels.
Can anyone please help me to find the total score using MySQL..!
I tried this code:
SELECT *, (classifica_score + MAX2) AS TOTAL FROM(
SELECT IFNULL( MAX(XX1.classifica_score), 0) AS classifica_score, XX1.classifica_level, XX1.classifica_round, XX1.fb_id,
(SELECT IFNULL( MAX(TC1.classifica_score), 0) FROM tab_classifica AS TC1 WHERE TC1.classifica_round = 2 AND TC1.fb_id = XX1.fb_id) AS MAX2
FROM tab_classifica AS XX1 WHERE XX1.classifica_round=1
GROUP BY XX1.classifica_level, XX1.classifica_round, XX1.fb_id
UNION ALL
SELECT IFNULL( MAX(XX2.classifica_score),0) AS classifica_score, XX2.classifica_level, XX2.classifica_round, XX2.fb_id,
(SELECT IFNULL( MAX(TC2.classifica_score),0) FROM tab_classifica AS TC2 WHERE TC2.classifica_round = 1 AND TC2.fb_id = XX2.fb_id ) AS MAX1
FROM tab_classifica AS XX2 WHERE XX2.classifica_round=2
GROUP BY XX2.classifica_level, XX2.classifica_round, XX2.fb_id
) AS TAB
my Db structure is as follows
classifica_id----- fb_id---classifica_nome----classifica_level----classifica_round---classifica_score

count consecutive number of 10 days when number is = 0 or > 10

Here is sqlfiddle that i made with mysql query
http://sqlfiddle.com/#!2/f2794/4
It count 10 consecutive days when present = 0, but i need to add second condition to count where present is > 10.
For example
11
22
0
0
0
0
0
0
0
0
0
0
0
0
1
should count 14
here is that query
select sum(count) total from (
SELECT COUNT(present) as count FROM (
SELECT
IF((q.present != 0), #rownum:=#rownum+1, #rownum:=#rownum) AS rownumber, #prevDate:=q.date, q.*
FROM (
SELECT
name
, date
, present
FROM
teacher, (SELECT #rownum:=0, #prevDate:='') vars
WHERE date BETWEEN '2013-07-01' AND '2013-07-31'
ORDER BY date, present
) q
) sq
GROUP BY present, rownumber
HAVING COUNT(*) >= 10
) d
So if U can help me, pls do it :)
best regards
m.
I dont really understand your query overly well, but I think simply changing (q.Present != 0) to incorporate the additional test should solve your problem:
SELECT sum(count) total from (
SELECT COUNT(present) as count FROM (
SELECT
IF((q.present != 0 AND q.present <= 10), #rownum:=#rownum+1, #rownum:=#rownum) AS rownumber, #prevDate:=q.date, q.*
FROM (
SELECT
name
, date
, present
FROM
teacher, (SELECT #rownum:=0, #prevDate:='') vars
WHERE date BETWEEN '2013-07-01' AND '2013-07-31'
ORDER BY date, present
) q
) sq
GROUP BY present, rownumber
HAVING COUNT(*) >= 10
) d