Write results of SQL sub query back to database - mysql

I've been playing with this SQL code:
SELECT
id,
#prev := #curr as prev,
#curr := measure as curr,
#rank := IF(#prev > #curr, #rank+#ties, #rank) AS rank,
#ties := IF(#prev = #curr, #ties+1, 1) AS ties,
(1-#rank/#total) as percentrank
FROM
mytable,
(SELECT
#curr := null,
#prev := null,
#rank := 0,
#ties := 1,
#total := count(*) from mytable where measure is not null
) b
WHERE
measure is not null
ORDER BY
measure DESC
I'd like to write the calculated 'percentrank' back to each corresponding row of mytable in a column named "percentile," but I can't recall how to work in my update statement.
I appreciate the help.
Credit to http://code.openark.org/blog/mysql/sql-ranking-without-self-join for the SQL.

To update from a subquery, give the subquery an alias so that it's a derived table. Then use this syntax:
update YourTable
set SomeField = DerivedTable.something
, etc
from YourTable join
(subquery goes here) DerivedTable on YourTable.Whatever = DerivedTable.Whatever
etc

Related

Summarize data in a new table

The problem is as follows during the day data is put into status_table (this can reach up to 4000 entries a day
During the night i want to put the data in a more organized way in a storage table
(status_table_storage) and remove it from the status_table.
The idea is to group all the entries that have the same status until the status/error changes. from that point on a new group should start an be placed into the table. an example can be found in the fiddle
what is the best way to do this.
an example of the table structure can be found here: http://sqlfiddle.com/#!9/488524/1
Step by step explanation:
First you order the table by name and timestamp and initialize three user-defined variables.
SELECT s.* FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
As you can see, we can use a subquery for that. The ORDER BY is important, because there's no order in a relational database, unless you specify it.
Now, MySQL evaluates the SELECT clause in the specified order, therefore don't change the order here.
SELECT
s.*,
#prevName,
#prevStatus,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
When you execute this statement, you can see, that when we simply select the variables they hold the value of the previous row or NULL when it's the first row, that was read. Then the value of the current row is assigned to the variables. So we can compare now the current row with the previous row. If something changed, we simply increment the third variable, which is a number for each "group" that we are building.
SELECT
s.*,
#group_number := IF(#prevName != s.name OR #prevStatus != s.status, #group_number + 1, #group_number) AS group_number,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
So we incremented the #group_number when something changed and assigned the variable to itself if not, so that it doesn't change.
Now we can simply use this query as subquery and do a simple grouping.
SELECT
group_number AS id,
name,
status,
MIN(error) AS error,
MIN(timestamp) AS firstEntry,
MAX(timestamp) AS lastEntry,
COUNT(*) AS entries
FROM (
SELECT
s.*,
#group_number := IF(#prevName != s.name OR #prevStatus != s.status, #group_number + 1, #group_number) AS group_number,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
) sq
GROUP BY
group_number,
name,
status
see it working in this sqlfiddle

How to SELECT the total row number as a column

My first query
SELECT
id,
year_,
month_
FROM
(SELECT
tp.id,
YEAR(FROM_UNIXTIME(tp.visited_date)) as year_,
MONTH(FROM_UNIXTIME(tp.visited_date)) as month_,
#rn := IF(#prev = CONCAT(YEAR(FROM_UNIXTIME(tp.visited_date)),MONTH(FROM_UNIXTIME(tp.visited_date))), #rn + 1, 1) AS rn,
#prev := CONCAT(YEAR(FROM_UNIXTIME(tp.visited_date)),MONTH(FROM_UNIXTIME(tp.visited_date)))
FROM
tr_place tp
JOIN
(SELECT #prev := NULL, #rn := 0) AS vars
ORDER BY
YEAR(FROM_UNIXTIME(tp.visited_date)) DESC,
MONTH(FROM_UNIXTIME(tp.visited_date)) DESC) AS T1
WHERE
rn < 3;
Will returns the data set This is the result which I got from the query
The subquery in it
SELECT
tp.id,
YEAR(FROM_UNIXTIME(tp.visited_date)) as year_,
MONTH(FROM_UNIXTIME(tp.visited_date)) as month_,
#rn := IF(#prev = CONCAT(YEAR(FROM_UNIXTIME(tp.visited_date)),MONTH(FROM_UNIXTIME(tp.visited_date))), #rn + 1, 1) AS rn,
#prev := CONCAT(YEAR(FROM_UNIXTIME(tp.visited_date)),MONTH(FROM_UNIXTIME(tp.visited_date)))
FROM
tr_place tp
JOIN
(SELECT #prev := NULL, #rn := 0) AS vars
ORDER BY
YEAR(FROM_UNIXTIME(tp.visited_date)) DESC,
MONTH(FROM_UNIXTIME(tp.visited_date)) DESC;
Returns data The sub query will returns this data
I need the subquery's greatest rn as a column in the first query.
How can I achieve that?
You may be having a problem with variable assignment. You should not be referencing variables in multiple expressions in the select. So, the correct way to write the first query is:
SELECT id, year_, month_
FROM (SELECT tp.id,
YEAR(FROM_UNIXTIME(tp.visited_date)) as year_,
MONTH(FROM_UNIXTIME(tp.visited_date)) as month_,
(#rn := IF (#prev = CONCAT(YEAR(FROM_UNIXTIME(tp.visited_date)), MONTH(FROM_UNIXTIME(tp.visited_date))), #rn,
if(#prev := CONCAT(YEAR(FROM_UNIXTIME(tp.visited_date)), MONTH(FROM_UNIXTIME(tp.visited_date))), 1, 1
)
) as rn
FROM tr_place tp JOIN
(SELECT #prev := NULL, #rn := 0) AS vars
ORDER BY YEAR(FROM_UNIXTIME(tp.visited_date)) DESC,
MONTH(FROM_UNIXTIME(tp.visited_date)) DESC
) t1
WHERE rn < 3;
Note the variable assignments are all in a single expression.
This may fix your issue.

Ranking order with having same rank repeating to the same level rank category

I am little new to sql and I need to rank my votes table based on votes assigned to each person and rank should be same for the similar votes.
My table would be like
CREATE TABLE votes ( name varchar(10), votes INT );
INSERT INTO votes VALUES
('Ann',100), ('Jones',151), ('Smith',100), ('Rose',240), ('Lee',500), ('Adhams',500);
In my display rows I need to have the rank column first and it should display the rank based on the highest number of votes. Importantly same number of votes need to have the same rank.
I have tried it several times and failed to do it...
Please help me
thanks
You can try below code. There are many links available which would be easily find your answer if you have carefully searched.
SET #rank=0;
SET #votes=0;
select x.rank as rank, x.name as name, x.votes as votes
from(
select #rank:=if(#votes=votes,#rank, #rank +1)AS rank,
#votes := votes,
v.name,
v.votes
from votes v
order by v.votes desc) as x;
To generate RANK, you first need a row number in the order of decreasing votes (variable #rn) and then based on the previous value of vote, create rank (variable #rank).
Try this:
SELECT v.*,
#rank := if((#rn := #rn + 1) is not null,
if (#votes = votes,
#rank,
if ((#votes := votes) is not null, #rn, 1)
),1
) rank
FROM votes v
CROSS JOIN (
SELECT
#votes := NULL,
#rank := 0,
#rn := 0
) t
ORDER BY v.votes DESC;
Demo
About this:
(#rn := #rn + 1) is not null
Since, the expression #rn := #rn + 1 can't be null, we use it to our advantage by not duplicating the whole logic twice.
You can use variables for this:
SELECT #rnk := IF(#v = votes, #rnk,
IF(#v := votes, #rnk + 1, #rnk + 1) AS rnk
name, votes
FROM mytable
CROSS JOIN (SELECT #rnk := 0, #v = :0) AS vars
ORDER BY votes DESC

Getting incorrect rank using WHERE clause in mySQL

http://sqlfiddle.com/#!9/083f5d/1
It's returning 6th rank instead of 1st rank in above SQL query. What changes I need to do in SQL query so it provides 1st rank because I have used WHERE condition to check mpkid and roundpkid (WHERE mpkid=37 AND roundpkid=3).
Here is the query:
SELECT mpkid, totalvote, rank
FROM (
SELECT mpkid, roundpkid, totalvote,
#n := IF(#g = totalvote, #n, #n + 1) rank,
#g := totalvote
FROM tr_msearch_vote_summary,
(SELECT #n := 0) i
ORDER BY totalvote DESC
) q
WHERE mpkid=37 AND roundpkid=3
What I want:
Please see sqlfiddle first. There is only one record for round #3, and it is 37 so I want that it should display rank #1 for mpkid #37 and round #3 but it is giving rank #6.
You are applying the WHERE condition after the ranking is already complete. I'm guessing you want to do the ranking after the WHERE is applied:
SELECT t.mpkid, t.roundpkid,
#n := IF(#g = t.totalvote, #n, #n + 1) rank,
#g := t.totalvote totalvote
FROM tr_msearch_vote_summary t, (SELECT #n := 0) i
WHERE t.mpkid=37 AND t.roundpkid=3
ORDER BY t.totalvote DESC
You should also initialise #g to make sure it isn't preset in another query:
SELECT t.mpkid, t.roundpkid,
#n := IF(#g = t.totalvote, #n, #n + 1) rank,
#g := t.totalvote totalvote
FROM tr_msearch_vote_summary t, (SELECT #n := 0, #g := NULL) i
WHERE t.mpkid=37 AND t.roundpkid=3
ORDER BY t.totalvote DESC
UPDATE
If you want the ranking grouped by round and then to simply SELECT by mpkid, this is a more powerful query:
SELECT mpkid,
roundpkid,
totalvote,
rank
FROM (
SELECT t.mpkid,
#n := CASE
WHEN #r = t.roundpkid AND #g = t.totalvote THEN #n
WHEN #r = t.roundpkid THEN #n + 1
ELSE 1
END rank,
#r := t.roundpkid roundpkid,
#g := t.totalvote totalvote
FROM tr_msearch_vote_summary t, (SELECT #n := 0, #g := NULL, #r := NULL) i
ORDER BY t.roundpkid, t.totalvote DESC
) r
WHERE mpkid = 37;
Note that you do not need to supply the roundpkid. See updated fiddle
Please try this query,
SELECT mpkid, totalvote, rank
FROM (
SELECT mpkid, roundpkid, totalvote,
#n := IF(#g = totalvote, #n, #n + 1) rank,
#g := totalvote
FROM tr_msearch_vote_summary,
(SELECT #n := 0) i
WHERE roundpkid=3
ORDER BY totalvote DESC
) q
WHERE mpkid=37
And here is your updated sqlfiddle. http://sqlfiddle.com/#!9/083f5d/9

How to implement paging in ranking query?

I am trying to implement a ranking with MySQL. I have found a good article about it without using self joins (ranking without self join).
SELECT
score_id, student_name, score,
#prev := #curr,
#curr := score,
#rank := IF(#prev = #curr, #rank, #rank+1) AS rank
FROM
score,
(SELECT #curr := null, #prev := null, #rank := 0) sel1
ORDER BY score DESC
Regarding that I will have a lot of rankings it would be nice to add paging.
First idea was to use LIMIT, but it fails (rank isn't continued, so it starts with one again).
I can't just multiple the page with the limit, because it is possible to have scores multiple times.
How can I achieve paging with the query?
You could possibly do it similar to LIMIT, by adding an outer query, like so:
SELECT score_id, student_name, score, rank
FROM
(SELECT
score_id, student_name, score,
#prev := #curr,
#curr := score,
#rank := IF(#prev = #curr, #rank, #rank+1) AS rank
FROM
score,
(SELECT #curr := null, #prev := null, #rank := 0) sel1
ORDER BY score DESC) AS b
WHERE rank BETWEEN 1 AND 10
Then you could do BETWEEN 11 AND 20, etc. for the subsequent pages. Might not be the best way to do it, but it is one way :)
I found another solution working for me.
I have added a new column rank to the table and calculate the rank with an UPDATE query.
UPDATE score s, (
SELECT
score_id, student_name, score,
#prev := #curr,
#curr := score,
#rank := IF(#prev = #curr, #rank, #rank+1) AS rank
FROM
score,
(SELECT #curr := null, #prev := null, #rank := 0) sel1
ORDER BY score DESC
) r
SET s.rank = r.rank WHERE s.score_id = r.score_id
Now, it is easily possible to limit the result.