Assign the same rank if multiple users have same count value - mysql

i have user_points Table with two columns.
select user_values, userid from user_points, based on count of userid i want to assgin the rank to users.. i have write this query
SELECT count_temp.* , #curRank:=(#curRank + 1) AS rank
FROM (
SELECT userid, COUNT(*) AS totalcount FROM user_points t GROUP BY t.userid
) AS count_temp
, (SELECT #curRank := 0) r
ORDER BY totalcount DESC;
gives the result as :
userid | totalcount | rank
2 6 1
3 2 2
4 2 3
1 1 4
but i want to assgin to rank 2 for userid 3 and 4 because their totalcount are same ..

To emulate RANK() function, which returns the rank of each row within the partition of a result set, you can do
SELECT userid, totalcount, rank
FROM
(
SELECT userid, totalcount,
#n := #n + 1, #r := IF(#c = totalcount, #r, #n) rank, #c := totalcount
FROM
(
SELECT userid, COUNT(*) AS totalcount
FROM user_points t
GROUP BY t.userid
ORDER BY totalcount DESC
) t CROSS JOIN
(
SELECT #r := 0, #n := 0, #c := NULL
) i
) q;
Output:
| USERID | TOTALCOUNT | RANK |
|--------|------------|------|
| 2 | 6 | 1 |
| 3 | 2 | 2 |
| 4 | 2 | 2 |
| 1 | 1 | 4 |
To emulate DENSE_RANK() function, which returns the rank of rows within the partition of a result set, without any gaps in the ranking, you can do
SELECT userid, totalcount, rank
FROM
(
SELECT userid, totalcount,
#r := IF(#c = totalcount, #r, #r + 1) rank, #c := totalcount
FROM
(
SELECT userid, COUNT(*) AS totalcount
FROM user_points t
GROUP BY t.userid
ORDER BY totalcount DESC
) t CROSS JOIN
(
SELECT #r := 0, #c := NULL
) i
) q;
Output:
| USERID | TOTALCOUNT | RANK |
|--------|------------|------|
| 2 | 6 | 1 |
| 3 | 2 | 2 |
| 4 | 2 | 2 |
| 1 | 1 | 3 |
Here is SQLFiddle demo for both queries

Related

How can I grouping an unix time per day?

I have a table like this:
// requests
+----+----------+-------------+
| id | id_user | unix_time |
+----+----------+-------------+
| 1 | 2353 | 1339412843 |
| 2 | 2353 | 1339412864 |
| 3 | 5462 | 1339412894 |
| 4 | 3422 | 1339412899 |
| 5 | 3422 | 1339412906 |
| 6 | 2353 | 1339412906 |
| 7 | 7785 | 1339412951 |
| 8 | 2353 | 1339413640 |
| 9 | 5462 | 1339413621 |
| 10 | 5462 | 1339414490 |
| 11 | 2353 | 1339414923 |
| 12 | 2353 | 1339419901 |
| 13 | 8007 | 1339424860 |
| 14 | 7785 | 1339424822 |
| 15 | 2353 | 1339424902 |
+----+----------+-------------+
I want to grouping unix_time column based on separated days. Actually I'm trying to make this for an specific user:
As you see I need tow numbers for an user:
the number of all days which there is a foot print of the user into requests table
the number of biggest consecutive days
How can I do that?
Actually I can use WHERE id_user = :id to select user's rows. And I can calculate the number of days by SUM(). And by using MAX() I can calculate the biggest consecutive range. Just I need to grouping those unix times.
Please give it a try:
SELECT
t.id_user,
COUNT(*) totalVisits,
MAX(t.max_cons) maxCons
FROM
(SELECT
id_user,
#lastUnixTime AS lastUnixTimeOfuser,
IF(#uid <> id_user, #currentMax := 1 , #currentMax),
IF(#uid <> id_user, #lastUnixTime := 0, #lastUnixTime := #lastUnixTimeOfLastRecord),
IF(#uid = id_user,
IF((#lastUnixTime + 86400) >= utime, #currentMax := #currentMax + 1, #currentMax := 1), #lastUnixTime := 0),
IF(#currentMax > #max, #max := #currentMax, #max ),
IF(#uid <> id_user , #max := 1 ,#max),
#uid := id_user,
#lastUnixTimeOfLastRecord := utime,
#max AS max_cons
FROM
(
SELECT
id_user,
(unix_time DIV 86400) * 86400 AS utime
FROM requests
GROUP BY id_user, utime ) dayWiseRequestTable ,
(
SELECT
#uid := 0,
#currentMax := 0,
#max := 0,
#lastUnixTime := 0,
#lastUnixTimeOfLastRecord := 0
) vars
ORDER BY id_user, utime) t
GROUP BY t.id_user;
SQL FIDDLE DEMO
Output:
The final output looks like below:
id_user Total_Visits Maximum_Consecutive_Visits
2353 7 2
3422 2 2
5462 3 2
7785 2 1
8007 1 1
EDIT:
In order to get output for a specific user you need to add a WHERE clause in the inner query.
Please check this SQL FIDDLE
Use can extract the day using from_unixtime(). Then you can get count the days using variables:
select id_user, d,
(#rn := if(#di = concat_ws(':', d - interval 1 day, id_user), #rn + 1,
if(#di := concat_ws(':', d, id_user), 1, 1)
)
) as rn
from (select id_user, date(from_unixtime(unix_time)) as d
from t
group by id_user, d
) cross join
(select #di := '', #rn := 0) params
order by id_user, d;
From here to the summary is just an aggregation:
select id_user, count(*) as numdays, max(rn) as maxconsecutive
from (select id_user, d,
(#rn := if(#di = concat_ws(':', d - interval 1 day, id_user), #rn + 1,
if(#di := concat_ws(':', d, id_user), 1, 1)
)
) as rn
from (select id_user, date(from_unixtime(unix_time)) as d
from t
group by id_user, d
) cross join
(select #di := '', #rn := 0) params
order by id_user, d
) ud
group by id_user;
Here is a SQL Fiddle illustrating the code.

Get first date from timestamp in SQL

I have in my Moodle db table for every session sessid and timestart. The table looks like this:
+----+--------+------------+
| id | sessid | timestart |
+----+--------+------------+
| 1 | 3 | 1456819200 |
| 2 | 3 | 1465887600 |
| 3 | 3 | 1459839600 |
| 4 | 2 | 1457940600 |
| 5 | 2 | 1460529000 |
+----+--------+------------+
How to get for every session the first date from the timestamps in SQL?
You can easy use this:
select sessid,min(timestart) FROM mytable GROUP by sessid;
And for your second question, something like this:
SELECT
my.id,
my.sessid,
IF(my.timestart = m.timestart, 'yes', 'NO' ) AS First,
my.timestart
FROM mytable my
LEFT JOIN
(
SELECT sessid,min(timestart) AS timestart FROM mytable GROUP BY sessid
) AS m ON m.sessid = my.sessid;
Try this.
SELECT
*
FROM
tbl
WHERE
(sessid, timestart) IN (
SELECT tbl2.sessid, MIN(tbl2.timestart)
FROM tbl tbl2
WHERE tbl.sessid = tbl2.sessid
);
Query
select sessid, min(timestart) as timestart
from your_table_name
group by sessid;
Just an other perspective if you need even the id.
select t.id, t.sessid, t.timestart from
(
select id, sessid, timestart,
(
case sessid when #curA
then #curRow := #curRow + 1
else #curRow := 1 and #curA := sessid end
) as rn
from your_table_name t,
(select #curRow := 0, #curA := '') r
order by sessid,id
)t
where t.rn = 1;

SQL Switch even/odd values of the selection

Currently I have this selection from 2 tables:
(SELECT e.id, e.num, '1' as TBL
FROM events e
)
UNION ALL
(SELECT p.id, p.num, '2' as TBL
FROM places p
)
ORDER BY 2 DESC
It returns values ordered by num like this:
id | num | TBL
3 | 9 | 2
1 | 8 | 2
4 | 7 | 1
1 | 4 | 1
7 | 1 | 2
But my goal is to mix tables in selection not losing ORDER within a specific table. Like this:
id | num | TBL
3 | 9 | 2
4 | 7 | 1
1 | 8 | 2
1 | 4 | 1
7 | 1 | 2
Thanks in advance! I appreciate ANY help!
If you want to interleave the tables, you will need additional information. If you enumerate each row, then you can use that for sorting. Something like this:
(SELECT e.id, e.num, '1' as TBL, (#rn1 := #rn1 + 1) as rn
FROM events e CROSS JOIN
(SELECT #rn1 := 0) vars
ORDER BY e.num desc
)
UNION ALL
(SELECT p.id, p.num, '2' as TBL, (#rn2 := #rn2 + 1) as rn
FROM places p CROSS JOIN
(SELECT #rn2 := 0) vars
ORDER BY p.num desc
)
ORDER BY rn, tbl desc;
Try this:
DECLARE #t TABLE (rn INT IDENTITY,id INT,TBL INT)
INSERT INTO #t (id, TBL)
SELECT id,1 FROM events
INSERT INTO #t
SELECT id,2 FROM places
DECLARE #t2 TABLE (rn INT IDENTITY,num numeric(18,2))
INSERT INTO #t2 (num)
(
SELECT num
FROM events
UNION ALL
SELECT num
FROM places)
ORDER BY num DESC
SELECT id, num, TBL
FROM #t a
JOIN #t2 b ON a.rn = b.rn

Get average of multiple ranks in mysql

I am trying to calculate the spearmans rank correlation for some data in mysql. For this I need to rank my data on a descending order. I got this working but when 2 rows have the same variable the rank should be the average of the 2 or more ranks.
As an example here is some example data with the current ranks and the expected ranks
| id|var|rank|
| 8 | 1 | 1 |
| 2 | 2 | 2 | # rank should be 2.5
| 6 | 2 | 3 | # rank should be 2.5
| 4 | 3 | 4 |
| 5 | 4 | 5 |
| 1 | 5 | 6 |
| 3 | 6 | 7 | # rank should be 8
| 7 | 6 | 8 | # rank should be 8
| 9 | 6 | 9 | # rank should be 8
My query looks like this right now:
SET #rownum := 0;
SET #rownum2 := 0;
SELECT rank_x.id, rank_x.var1, rank_x.rk_x
FROM
(SELECT id, #rownum := #rownum + 1 AS rk_x, var1
FROM sampledata order by var1 asc) as rank_x;
You can do this by assigning the sequential number and then taking the average. This requires some nested subqueries, but is doable. The idea is:
First assign the sequential value
Then find the max for each id.
Then find the min
Then take the average
The query looks like:
SELECT id, var1, (minrn + maxrn) / 2
FROM (SELECT sd.*,
(#maxrn := if(#v2 = var1, #maxrn,
if(#v2 := var1, rn, rn)
)
) as maxrn
FROM (SELECT sd.*,
(#minrn := if(#v = var1, #minrn,
if(#v := var1, rn, rn)
)
) as minrn
FROM (SELECT id, var1, (#rn := #rn + 1) as rn
FROM sampledata sd CROSS JOIN
(SELECT #rn := 0) vars
ORDER BY var1 asc
) sd CROSS JOIN
(SELECT #minrn := 0, #v := -1) vars
ORDER BY var1, rn
) sd CROSS JOIN
(SELECT #maxrn := 0, #v2 := -1) vars
ORDER BY var1, rn desc
) sd;

Rank project numbers in view, MYSQL

I created a view by the following statement.
CREATE VIEW
view_projectHour
AS
SELECT pno
, SUM( hours ) AS total_hours
FROM works_on
GROUP BY pno
ORDER BY total_hours DESC
Now, how can I implement ranking in this view? I want the projects to be ranked. The project with the highest hours must be ranked 1 and be placed on the top and so on. Also there are projects with the same hours.
Unfortunately MySQL lack support for analytic functions. Particularly RANK() and RANK_DENSE().
To emulate RANK() you can do
SELECT pno, total_hours, rank
FROM
(
SELECT pno, total_hours,
#n := #n + 1 rnum, #r := IF(#h = total_hours, #r, #n) rank, #h := total_hours
FROM
(
SELECT pno, SUM(hours) total_hours
FROM works_on
GROUP BY pno
) q CROSS JOIN (SELECT #n := 0, #r := 0, #h := NULL) i
ORDER BY total_hours DESC, pno
) t
Sample output:
| PNO | TOTAL_HOURS | RANK |
|-----|-------------|------|
| 3 | 61 | 1 |
| 1 | 40 | 2 |
| 2 | 40 | 2 |
| 4 | 10 | 4 |
To emulate DENSE_RANK() you can do
SELECT pno, total_hours, rank
FROM
(
SELECT pno, total_hours,
#r := IF(#h = total_hours, #r, #r + 1) rank, #h := total_hours
FROM
(
SELECT pno, SUM(hours) total_hours
FROM works_on
GROUP BY pno
) q CROSS JOIN (SELECT #r := 0, #h := NULL) i
ORDER BY total_hours DESC, pno
) t
Sample output:
| PNO | TOTAL_HOURS | RANK |
|-----|-------------|------|
| 3 | 61 | 1 |
| 1 | 40 | 2 |
| 2 | 40 | 2 |
| 4 | 10 | 3 |
Note: You can ditch outer SELECTs if you don't mind to have one or two extra columns in your resultset.
Here is SQLFiddle demo
An alternate solution is to use a JOIN to count how many values are ranked better for each row;
SELECT 1+COUNT(b.total_hours) rank, a.pno, a.total_hours
FROM test a
LEFT JOIN test b
ON a.total_hours < b.total_hours
GROUP BY a.pno, a.total_hours
ORDER BY total_hours DESC;
An SQLfiddle to test with.