MySQL - Rank per month across several months - mysql

I'm using MySQL database. I'm looking to generate the rank of customers month by month for the last 6 months.
I just got the following query to work to determine the rank of a customer in a monthly poll. This reports the rank correctly only if the date range in one month.
select
t1.*,
#rownum := #rownum + 1 AS RANK
from
(
select
date_format(EVE_DATE,'%Y-%m') as MON_DATE,
CUST,
SUM(POLL) as SCORE
from
TABLE
where
EVE_DATE >= '2016-01-01' and EVE_DATE <= '2016-01-31'
group by
MON_DATE,
CUST
order by
SCORE desc
)t1,
(SELECT #rownum := 0) r
order by
RANK DESC
The problem I have is, if I were to change the date range to span over multiple months, then the rank shown isn't right. I've dug a bit deeper & realize that, the problem is due to the fact that when the number of days span across months, every customer gets listed as many times as the number of months in question. Thereby, number of rows in the output is number_of_customers * number of months which means the rank per month is no longer a meaningful value.
For example, if there are 100 customers & if I were to calculate the rank for one month, the maximum rank I can have is 100 which is correct. However, if I considered 2 months, the rank can range from 1 to 200 which is incorrect. This is because there are only 100 customers, but, are appearing twice due to 2 months being the consideration.
How could I correct the below query to show me rank per month correctly?
select
t2.*
from
(
select
t1.*,
#rownum := #rownum + 1 AS RANK
from
(
select
date_format(EVE_DATE,'%Y-%m') as MON_DATE,
CUST,
SUM(POLL) as SCORE
from
TABLE
where
EVE_DATE >= (curdate() - INTERVAL 3 MONTH)
group by
MON_DATE,
CUST
order by
SCORE desc
)t1,
(SELECT #rownum := 0) r
order by
RANK DESC
)t2
where
t2.CUST= 'customerA'
order by
t2.MON_DATE desc
I'd appreciate any help here to get me going please.

I think you want the inner subquery to aggregate only by customer, not by customer and date:
select t1.*,
#rownum := #rownum + 1 AS RANK
from (select CUST, SUM(POLL) as SCORE
from TABLE
where EVE_DATE >= '2016-01-01' and EVE_DATE <= '2016-01-31'
group by CUST
order by SCORE desc
) t1 cross join
(SELECT #rownum := 0) r
order by RANK DESC;

Related

Incrementing MySql variable by SUM() not correct

I am trying to put together a query that groups records by date along with a total for that particular date (there can be multiple entries in a day) but I also need a running total that I intended on using a MySQL variable for. My issue is that the cumulative total column seems to contain the SUM() for just that date.
So this works fine for the daily totals
SELECT
YEAR(log_at) as year,
MONTH(log_at) as month,
DAY(log_at) as day,
SUM(ev) as dailyTotal,
count(*) as count
FROM tracks
WHERE user_id = 1
GROUP BY year, month, day
ORDER BY year, month, day ASC
Add what I thought was going to be a fairly simple variable in there to keep track of the running total
SET #cumulative := 0;
SELECT
YEAR(log_at) as year,
MONTH(log_at) as month,
DAY(log_at) as day,
SUM(ev) as dailyTotal,
#cumulative := #cumulative + sum(ev) AS cumulative,
count(*) as count
FROM tracks
WHERE user_id = 1
GROUP BY year, month, day
ORDER BY year, month, day ASC
And the cumulative variable just contains the total for that day. But if I change it to increment by 1 instead of the SUM() it seems to work correctly
Any advice for achieving the desired behaviour is greatly appreciated!
You have to first create the whole selection and only then do your cumulative-stuff because you can't append the sum of a column of your GROUP BY at the same time (I don't really know why dough)
SELECT
`year`,
`month`,
`day`,
`dailyTotal`,
#cum := #cum + `dailyTotal` as `cumulative`,
`count`
FROM
(
SELECT
YEAR(log_at) as year,
MONTH(log_at) as month,
DAY(log_at) as day,
SUM(ev) as dailyTotal,
count(*) as count
FROM tracks
WHERE user_id = 1
GROUP BY year, month, day
ORDER BY year, month, day ASC
) a
JOIN (SELECT #cum := 0) b
I tested it with a bit less intensive tables... maybe I got this one wrong here. But I hope you get the theory.

mysql row number is not working properly with order

I have a Store Database with the following tables :
1 - Provider(ProviderID,Name,Country)
2 - Product(ProductID,ProviderID,ProductPrice)
3 - Command(CommandID,ProductID,ProductQuantity,CommandDate)
I made a query that counts the gain by year
SELECT EXTRACT(YEAR FROM COMMAND_DATE) AS year,
SUM(PRODUCT_PRICE*PRODUCT_QUANTITY) AS gain
FROM PRODUCT JOIN COMMAND ON PRODUCT.PRODUCT_ID=COMMAND.PRODUCT_ID
GROUP BY year
ORDER BY year
This is the output :
Now I want to display the row number just like Oracle, so I used this query :
SET #currentRow = 0;
SELECT #currentRow := #currentRow + 1 AS counter,
EXTRACT(YEAR FROM COMMAND_DATE) AS year,
SUM(PRODUCT_PRICE*PRODUCT_QUANTITY) AS gain
FROM PRODUCT JOIN COMMAND ON PRODUCT.PRODUCT_ID=COMMAND.PRODUCT_ID
GROUP BY year
ORDER BY year
But I don't get what I want
It seems that order is affecting the row number. I want it to start from 1. Ordering by counter is not an option because I need to order by years.
This is how I want it to be :
1 2014 1863
2 2015 889
3 2016 2626
...
With group by, you need a subquery:
SELECT (#currentRow := #currentRow + 1) AS counter, y.*
FROM (SELECT EXTRACT(YEAR FROM COMMAND_DATE) AS year,
SUM(PRODUCT_PRICE*PRODUCT_QUANTITY) AS gain
FROM PRODUCT JOIN
COMMAND
ON PRODUCT.PRODUCT_ID = COMMAND.PRODUCT_ID
GROUP BY year
ORDER BY year
) y CROSS JOIN
(SELECT #currentRow := 0) params;
Or, you can use ROW_NUMBER() OVER (ORDER BY YEAR), if you are using MySQL 8+.

count consecutive days (streak) and number of records for current day

The following code was taken from another question on SO. Original Q&A
I would like to count the number of consecutive days (Streak) with records since today AND also how many records were made today. I'm using this to send notifications. If a user submits a new record the same day, they should not get a second notification telling them that they are on a streak (they were made aware the first time they submitted a record for the current day).
I tried adding a COUNT() function before #streak, after the first SELECT and pretty much everywhere that seemed reasonable but this query is too complex for me to figure it out.
SELECT streak + 1 as realStreak
FROM (
SELECT dt,
#streak := #streak+1 streak,
datediff(curdate(),dt) diff
FROM (
SELECT distinct date(dt) dt
FROM glucose where uid = 1
) t1
CROSS JOIN (SELECT #streak := -1) t2
ORDER BY dt desc
)
t1 where streak = diff
ORDER BY streak DESC LIMIT 1
http://sqlfiddle.com/#!9/45d386/1/0
The result of the above should be:
realStreak | RecordsToday
3 | 3
Just add a subquery for the today check
SELECT streak + 1 as realStreak,cdt
FROM (
SELECT dt,
#streak := #streak+1 streak,
datediff(curdate(),dt) diff
FROM (
SELECT distinct date(dt) dt
FROM gl where uid = 1
) t1
CROSS JOIN (SELECT #streak := -1) t2
ORDER BY dt desc
)t1
JOIN
(SELECT COUNT(CASE WHEN DATE(dt)=CURDATE() THEN 1 END) cdt FROM gl)x
where streak = diff
ORDER BY streak DESC LIMIT 1

Stop query when SUM is reached (mysql)

I have a database with colums I am working on. What I am looking for is the date associated with the row where the SUM(#) reaches 6 in a query. The query I have now will give the date when the number in the colum is six but not the sum of the previous rows. example below
Date number
---- ------
6mar16 1
8mar16 4
10mar16 6
12mar16 2
I would like to get a query to get the 10mar16 date because on that date the number is now greater than 6. Earlier dates wont total up to six.
Here is an example of a query i have been working on:
SELECT max(date) FROM `numbers` WHERE `number` > 60
You could use this query, which tracks the accumulated sum and then returns the first one that meets the condition:
select date
from (select * from mytable order by date) as base,
(select #sum := 0) init
where (#sum := #sum + number) >= 6
limit 1
SQL Fiddle
Most databases support ANSI standard window functions. In this case, cumulative sum is your friend:
select t.*
from (select t.*, sum(number) over (order by date) as sumnumber
from t
) t
where sumnumber >= 10
order by sumnumber
fetch first 1 row only;
In MySQL, you need variables:
select t.*
from (select t.*, (#sumn := #sumn + number) as sumnumber
from t cross join (select #sumn) params
order by date
) t
where sumnumber >= 10
order by sumnumber
fetch first 1 row only;
Awesome!!!! It seems to be working great. Here is the code that I used.
SELECT date, id, crewname
FROM (select * FROM flightrecord WHERE `crewname` = 'brayn'
ORDER BY dutyTimeArrive DESC) as base,
(select #sum := 0) init
WHERE (#sum := #sum + tankDropCount) >= 6
limit 1

specific status on consecutive days

I have a MySQL table ATT which has EMP_ID,ATT_DATE,ATT_STATUS with ATT_STATUS with different values 1-Present,2-Absent,3-Weekly-off. I want to find out those EMP_ID's which have status 2 consecutively for 10 days in a given date range.
Please help
Please have a try with this:
SELECT EMP_ID FROM (
SELECT
IF((#prevDate!=(q.ATT_DATE - INTERVAL 1 DAY)) OR (#prevEmp!=q.EMP_ID) OR (q.ATT_STATUS != 2), #rownum:=#rownum+1, #rownum:=#rownum) AS rownumber, #prevDate:=q.ATT_DATE, #prevEmp:=q.EMP_ID, q.*
FROM (
SELECT
EMP_ID
, ATT_DATE
, ATT_STATUS
FROM
org_tb_dailyattendance, (SELECT #rownum:=0, #prevDate:='', #prevEmp:=0) vars
WHERE ATT_DATE BETWEEN '2013-01-01' AND '2013-02-15'
ORDER BY EMP_ID, ATT_DATE, ATT_STATUS
) q
) sq
GROUP BY EMP_ID, rownumber
HAVING COUNT(*) >= 10
The logic is, to first sort the table by employee id and the dates. Then introduce a rownumber which increases only if
the days are not consecutive or
the employee id is not the previous one or
the status is not 2
Then I just grouped by this rownumber and counted if there are 10 rows in each group. That should be the ones who were absent for 10 days or more.
Have you tried something like this
SELECT EMP_ID count(*) as consecutive_count min(ATT_DATE)
FROM (SELECT * FROM ATT ORDER BY EMP_ID)
GROUP BY EMP_ID, ATT_DATE
WHERE ATT_STATUS = 2
HAVING consecutive_count > 10