I have a MySQL table with two columns: takenOn(datetime), and count(int). count contains the number of steps I have taken.
I'm trying to write a query that will tell me the time when I meet my goal of 10,000 steps every day.
So far, I have the following query:
SET #runningTotal=0;
SELECT
`Date`,
DATE_FORMAT(MIN(takenOn), '%l:%i %p') AS `Time`,
TotalCount
FROM
(SELECT
DATE(s.takenOn) AS `Date`,
s.takenOn,
s.`count`,
#runningTotal := #runningTotal + s.`count` AS TotalCount
FROM
(select * from step where DATE(takenOn) = '2016-10-29') s) temp
WHERE TotalCount >= 10000;
This works, but of course gives me the MIN(takenOn) for October 29th only. How can I expand this query to give me MIN(takenOn) for all possible dates in the table?
Thank you!
I am assuming that the steps you care about are all within one day. You are on the right track. Here is the code for multiple days:
SELECT `Date`, DATE_FORMAT(MIN(takenOn), '%l:%i %p') AS `Time`,
MIN(TotalCount)
FROM (SELECT DATE(s.takenOn) AS `Date`,
s.takenOn,
s.`count`,
(#runningTotal := if(#d = DATE(s.takenOn), #runningTotal + s.`count`,
if(#d := DATE(s.takeOn), s.`count`, s.`count`)
)
) AS TotalCount
FROM step s CROSS JOIN
(SELECT #runningTotal := 0, #d = '') params
ORDER BY takenOn
) s
WHERE TotalCount >= 10000
GROUP BY `Date`;
Note that all the variable assignments are in one expression. This is important because MySQL does not guarantee the order of evaluation of expressions in a SELECT. So, if you split the assignments across more than one expression, you are not guaranteed that the code will work.
You can use the Group By and Having clause to achieve this, refer to this example:
SELECT
sum(takenON),date
FROM
step
GROUP BY
day(date)
Having SUM(takenON)>150
Related
I'm trying in MySql to count the number of users created each day and then get an accumulative figure on a row by row basis. I have followed other suggestions on here, but I cannot seem to get the accumulation to be correct.
The problem is that it keeps counting from the base number of 200 and not taking account of previous rows.
Where was I would expect it to return
My Sql is as follows;
SELECT day(created_at), count(*), (#something := #something+count(*)) as value
FROM myTable
CROSS JOIN (SELECT #something := 200) r
GROUP BY day(created_at);
To create the table and populate it you can use;
CREATE TABLE myTable (
id INT AUTO_INCREMENT,
created_at DATETIME,
PRIMARY KEY (id)
);
INSERT INTO myTable (created_at)
VALUES ('2018-04-01'),
('2018-04-01'),
('2018-04-01'),
('2018-04-01'),
('2018-04-02'),
('2018-04-02'),
('2018-04-02'),
('2018-04-03'),
('2018-04-03');
You can view this on SqlFiddle.
Use a subquery:
SELECT day, cnt, (#s := #s + cnt)
FROM (SELECT day(created_at) as day, count(*) as cnt
FROM myTable
GROUP BY day(created_at)
) d CROSS JOIN
(SELECT #s := 0) r;
GROUP BY and variables have not worked together for a long time. In more recent versions, ORDER BY also needs a subquery.
I have a MySQL table for fictional fitness app.
Let's say that app is monitoring user progress on doing pushups day by day.
TrainingDays
id | id_user | date | number_of_pushups
Now, I need to find if user have ever managed to do more than 100 pushups 5 days in a row.
I know this is probably doable by fetching all days and then making some php loops, but I wonder if there is possibility to do this in plain mysql...
In MySQL, the easiest way is to use variables. The following gets all sequences of days with 100 or more pushups:
select grp, count(*) as numdaysinarow
from (select (date - interval rn day) as grp, td.*
from (select td.*,
(#rn := if(#i = id_user, #rn + 1
if(#i := id_user, 1, 1)
) as rn
from trainingdays td cross join
(select #rn := 0, #i := NULL) vars
where number_of_pushups >= 100
order by id_user, date
) td
) td
group by grp;
This uses the observation that when you subtract a sequence of numbers from a series of dates that increment, then the resulting value is constant.
To determine if there are 5 or more days in a row, use max():
select max(numdaysinarow)
from (select grp, count(*) as numdaysinarow
from (select (date - interval rn day) as grp, td.*
from (select td.*,
(#rn := if(#i = id_user, #rn + 1
if(#i := id_user, 1, 1)
) as rn
from trainingdays td cross join
(select #rn := 0, #i := NULL) vars
where number_of_pushups >= 100
order by id_user, date
) td
) td
group by grp
) td;
Your app can then check the value against whatever minimum you like.
Note: this assumes that there is only one record per day. The above can easily be modified if you are looking for the sum of the number of pushups on each day.
Order of records shouldn't be relied on, e.g. with ORDER BY you can change the sequence.
However, you have many functions at hand in a database, which also enables you to use less PHP. What you want is SUM function. Combined with a WHERE clause, this should get you started:
SELECT SUM(number_of_pushups) AS sum_pushups
FROM TrainingDays
WHERE date >= :start_day
AND user_id = :user_id
I have an data set that simulates the rate of return for a trading account. There is an entry for each day showing the balance and the open equity. I want to calculate the yearly, or quarterly, or monthly change and percent gain or loss. I have this working for daily data, but for some reason I can't seem to get it to work for yearly data.
The code for daily data follows:
SELECT b.`Date`, b.Open_Equity, delta,
concat(round(delta_p*100,4),'%') as delta_p
FROM (SELECT *,
(Open_Equity - #pequity) as delta,
(Open_Equity - #pequity)/#pequity as delta_p,
(#pequity:= Open_Equity)
FROM tim_account_history p
CROSS JOIN
(SELECT #pequity:= NULL
FROM tim_account_history
ORDER by `Date` LIMIT 1) as a
ORDER BY `Date`) as b
ORDER by `Date` ASC
Grouping by YEAR(Date) doesn't seem to make the desired difference. I have tried everything I can think of, but it still seems to return daily rate of change even if you group by month or year, etc. I think I'm not using windowing correctly, but I can't seem to figure it out. If anyone knows of a good book about this sort of query I'd appreciate that also.
Thanks.sqlfiddle example
Using what Lolo contributed, I have added some code so the data comes from the last day of the year, instead of the first. I also just need the Open_Equity, not the sum.
I'm still not certain I understand why this works, but it does give me what I was looking for. Using another select statement as a from seems to be the key here; I don't think I would have come up with this without Lolo's help. Thank you.
SELECT b.`yyyy`, b.Open_Equity,
concat('$',round(delta, 2)) as delta,
concat(round(delta_p*100,4),'%') as delta_p
FROM (SELECT *,
(Open_Equity - #pequity) as delta,
(Open_Equity - #pequity)/#pequity as delta_p,
(#pequity:= Open_Equity)
FROM (SELECT (EXTRACT(YEAR FROM `Date`)) as `yyyy`,
(SUBSTRING_INDEX(GROUP_CONCAT(CAST(`Open_Equity` AS CHAR) ORDER BY `Date` DESC), ',', 1 )) AS `Open_Equity`
FROM tim_account_history GROUP BY `yyyy` ORDER BY `yyyy` DESC) p
CROSS JOIN
(SELECT #pequity:= NULL) as a
ORDER BY `yyyy` ) as b
ORDER by `yyyy` ASC
Try this:
SELECT b.`Date`, b.Open_Equity, delta,
concat(round(delta_p*100,4),'%') as delta_p
FROM (SELECT *,
(Open_Equity - #pequity) as delta,
(Open_Equity - #pequity)/#pequity as delta_p,
(#pequity:= Open_Equity)
FROM (SELECT YEAR(`Date`) `Date`, SUM(Open_Equity) Open_Equity FROM tim_account_history GROUP BY YEAR(`Date`)) p
CROSS JOIN
(SELECT #pequity:= NULL) as a
ORDER BY `Date` ) as b
ORDER by `Date` ASC
I have a dataset that I need to do some aggregation on to display.
Date, Rank, Vote
07/20/2013, 8, -1
07/21/2013, 8, -1
07/23/2013, 7, -1
07/24/2013, 6, 1
07/25/2013, 7, 1
07/26/2013, 7, -1
07/27/2013, 8, 1
07/28/2013, 8, 1
07/29/2013, 8, -1
I'd like to group by consecutive ranks, summing the vote, and choosing the first of the grouped dates. The above data set would return:
07/20/2013,8,-2
07/23/2013,7,-1
07/24/2013,6,1
07/25/2013,7,0
07/27/2013,8,1
My first thought was GROUP BY Date and Rank but that wouldn't consolidate multiple days. I can do this after the fact by looping thru the data or I can use a cursor within a stored procedure but I'm curious if there is a simpler way with a SQL query.
This does it:
SELECT firstDate, Rank, SUM(vote) Votes
FROM (
SELECT #first_date := CASE WHEN Rank = #prev_rank
THEN #first_date
ELSE Date
END firstDate,
#prev_rank := Rank curRank,
data.*
FROM (SELECT #first_date := NULL, #prev_rank := NULL) init
JOIN (SELECT Date, Rank, Vote
FROM MyTable
Order by Date) data
) grouped
GROUP BY firstDate, Rank
SQLFIDDLE
Most straightforward way I can see, is what you already pointed out.
Use the Group By SQL:
SELECT date, rank, SUM(vote) FROM YourTable
GROUP BY date, rank
Fiddle Demo: http://sqlfiddle.com/#!2/d65d5c/3
Iterate through this record set in your program and do what is needed to get your data.
(If you tag your question with a programming language, I can show you how).
So basically no, I can't see any better way to do this. As far I can see this would end up in a fairly complicated and slow SQL query. But maybe someone can teach me better.
I have a table containing date, id, and value, with about 1000 id rows per date. I need to calculate the percentile rank of each row, by date. I am using the following code for percentile rank for a single date, but with over 10 years of daily data this is very inefficient to run date-by-date. Seems that it should be able to be formulated in MySQL but I've not been able to make it work.
Date ID Value
date1 01 -7.2
date1 02 0.6
date2 01 1.2
date2 02 3.8
SELECT c.id, c.value, ROUND( (
(#rank - rank) / #rank ) *100, 2) AS rank
FROM (
SELECT * , #prev := #curr , #curr := a.value,
#nxtRnk := #nxtRnk + 1,
#rank := IF( #prev = #curr , #rank , #nxtRnk ) AS rank
FROM (
SELECT id, value
FROM temp
WHERE date = '2013-06-28'
) AS a, (
SELECT #curr := NULL , #prev := NULL , #rank :=0, #nxtRnk :=0
) AS b
ORDER BY value DESC
) AS c
So basically I want to SELECT DISTINCT(date), and then for each date perform the above SELECT, which is preceeded by INSERT INTO table2( ... ) to write the results to table2.
Thanks for any help,
Hugh
I finally developed an acceptable solution by using a temporary table. Maybe not the optimum solution, but it works in about 5 sec on a million + record table.
My temporary table (t1) contains date and the count of rows for date.
The third select above is changed to
SELECT t1.date, t1.cnt, id, value FROM t1 LEFT JOIN temp ON(t1.date = temp.date)
Also, the calculations in the first SELECT above were changed to use c.cnt rather than #rank, and an #prevDate variable was created to reset the rank count on date changes.
Thanks to anyone who looked at this and tried to work up a solution.
I was trying to solve this for quite some time and then I found the following answer. Honestly brilliant. Also quite fast even for big tables (the table where I used it contained approx 5 mil records and needed a couple of seconds).
SELECT
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(field_name ORDER BY
field_name SEPARATOR ','), ',', 95/100 * COUNT(*) + 1), ',', -1) AS DECIMAL)
AS 95th Per
FROM table_name;
As you can imagine just replace table_name and field_name with your table's and column's names.
For further information check Roland Bouman's original post