User-defined variable in ranking statement - mysql

I will appreaciate any help on this issue. I already spent hours without any real solution.
I have a SQL
SELECT to_place, rank
FROM
(SELECT g1.to_place as to_place, g1.pcount as pcount,
#rank := IF(#current_to_place = g1.to_place, #rank + 1, 1) AS rank,
#current_to_place := g1.to_place
FROM
(select
to_place, count(*) as pcount
from temp_workflows
group by to_place
order by to_place,pcount desc) g1
ORDER BY g1.to_place, g1.pcount DESC) ranked
In table g1, I am grouping my data to find the most common occurrence of to_place.And then I want to rand those occurrences in ascending order (so I can later select top 3 of the most common occurrences per each to_place category.
The issue is that the user-defined variable is unpredictable (#rank is sometimes always 1) which probably is related to the fact that in one statement, I should not reference the same variable (current_to_place). I read a lot about using separate statements etc. but I could find a way to write my statement in a different way. How can I define #current_to_place elsewhere so the result is the same?
Thanks in advance for your help.

I think you should be testing pcount to get rank and you should initialise variables
DROP TABLE IF EXISTS T;
CREATE TABLE T
(to_place int);
insert into t values (1),(2),(2),(3),(3),(3);
SELECT to_place, rank
FROM
(
SELECT g1.to_place as to_place, g1.pcount as pcount,
#rank := IF(#current_to_place <> pcount, #rank + 1, 1) AS rank,
#current_to_place := pcount
FROM
(select
to_place, count(*) as pcount
from t
group by to_place
order by to_place,pcount desc) g1
cross join(select #rank:=0,#current_to_place:=0) r
ORDER BY g1.pcount DESC
)
ranked
+----------+------+
| to_place | rank |
+----------+------+
| 3 | 1 |
| 2 | 2 |
| 1 | 3 |
+----------+------+
3 rows in set (0.016 sec)

Related

SQL DISTINCT EXISTS GROUP BY aggregate function

Does a relational database exist that has a GROUP BY aggregate function such as DISTINCT EXISTS that returns TRUE if there is more than one distinct value for the group and FALSE otherwise? I am looking for something that would iterate through the values in the group until the current value is not the same as the previous value, instead of counting ALL of the distinct values.
Example:
pv_name | time_stamp | value
A | 1 | 1
B | 2 | 1
C | 3 | 1
A | 4 | 2
C | 5 | 2
B | 6 | 3
SELECT pv_name
FROM example
WHERE time_stamp > 0 AND time_stamp < 6
GROUP BY pv_name
HAVING DISTINCT_EXISTS(value);
Result: A, C
SELECT pv_name
FROM example
WHERE time_stamp > 0 AND time_stamp < 6
GROUP BY pv_name
HAVING MIN(value)<>MAX(value);
Might get you there quicker depending on indexes. I don't think you'll do much better than this or COUNT(DISTINCT value) though.
Have you tried joining to example twice?
Psuedo-code example:
with
(
SELECT pv_name
FROM example
WHERE time_stamp > 0 AND time_stamp < 6
) as Q
select distinct Q1.pv_name
from Q as Q1 inner join Q as Q2 on
Q1.pv_name=Q2.pv_name and
Q1.value<>q2.value
You probably know about the COUNT(DISTINCT) function and you want to avoid it to prevent unnecessary computations.
It is hard to know why you are looking for this but I assume that it takes long time to find these groups using the most obvious query:
SELECT type, COUNT(DISTINCT product)
FROM aTable
GROUP BY type
HAVING COUNT(DISTINCT product) > 1
I can recommend you try the window functions. Try for example the new T-SQL's LAST_VALUE and FIRST_VALUE functions:
with c as (
SELECT type
,LAST_VALUE(product) OVER (PARTITION BY type ORDER BY product) lv
,FIRST_VALUE(product) OVER (PARTITION BY type ORDER BY product) pv
FROM aTable
)
SELECT * from c where lv <> pv
If the DB engine is smart enough it will find the first/last value for the group and will not try to count all the values, and therefore perform better.
For MySQL you can use helper variables to get the row_number per group based on the distinct values, something like this:
SELECT type, product
FROM (
SELECT #row_num := IF(#prev_type=type and #prev_prod=product,#row_num+1,1) AS RowNumber
,type
,product
,#prev_type := type
,#prev_prod := product
FROM Person,
(SELECT #row_num := 1) x,
(SELECT #prev_type := '') y,
(SELECT #prev_prod := '') z
ORDER BY type, product
) as a
WHERE RowNumber > 1
I think the having min (value) <> max (value) will be most efficient here. An alternative is:
Select distinct pv_name
From example e
Left join (
Select value
From example
Where ...
Group by value
Having count (*) = 1
) s on e.value = s.value
Where s.value is null
Or you could use NOT EXISTS against that subquery instead.
Include the relevant where clause in the sub query.

reverse the serial number of a grouped query

The following query orders votes based on how many times users voted... I would like to know the # in the queue of the specific user.
SELECT #s:=#s+1 serial_number, user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`,(SELECT #s:= 0) AS s
GROUP BY slug_owner
ORDER BY cnt DESC
serial_number | user_id | cnt 
3 | 19 | 8
2 | 14 | 4
1 | 13 | 2
Essentially i need the numbers in the serial_number column to be reversed so I can tell that user 13 is #3 based on votes ..
Assign the serial numbers after generating the ordered count:
SELECT #s:=#s+1 serial_number, temp.*
FROM (
SELECT user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`,
GROUP BY slug_owner
ORDER BY cnt DESC
) temp, (SELECT #s:= 0) AS s
The serial numbers are not reversed because GROUP BY and variables don't mix. An ORDER BY is fine. You can use #hjpotter92's solution, but the following also fixes the problem:
SELECT (#rn := #rn + 1) as serial_number, t.*
FROM (SELECT serial_number, user_id, COUNT(slug_owner) as cnt
FROM `votes_queue`
GROUP BY slug_owner
) t CROSS JOIN
(SELECT #rn := 0) params
ORDER BY cnt DESC;
I think the performance is the same. I am offering this answer just to clarify what the actual problem is.

MySQL ranking with GROUP BY and SUM

I have a point table with some columns being:
| user_id | points |
--------------------
| 1 | 10 |
| 5 | 10 |
| 5 | 50 |
| 3 | 15 |
| 3 | 10 |
I would like to get the rank for each user with MySQL.
I've seen this post MySQL get row position in ORDER BY but it doesn't have a SUM in it and I can't get it to work with SUM.
I would like to be able to get the rank of a given user_id.
Thanks in advance for your help!
Reminding the OP's question:
I would like to be able to get the rank of a given user_id.
In order to actually perform operations over the #rank you have to user another derived table (yes, it is inefficient, that's why it is better not to handle this in MySQL):
SELECT * FROM (
SELECT s.*, #rank := #rank + 1 rank FROM (
SELECT user_id, sum(points) TotalPoints FROM t
GROUP BY user_id
) s, (SELECT #rank := 0) init
ORDER BY TotalPoints DESC
) r
WHERE user_id = 3
Output
| USER_ID | TOTALPOINTS | RANK |
|---------|-------------|------|
| 3 | 25 | 2 |
The process is basically:
Get the total amounts of points per user
Sort by those total points ranking
Filter once you have the rank (otherwise, the rank will be compromised)
Fiddle here.
Try this::
SELECT #rownum:=#rownum + 1 as row_number,
t.*
FROM (
select user_id, SUM(points) as Addedpoint
from mytable group by user_id order by Addedpoint desc
) t,
(SELECT #rownum := 0) r
You can achieve that with subquery, inside which you should calculate your sum:
SELECT
#rank:=#rank+1 AS rank,
user_id,
total_points
FROM
(SELECT
user_id,
SUM(points) AS total_points
FROM t
GROUP BY
user_id) AS sum_points
CROSS JOIN
(SELECT #rank:=0) AS init
ORDER BY
sum_points.total_points DESC
-see my fiddle.
select
#rownum := #rownum + 1 AS position,
user_id,
total_points
from
(select user_id, sum(points) as total_points from table)a
join
(SELECT #rownum := 0) r
order by total_points desc
Using a user variable you can do something like this:-
SELECT user_id, tot_points, #Rank:=#Rank + 1 AS user_rank
FROM
(
SELECT user_id, SUM(points) AS tot_points
FROM SomeTable
GROUP BY user_id
ORDER BY tot_points DESC
) Sub1
CROSS JOIN (SELECT #Rank:=0) Sub2
http://sqlfiddle.com/#!2/d0be9/1
SET #rank=0;
select #rank:=#rank+1 AS rank, pointsScored.user_id, sumPoints
from (
select user_id , SUM(points)as sumPoints
from point
group by user_id
order by sumPoints desc
)as pointsScored

How to find rows in SQL / MySQL with ORDER BY

I have a table user
Name | Poin
==================
user1 | 20
user2 | 30
user3 | 80
user4 | 60
user5 | 10
user6 | 85
And I have SQL query
SELECT *
FROM user
ORDER BY poin
It would appear that the data sequence based on points.
But what I need is data like this (for example, I was user1):
Position 1 : user6 - 85 point
Position 2 : user3 - 80 point
Position 3 : user4 - 60 point
You are position 5 : user1 - 20 point
UPDATE
I use this sql
SELECT x.name,
x.position
FROM (SELECT t.user,
#rownum := #rownum + 1 AS position
FROM user t
JOIN (SELECT #rownum := 0) r
ORDER BY t.poin DESC) x
WHERE x.user = 'user1'
This will give current rank for user1:
SELECT count(*) AS rank
FROM user
WHERE poin >= (SELECT poin FROM user WHERE name = 'user1')
Small issue with this query is that if another user has the same points, it will be assigned the same rank - whether it is correct, it is questionable.
If you want to simply add rank for every user, use this:
SELECT
#rank:=#rank+1 AS rank,
name,
poin
FROM user,
(SELECT #rank:=0) r
ORDER BY poin DESC
You can use small variation of this query to get rank of single user, but avoid issue of the same ranking ambiguity:
SELECT *
FROM (
SELECT
#rank:=#rank+1 AS rank,
name,
poin
FROM user,
(SELECT #rank:=0) r
ORDER BY poin DESC
) x
WHERE name = 'user1'
select * from user order by poin desc
Hope this helps
SELECT * FROM user ORDER BY poin DESC
The ORDER BY keyword is used to sort the result-set by a specified column.
The ORDER BY keyword sorts the records in ascending order by default.
If you want to sort the records in a descending order, you can use the DESC keyword.
SQL ORDER BY Syntax
SELECT column_name(s)
FROM table_name
ORDER BY column_name(s) ASC|DESC
SELECT Name,
Poin,
#rowNum := #rowNum + 1 AS position
FROM user
JOIN (SELECT #rowNum := 0) r
ORDER BY poin;
The following code:
SELECT count(*) AS rank
FROM user
WHERE poin >= (SELECT poin FROM user WHERE name = 'user1')
Has a problem when is has double point in different rows.
SELECT Name,
Poin,
#rowNum := #rowNum + 1 AS position
FROM user
JOIN (SELECT #rowNum := 0) r
ORDER BY poin DESC;

Listing the average from the top 5 scores of each player

After reading several other answers of similar problems, I still can't wrap my head to achieve the following:
Having a list of player scores, I would like to get the top n scores of each player (the scores table only has the player id and a value). The final purpose is to aggregate the scores with the AVG() function.
Also note that the n bound is just a limit; a player may have less than n scores, in which case all of them should be computed.
Once the results are calculated, joining with the player table will allow to expand each player id into printable information.
In MySQL you need vars to accomplish yours requirements:
select
idplayer, Score
from
(
select
idplayer, T.Score,
#r := IF(#g=idplayer,#r+1,1) RowNum,
#g := idplayer
from (select #g:=null) initvars
CROSS JOIN
(
SELECT s.Score,
s.idplayer
FROM scores s
ORDER BY idplayer, s.score DESC
) T
) U
WHERE RowNum <= 3
Test it at sqlfiddle:
create table scores( idplayer int, score int);
insert into scores values
(1,5), (1,7), (1,18), (1,27), (2,6);
Results:
| IDPLAYER | SCORE |
--------------------
| 1 | 27 |
| 1 | 18 |
| 1 | 7 |
| 2 | 6 |
Start from here:
drop table if exists scores;
create table scores (playerid integer, score integer);
insert into scores values
(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),
(2,1),(2,2),(2,3),(2,4);
select p1.playerid, p1.score
from scores p1, scores p2
where p1.playerid = p2.playerid
and
p1.score >=
ifnull((select score
from scores
where playerid=p1.playerid
order by score desc limit 4,1
),0)
group by p1.playerid,p1.score;
which will give you the desired list of top scores.
I'm not 100% sure this will work in mysql. However, the following captures the idea as a correlated subquery:
select p.*,
(select sum(score)
from (select score
from scores s
where s.playerid = p.playerid
order by score desc
limit 5
) s2
) as summax5
from players p