I have some historical data in a MySQL database, and I'm making graphs of it. Currently, my graphs look like this:
I'd like, however, to get the change in the value over time - i.e. show the change since the last measurement. Something like this (obviously the data isn't correct):
How can I write a MySQL query that gets the delta of a value when compared with the previous row?
This is my current query, with help from juergen d:
SELECT `timestamp`, total_users, total_users - #prev AS col1, #prev := total_users, FROM stats, (select #prev := 0) p GROUP BY DAY(`timestamp`), floor(HOUR(`timestamp`)/2)
Generally
Use a variable that holds the last records' value to calculate the delta
select some_column,
some_column - #prev as delta,
#prev := some_column
from your_table, (select #prev := 0) p
SQLFiddle demo
The actual query
SELECT DAY(`timestamp`) as day_ts,
floor(HOUR(`timestamp`)/2) as hour_ts
sum(total_users)
sum(total_users) - #prev AS col1,
#prev := sum(total_users)
FROM stats, (select #prev := 0) p
GROUP BY day_ts, hour_ts
Related
Can someone explain how to write an update query to update a column "newBalance" in table "abcd" with the O/P of balance in the following query ?
I would like to update newBalance column with the balance calculated for each row using the below query.
SELECT id
, inAmt
, outAmt
, #prev := if(id = 1, #prev := #curBalance , #prev + (#prevOut-#prevIn)) as balance
, #prevIn :=inAmt
, #prevOut := outAmt
from (select #prev := 0) as i
, abcd
order
by id
Any help is highly appreciated.
You can use update with a join:
update abcd join
(select id, inAmt, outAmt,
#prev := if(id = 1, #prev := #curBalance , #prev + (#prevOut-#prevIn)) as balance,
#prevIn := inAmt,
#prevOut := outAmt
from (select #prev := 0) as i cross join
abcd
order by id
) x
on abcd.id = x.id
set abcd.newbalance = x.balance;
All that said, your query is definitely not guaranteed to work and can fail at any time. You are using variables in one expression and assigning them in another. MySQL does not guarantee the order of evaluation of expressions.
However, that was not the question that you asked. If you want the query fixed, ask a new question with sample data and desired results.
I am working on migration from MS SQL Server to MariaDB 10.0. I have query which use RANK() PARTITION BY. I already created some kind of RANK() implementation on my table but it's not working properly.
The original query was:
RANK() OVER (PARTITION BY visits.id_partner ORDER BY visits.updated_at DESC) AS rank
My implementation for MariaDB/MySQL:
SELECT
...,
(
CASE visits.id_partner
WHEN #currId THEN
#curRow := #curRow + 1
ELSE
#curRow := 1 AND #currId := visits.id_partner
END
) AS rank
FROM
records rec
JOIN visits ON visits.id = rec.id_visit,
(
SELECT
#curRank := 0,
#currId := NULL
) r
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
I want to select row ranked by id_partner order by updated_at field. When id_partner is same as on row before RANK should increase by 1. When is different than before, it should reset to 1.
But my query is not working at all. I have still rank 1 on all rows. Can you help me find mistake?
Thank you for help!
It is tricky to use variables in MySQL/MariaDB. A variable should only be used and assigned in one statement (as you do correctly). However, AND can short-circuit variable assignment.
I use a construct like this for ROW_NUMBER(). RANK() is actually a bit of a pain . . . DENSE_RANK() and ROW_NUMBER() are simpler. However, this seems to be the code that you are aiming for:
SELECT ...,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM records rec JOIN
visits
ON visits.id = rec.id_visit CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC;
EDIT:
In MySQL (and presumably in MariaDB), sometimes variables don't work quite right unless you use a subquery. So, try this:
SELECT . . .,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM (SELECT ...
FROM records rec JOIN
visits
ON visits.id = rec.id_visit
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
) t CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params;
I'm looking for a way on how can I get the rank of the duplicate entries of my table based on their create dates. The older the date will be the one who will get the rank 1 and so on for the next duplicates. It should look like this:
id number create_date rank
1 1 02/03 1
2 1 02/04 2
3 3 02/03 1
4 4 02/03 1
5 4 02/04 2
6 4 02/05 3
I tried searching for this but I can't understand well on how they implement it or more like it is not the way I wanted it to be. Hope someone can help me on this.
select
t.*,
#rank := if(#prevDate = create_date, #rank, #rank + 1) as rank,
#prevDate := create_date
from
your_table t
, (select #rank := 0, #prevDate := null) var_init
order by create_date, id
Explanation:
Here
, (select #rank := 0, #prevDate := null) var_init
the variables are initalized. It's the same as writing
set #rank = 0;
set #prevDate = null;
select ... /*without the crossjoin*/;
Then the order of the columns in the select clause is important. First we check with this line
#rank := if(#prevDate = create_date, #rank, #rank + 1) as rank,
if the current row has the same date as the previous row. The #prevDate holds the value of the previous row. If yes, the #rank variable stays the same, if not it's incremented.
In the next line
#prevDate := create_date
we set the #prevDate variable to the value of the current row. That's why the order of the columns in the select clause is important.
Finally, since we're checking with the previous row, if the dates differ, the order by clause is important.
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
I have a query that ranks results in MySQL:
SET #rank := 0;
SELECT Name, Score, #rank := #rank + 1
FROM Results
ORDER BY Score
This works fine until I try to base the ranking on the average score:
SET #rank := 0;
SELECT Name, AVG(Score) as AvScore, #rank := #rank + 1
FROM Results
ORDER BY AvScore
If I run this I get just the one record back because of the AVG. However, if I add a GROUP BY on Name so that I can get the averages listed for everyone, this has the effect of messing up the correct rankings.
I know the answer's probably staring me in the face but I can't quite get it. How can I output a ranking for each name based on their average result?
You need to use a sub-query:
SET #rank := 0;
SELECT a.name,
a.avscore,
#rank := #rank + 1
FROM (SELECT name,
Avg(score) AS AvScore
FROM results
GROUP BY name) a
ORDER BY a.avscore
You have to order first and then select rank from a derived table:
SELECT Name, AvScore, #rank := #rank + 1
FROM (
SELECT Name, AVG(AvScore) AS AvScore FROM Results
GROUP BY Name ORDER BY AVG(AvScore)
) t1, (SELECT #rank = 0) t2;