How to get TopN query group by month MYSQL - mysql

There's a table like:
months contact COUNT
202007 asdas 45
202007 madhouse 1
202007 RORC YANG 1
202007 RORG 2
202007 ROR 5
202008 SARINA 1
202008 SMB 1
How can I get top 4 query result each month?
Expected result:
months contact COUNT
202007 asdas 45
202007 ROR 5
202007 RORG 2
202008 SARINA 1
202008 SMB 1
I'm working with mysql5.6

Here are 2 choices. The first uses rank() over() which does not guarantee only 4 rows per month (there could be more) and the second uses row_number() over() which will limit number of rows to a max of 4 per month
select
*
from (
select
* , rank() over(partition by months order by c desc) as cr
from (
select months, contact, count(*) as c
from mytable
group by months, contact
) as g
) as d
where cr <= 4
;
select
*
from (
select
* , row_number() over(partition by months order by c desc) as rn
from (
select months, contact, count(*) as c
from mytable
group by months, contact
) as g
) as d
where rn <= 4
;
see demo
for older MySQL try a row number hack:
select
*
from (
select
#row_num :=IF(#prev_value=g.months,#row_num+1,1)AS RowNumber
, g.months
, g.contact
, g.c
, #prev_value := g.months
from (
select months, contact, count(*) as c
from mytable
group by months, contact
) as g
CROSS JOIN (SELECT #row_num :=1, #prev_value :='') vars
ORDER BY g.months, g.contact
) as d
where RowNumber <= 4
see that in demo

TOP5
SELECT z.months, z.contact, z.count
FROM
(SELECT
x.*,
#rownum := #rownum + 1,
IF(#part = x.months,#r := #r + 1,#r := 1) AS rank,
#part := x.months
FROM
(
SELECT
*
FROM
my_table e
ORDER BY
e.months ASC,e.count DESC) X,
(
SELECT
#rownum := 0,
#part := NULL,
#r := 0) rt)z
WHERE z.rank <=5

Related

Mysql Group By Levels ranking

rank points player_id quiz_id
1 88 1 40
2 80 3 40
3 30 3 41
4 20 1 41
Getting this output from the following query:
SELECT m.rank,
m.scorer AS points,
m.player_id
FROM
( SELECT d.player_id,
d.scorer, #rownum := #rownum + 1 AS rank
FROM
( SELECT t.player_id,
SUM(t.score) AS scorer
FROM answers t
JOIN PROFILE ON profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quiz.id
WHERE t.is_active = 1
AND quiz.contest_id = 1
AND profile.signin_source_id != 1
AND profile.is_active = 1
AND t.quiz_id IN (1,
2)
GROUP BY t.player_id
ORDER BY scorer DESC, t.created_utc ASC) d,
(SELECT #rownum := 0) r) m
WHERE m.scorer > 0
However, the output I want is rank for each level separated out.
rank points player_id quiz_id
1 88 1 40
2 80 3 40
1 30 3 41
2 20 1 41
I followed these :
How to perform grouped ranking in MySQL
https://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/
But can't get the desired output. Any suggestion or help is appreciated.
Try this query, it is simplier IMO:
select #quiz_id_lag := 0, #rank := 1;
select rank, points, player_id, quiz_id from (
select points,
player_id,
case when #quiz_id_lag = quiz_id then #rank := #rank + 1 else #rank := 1 end rank,
#quiz_id_lag,
#quiz_id_lag := quiz_id,
quiz_id
from tbl
order by quiz_id, points desc
) a;
To incorporate this in your query, try:
SELECT #quiz_id_lag := 0, #rank := 1;
SELECT rank,
scorer AS points,
player_id
FROM (
SELECT quiz_id,
player_id,
scorer,
CASE WHEN #quiz_id_lag = quiz_id THEN #rank := #rank + 1 ELSE #rank := 1 END rank,
#quiz_id_lag := quiz_id,
quiz_id
FROM (
SELECT t.player_id,
SUM(t.score) AS scorer,
t.quiz_id
FROM answers t
JOIN PROFILE ON profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quiz.id
WHERE t.is_active = 1 AND quiz.contest_id = 1 AND profile.signin_source_id != 1
AND profile.is_active = 1 AND t.quiz_id IN (1, 2)
GROUP BY t.player_id
) d
WHERE scorer > 0
ORDER BY quiz_id, scorer DESC
) m
Finally got the desired result, ending up with the query, in order to get proper ranking for level wise:
SELECT m.rank,m.scorer AS points,m.player_id, m.quiz_id FROM (
SELECT d.player_id,d.scorer,
#rownum:= CASE WHEN #quiz_id <> d.quiz_id THEN 1 ELSE #rownum+1 END as rank,
#quiz_id:= d.quiz_id as quiz_id FROM
(SELECT #rownum := 1) r,
(SELECT #quiz_id := 0) c,(
SELECT t.player_id,SUM(t.score) as scorer, t.quiz_id
FROM answers t JOIN profile ON
profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quiz.id
WHERE t.is_active = 1 AND quiz.contest_id = 2 AND
profile.signin_source_id != 1 AND profile.is_active = 1
GROUP BY t.player_id,t.quiz_id
ORDER BY quiz_id DESC,scorer DESC, t.created_utc ASC) d
) m
WHERE m.scorer > 0 ORDER BY quiz_id
This will give the entire result set for all the levels for a specific group, if want to get the rank for specific levels from a specific group, then do add
AND t.quiz_id IN (1,2)
Thanks to all who ever participated!
Try this
SELECT m.rank,m.scorer AS points,m.player_id, m.quiz_id
FROM (
SELECT d.player_id,d.quiz_id, d.scorer,
#cur:= IF(quiz_id=#id, #cur+1, 1) AS rank,
#id := quiz_id
FROM (
SELECT t.player_id, quiz.id as quiz_id, SUM(t.score) as scorer
FROM answers t JOIN profile ON profile.player_id = t.player_id
JOIN quiz ON t.quiz_id = quize.id
WHERE t.is_active = 1 AND quiz.contest_id = 1
AND profile.signin_source_id != 1 AND profile.is_active = 1
AND t.quiz_id IN (1,2)
GROUP BY t.player_id, quiz.id
ORDER BY scorer DESC
) d, (SELECT #id:=(SELECT MIN(id) FROM quiz), #cur:=0) AS init
order by d.quiz_id, d.scorer desc) m
WHERE m.scorer > 0

How to get all rows with second highest value

I have this following table:
name value year
A 1 2015
A 2 2014
A 3 2013
B 1 2015
B 3 2013
C 1 2015
C 2 2014
How can I get, for each name, the row with the second highest year, like this:
name value year
A 2 2014
B 3 2013
C 2 2014
I tried the following query but no success:
select name, value, year
from TABLE_NAME
WHERE year IN (select year from TABLE_NAME order by year desc limit 1,1)
The previous query gives me this error:
"SQL Error (1235): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' "
And I can't change the MySQL version (5.6.25) right now, because the solution is already in production.
Any help, please?
One way to solve n per group in MySQL is to simulate ROW_NUMBER. Note that this will only return one value per name.
SELECT
name,
value,
year
FROM
(SELECT
t.name,
t.value,
t.year,
#rn := if(#prev = t.name, #rn + 1,1) as rn,
#prev:=t.name
FROM
test_table as t
JOIN (SELECT #Prev:= Null, #Rn := 0) as v
ORDER BY
t.name,
T.year desc) as t
WHERE
rn = 2;
How this works.
SELECT #Prev:= Null, #Rn := 0 initializes two variables #Prev and #Rn.
#rn := if(#prev = t.name, #rn + 1,1) as rn set the variable of #rn to either 1 or #rn + 1 depending on if #prev = t.Name and returns the value of #rn as the column rn
#prev:=t.name sets the value of #prev equal to the current value of name
if you run
SELECT
t.name,
t.value,
t.year,
#prev = t.name as eval,
#rn := if(#prev = t.name, #rn + 1,1) as rn,
#prev:=t.name as prev
FROM
test_table as t
JOIN (SELECT #Prev:= Null, #Rn := 0) as v
ORDER BY
t.name,
T.year desc
I would expect something like
name value year eval rn prev
A 1 2015 false 1 null
A 2 2014 true 2 A
A 3 2013 true 3 A
B 1 2015 false 1 A
B 3 2013 true 2 B
C 1 2015 false 1 B
C 2 2014 true 2 C
Wrapping into a subquery and the filtering for rn=2 gives you the desired result
My strategy is to use a grouping to find the highest years. Then join with the original table to remove the highest years. Finally do a grouping on the combined table to find the second highest year for each name. (If you need value you can do an INNER JOIN with the original table to find it.)
SELECT name, MAX(year)
FROM
(SELECT name, year
FROM TABLE_NAME) AS x1
INNER JOIN
(SELECT name, MAX(year) AS year
FROM TABLE_NAME
GROUP BY name, year
) AS x2
ON x1.name = x2.name AND x1.year <> x2.year
GROUP BY name
ORDER BY name ASC ;
Try this :
select * from test_table where year = (select distinct year from test_table order by year desc limit 1,1)
It should work if you rewrite it as a join:
select b.name, b.value, b.year
from (select year
from table_name
order by year desc
limit 1, 1) a
join table_name b
on b.year = a.year

How to get correct position on ties in mysql rankings

This is my code and works for ties but it does not skip position on ties
SELECT `item`, (`totalrate` / `nrrates`),
#rank_count := #rank_count + (totalrate/nrrates < #prev_value) rank,
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
ORDER BY avg DESC
Here is the out I get
item (`totalrate` / `nrrates`) rank avg
Virginia 10.0000 1 10
Ana 9.7500 2 9.75
Angeie 9.72 3 9.72
Carel 9.666666666 4 9.66
sammy 9.666666666 4 9.66
Oda 9.500000000 5 9.5
I want
item (`totalrate` / `nrrates`) rank avg
Virginia 10.0000 1 10
Ana 9.7500 2 9.75
Angeie 9.72 3 9.72
Carel 9.666666666 4 9.66
sammy 9.666666666 4 9.66
Oda 9.500000000 6 9.5
To skip the 5 position
I would like to merge with this that does skip position on ties
(I took the below code from this post
MySQL Rank in the Case of Ties)
SELECT t1.name, (SELECT COUNT(*) FROM table_1 t2 WHERE t2.score > t1.score) +1
AS rnk
FROM table_1 t1
how would I modify my code to get it to skip position with the above code it looks simple but i haven't figured it out.
Thanks
On ties, you may want to skip and use current row num to next unmatched avg value row as next rank.
Following should help you
SELECT `item`, #curr_avg := ( `totalrate` / `nrrates` )
, case when #prev_avg = #curr_avg then #rank := #rank
else #rank := ( #cur_row + 1 )
end as rank
, #cur_row := ( #cur_row + 1 ) as cur_row
, #prev_value := #curr_avg avg
FROM table
, ( SELECT #prev_avg := 0, #curr_avg := 0
, #rank := 0, #cur_row := 0 ) init
ORDER BY avg DESC
Similar examples:
To display top 4 rows using rank
Mysql Query for Rank (RowNumber) and Groupings
Update a field with an incrementing value that resets based on
field
Here's another alternative. First, the averages are calculated. If they are already available in a table, it would be even easier (as can be seen in the fiddle demo). Anyways, the rank is based on the logic of counting how many items have a lesser average than the current item.
SELECT
A1.`item`,
A1.avg,
COUNT(A2.`item`) avg_rank
FROM
(
SELECT `item`, (`totalrate` / `nrrates`),
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
) A1 --alias for the inline view
INNER JOIN
(
SELECT `item`, (`totalrate` / `nrrates`),
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
) A2 --alias for the inline view
ON A2.avg < A1.avg
GROUP BY A1.id, A1.avg
ORDER BY A1.avg;
SQL Fiddle demo

To display top 4 rows using mysql Rank is displaying wrong

I need to display the top4 and lease 4 rows based Amount and group by agentId but here rank is showing wrong
And how to show least(last 4 rows?)
schema:
AgentID amount
1 3000
1 3200
2 9000
SELECT Agentid,SUM(AmountRecevied) as Amount,#rownum := #rownum + 1 AS Rank
FROM collection ,(SELECT #rownum := 0) r
GROUP BY AgentID
ORDER BY Amount DESC
limit 4;
Try this way:
SELECT T.Agentid,T.Amount, #rownum := #rownum - 1 AS Rank
FROM
(SELECT Agentid,SUM(AmountRecevied) as Amount
FROM collection
GROUP BY AgentID
ORDER BY Amount
LIMIT 4) T,(SELECT #rownum := 11) r
Try this :
SELECT
C.*,
#rownum := #rownum + 1 AS Rank
FROM (
SELECT
Agentid,
SUM(AmountRecevied) as Amount
FROM collection
GROUP BY AgentID
ORDER BY Amount DESC
LIMIT 4
) AS C, (SELECT #rownum := 0) r
In case of amount matching for different agentids, then, I believe, ranks should be assigned same.
This solution should help you:
select
/*case when rank>6 then '' else rank end as */
rank, agentid, amount
from (
select agentid, #ca:=amount amount
, case when #pa=#ca then #rn:=#rn
else #rn:=( #rn + 1 )
end as rank
, #pa:=#ca as temp_currAmount
from ( select agentid, sum(amount) as amount
from agents
group by agentid
order by amount
) amounts_summary,
(select #pa:=0, #c0:=0,
#rn:=0) row_nums
order by rank desc
) results
where rank > 6
order by rank
;
Demo # MySQL 5.6.6 Fiddle
And if you want no display ranks greater than '6' but empty, then
just uncomment the case line and comment the where condition line
select
case when rank>6 then '' else rank end as
rank, agentid, amount
from (
select agentid, #ca:=amount amount
, case when #pa=#ca then #rn:=#rn
else #rn:=( #rn + 1 )
end as rank
, #pa:=#ca as temp_currAmount
from ( select agentid, sum(amount) as amount
from agents
group by agentid
order by amount
) amounts_summary,
(select #pa:=0, #ca:=0,
#rn:=0) row_nums
order by rank
) results
-- where rank > 6
order by rank
;
You can modify asc or desc as required.
Demo # MySQL 5.6.6 Fiddle

Cumulative count over time

I have a table orders like this:
customer_id order_date
10 2012-01-01
11 2012-01-02
10 2012-01-02
12 2012-01-03
11 2012-01-04
12 2012-02-01
11 2012-02-04
13 2012-02-05
14 2012-02-06
How can I get a cumulative average over time (per month) like this:
order date count orders count customers (customer_id)
2012-01 1 1 (12)
2012-01 2 2 (10,11)
2012-02 1 2 (13,14)
2012-02 2 2 (10,12
2012-02 3 2 (11)
showing how the number of customers vs. number of orders per customer develops over time.
The following query gives me the wanted information - but not over time. How can I iterate the query over time?
SELECT number_of_orders, count(*) as amount FROM (
SELECT o.customer_id, count(*) as number_of_orders
FROM orders o
GROUP BY o.customer_id) as t1
GROUP BY number_of_orders
Update:
have now build the following PHP code to generate what I need, wonder if that could be done using cumulative counts like on http://www.freeopenbook.com/mysqlcookbook/mysqlckbk-chp-12-sect-14.html
$year = 2011;
for ($cnt_months = 1; $cnt_months <= 12; $cnt_months++) {
$cnt_months_str = ($cnt_months < 10) ? '0'.$cnt_months : $cnt_months;
$raw_query = "SELECT number_of_orders, count(*) as amount
FROM (
SELECT
o.customer_id,
count(*) as number_of_orders
FROM orders o
where Date_Format( o.order_date, '%Y%m' ) >= " . $year . "01 and Date_Format( o.order_date, '%Y%m' ) <= " . $year . $cnt_months_str . "
GROUP BY o.customer_id) as t1
GROUP BY number_of_orders";
$query = db_query($raw_query);
while ($row = db_fetch_array($query)) {
$data[$cnt_months_str][$row['number_of_orders']] = array($row['number_of_orders'], (int)$row['amount']);
}
}
A good starting point is
SELECT
order_date,
COUNT(*) AS distinctOrders,
COUNT(DISTINCT customer_id) AS distinctCustomers,
GROUP_CONCAT(DISTINCT customer_id ASC) AS customerIDs
FROM orders
GROUP BY order_date ASC
This will give you the order_date, the number of orders on that date, the number of customers on that date, and the list of customer ids on that date.
Just looking at a way to tally up on a month by month basis. So taking this forward I've used a subquery to tally up as it goes
SELECT
ordersPerDate.*,
IF(
MONTH(ordersPerDate.order_date)=#thisMonth,
#runningTotal := #runningTotal+ordersPerDate.distinctOrders,
#runningTotal := 0
) AS ordersInThisMonth,
#thisMonth := MONTH(ordersPerDate.order_date)
FROM
(
SELECT
#thisMonth := 0,
#runningTotal := 0
) AS variableInit,
(
SELECT
order_date,
COUNT(*) AS distinctOrders,
COUNT(DISTINCT customer_id) AS distinctCustomers,
GROUP_CONCAT(DISTINCT customer_id ASC) AS customerIDs
FROM orders
GROUP BY order_date ASC
) AS ordersPerDate
And finally to clean it up, wrapped it in yet another subquery just to return the rows desired rather than the internal variables
Grouping on individual days
SELECT
collatedData.order_date,
collatedData.ordersInThisMonth AS count_orders,
collatedData.distinctCustomers AS count_customers,
collatedData.customerIDs AS customer_ids
FROM (
SELECT
ordersPerDate.*,
IF(
MONTH(ordersPerDate.order_date)=#thisMonth,
#runningTotal := #runningTotal+ordersPerDate.distinctOrders,
#runningTotal := 0
) AS ordersInThisMonth,
#thisMonth := MONTH(ordersPerDate.order_date)
FROM
(
SELECT
#thisMonth := 0,
#runningTotal := 0
) AS variableInit,
(
SELECT
order_date,
COUNT(*) AS distinctOrders,
COUNT(DISTINCT customer_id) AS distinctCustomers,
GROUP_CONCAT(DISTINCT customer_id) AS customerIDs
FROM orders
GROUP BY order_date ASC
) AS ordersPerDate
) AS collatedData
And now finally, following additional information from the OP, the end product
Grouping on calendar months
// Top level will sanitise the output
SELECT
collatedData.orderYear,
collatedData.orderMonth,
collatedData.distinctOrders,
collatedData.ordersInThisMonth AS count_orders,
collatedData.distinctCustomers AS count_customers,
collatedData.customerIDs AS customer_ids
FROM (
// This level up will iterate through calculating running totals
SELECT
ordersPerDate.*,
IF(
(ordersPerDate.orderYear,ordersPerDate.orderMonth) = (#thisYear,#thisMonth),
#runningTotal := #runningTotal+ordersPerDate.distinctOrders*ordersPerDate.distinctCustomers,
#runningTotal := 0
) AS ordersInThisMonth,
#thisMonth := ordersPerDate.orderMonth,
#thisYear := ordersPerDate.orderYear
FROM
(
SELECT
#thisMonth := 0,
#thisYear := 0,
#runningTotal := 0
) AS variableInit,
(
// Next level up will collate this to get per year, month, and per number of orders
SELECT
ordersPerDatePerUser.orderYear,
ordersPerDatePerUser.orderMonth,
ordersPerDatePerUser.distinctOrders,
COUNT(DISTINCT ordersPerDatePerUser.customer_id) AS distinctCustomers,
GROUP_CONCAT(ordersPerDatePerUser.customer_id) AS customerIDs
FROM (
// Inner query will get the number of orders for each year, month, and customer
SELECT
YEAR(order_date) AS orderYear,
MONTH(order_date) AS orderMonth,
customer_id,
COUNT(*) AS distinctOrders
FROM orders
GROUP BY orderYear ASC, orderMonth ASC, customer_id ASC
) AS ordersPerDatePerUser
GROUP BY
ordersPerDatePerUser.orderYear ASC,
ordersPerDatePerUser.orderMonth ASC,
ordersPerDatePerUser.distinctOrders DESC
) AS ordersPerDate
) AS collatedData
SELECT
substr(order_date,1,7) AS order_period,
count(*) AS number_of_orders,
count(DISTINCT orders.customer_id) AS number_of_customers,
GROUP_CONCAT(DISTINCT orders.customer_id) AS customers
FROM orders
GROUP BY substr(order_date,1,7)