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.
Related
I am struggling to make a GROUP BY contiguous blocks, I've used the following two for references:
- GROUP BY for continuous rows in SQL
- How can I do a contiguous group by in MySQL?
- https://gcbenison.wordpress.com/2011/09/26/queries-that-group-tables-by-contiguous-blocks/
The primary idea that I am trying to encapsulate periods with a start and end date of a given state. A complexity unlike other examples is that I'm using a date per room_id as the indexing field (rather than a sequential id).
My table:
room_id | calendar_date | state
Sample data:
1 | 2016-03-01 | 'a'
1 | 2016-03-02 | 'a'
1 | 2016-03-03 | 'a'
1 | 2016-03-04 | 'b'
1 | 2016-03-05 | 'b'
1 | 2016-03-06 | 'c'
1 | 2016-03-07 | 'c'
1 | 2016-03-08 | 'c'
1 | 2016-03-09 | 'c'
2 | 2016-04-01 | 'b'
2 | 2016-04-02 | 'a'
2 | 2016-04-03 | 'a'
2 | 2016-04-04 | 'a'
The objective:
room_id | date_start | date_end | state
1 | 2016-03-01 | 2016-03-03 | a
1 | 2016-03-04 | 2016-03-05 | b
1 | 2016-03-06 | 2016-03-09 | c
2 | 2016-04-01 | 2016-04-01 | b
2 | 2016-04-02 | 2016-04-04 | c
The two attempts I've made at this:
1)
SELECT
rooms.row_new,
rooms.state_new,
MIN(rooms.room_id) AS room_id,
MIN(rooms.state) AS state,
MIN(rooms.date) AS date_start,
MAX(rooms.date) AS date_end,
FROM
(
SELECT #r := #r + (#state != state) AS row_new,
#state := state AS state_new,
rooms.*
FROM (
SELECT #r := 0,
#state := ''
) AS vars,
rooms_vw
ORDER BY room_id, date
) AS rooms
WHERE room_id = 1
GROUP BY row_new
ORDER BY room_id, date
;
This is very close to working, but when I print out row_new it starts to jump (1, 2, 3, 5, 7, ...)
2)
SELECT
MIN(rooms_final.calendar_date) AS date_start,
MAX(rooms_final.calendar_date) AS date_end,
rooms_final.state,
rooms_final.room_id,
COUNT(*)
FROM (SELECT
rooms.date,
rooms.state,
rooms.room_id,
CASE
WHEN rooms_merge.state IS NULL OR rooms_merge.state != rooms.state THEN
#rownum := #rownum+1
ELSE
#rownum
END AS row_num
FROM rooms
JOIN (SELECT #rownum := 0) AS row
LEFT JOIN (SELECT rooms.date + INTERVAL 1 DAY AS date,
rooms.state,
rooms.room_id
FROM rooms) AS rooms_merge ON rooms_merge.calendar_date = rooms.calendar_date AND rooms_merge.room_id = rooms.room_id
ORDER BY rooms.room_id, rooms.calendar_date
) AS rooms_final
GROUP BY rooms_final.state, rooms_final.row_num
ORDER BY room_id, calendar_date;
For some reason this is returning some null room_id's results as well as generally inaccurate.
Working with variables is a bit tricky. I would go for:
SELECT r.state_new, MIN(r.room_id) AS room_id, MIN(r.state) AS state,
MIN(r.date) AS date_start, MAX(r.date) AS date_end
FROM (SELECT r.*,
(#grp := if(#rs = concat_ws(':', room, state), #grp,
if(#rs := concat_ws(':', room, state), #grp + 1, #grp + 1)
)
) as grp
FROM (SELECT r.* FROM rooms_vw r ORDER BY ORDER BY room_id, date
) r CROSS JOIN
(SELECT #grp := 0, #rs := '') AS params
) AS rooms
WHERE room_id = 1
GROUP BY room_id, grp
ORDER BY room_id, date;
Notes:
Assigning a variable in one expression and using it in another is unsafe. MySQL does not guarantee the order of evaluation of expressions.
In more recent versions of MySQL, you need to do the ORDER BY in a subquery.
In the most recent versions, you can use row_number(), greatly simplifying the calculation.
Thanks to #Gordon Linoff for giving me insights to get to this answer:
SELECT
MIN(room_id) AS room_id,
MIN(state) AS state,
MIN(date) AS date_start,
MAX(date) AS date_end
FROM
(
SELECT
#r := #r + IF(#state <> state OR #room_id <> room_id, 1, 0) AS row_new,
#state := state AS state_new,
#room_id := room_id AS room_id_new,
tmp_rooms.*
FROM (
SELECT #r := 0,
#room_id := 0,
#state := ''
) AS vars,
(SELECT * FROM rooms WHERE room_id IS NOT NULL ORDER BY room_id, date) tmp_rooms
) AS rooms
GROUP BY row_new
order by room_id, date
;
I have a query like this:
SELECT #rank := #rank + 3 `rank`, id, subject, name
FROM quran, (select #rank := -2) q
WHERE MATCH (subject, name) AGAINST ('anything') and aye IN ("10")
UNION DISTINCT
SELECT #rank1 := #rank1 + 3 `rank`, id, subject, name
FROM quran, (select #rank1 := -1) q
WHERE MATCH (subject, name) AGAINST ('anything')
UNION ALL
SELECT #rank2 := #rank2 + 3 `rank`, id, subject, byA
FROM hadith, (select #rank2 := 0) q
WHERE MATCH (subject) AGAINST ('anything')
ORDER BY rank LIMIT 0, 11
Now I optimized my query and combined tow first SELECT clause to one, like this: (because they have the same table name)
(SELECT #rank1 := #rank1 + 2 `rank`, id, subject, name
FROM quran, (select #rank1 := -1) q
WHERE MATCH (subject, name) AGAINST ('anything')
ORDER BY CASE WHEN aye IN ('10')
THEN 0
ELSE 1
END
)
UNION ALL
(SELECT #rank2 := #rank2 + 2 `rank`, id, subject, byA
FROM hadith, (select #rank2 := 0) q
WHERE MATCH (subject) AGAINST ('anything')
)
ORDER BY rank LIMIT 0, 11
But I don't know why the sort of result is not identical with the first query. Why? And how can I fix it?
Edit: Here is some examples:
// quran // hadith
+----+---------+--------+ +----+---------+-------+
| id | subject | name | | id | subject | byA |
+----+---------+--------+ +----+---------+-------+
| 1 | hello | jack | | 1 | blue | jack |
| 2 | blue | peter | | 2 | how | hello |
| 3 | jack | red | | 3 | jack | blue |
| 4 | back | blue | +----+---------+-------+
| 10 | jack | how |
+----+---------+--------+
Now, I want this output: So first priority is that $number, and then subject column and then name column, Also the result is alternating for both tables.
$anything = 'jack', $number = 10
+----+---------+--------+
| id | subject | name |
+----+---------+--------+
| 10 | jack | how |
| 3 | jack | blue |
| 3 | jack | red |
| 1 | blue | jack |
| 1 | hello | jack |
+----+---------+--------+
I'm not saying this is the best way to go about things, but it is the least modification to your existing attempts.
(
SELECT IF(aye IN ("10"), 0, 1) AS sortGroup
, IF(aye IN ("10"), #rank := #rank + 3, #rank1 := #rank1 + 3) AS `rank`
, id, subject, name
FROM quran
, (select #rank := -2) AS rq, (select #rank1 := -1) AS r1q
WHERE MATCH (subject, name) AGAINST ('anything')
)
UNION ALL
(
SELECT 2 AS sortGroup
, #rank2 := #rank2 + 2 `rank`
, id, subject, byA
FROM hadith
, (select #rank2 := 0) AS q
WHERE MATCH (subject) AGAINST ('anything')
)
ORDER BY sortGroup, rank
LIMIT 0, 11
Actually, I am not positive you can merge the first two unioned queries and get the same results. In the original query, with UNION DISTINCT and the separate computation of rank in the original, records that satisfy the aye IN ("10") criteria will probably often appear twice (but with different rank values).
I have a table with gr_no, year_dob, family_id etc. etc.
I am trying to rank birth year according to family_id and am unable to generate the siblings_rank result.
+----------+--------------+-----------+
| gr_no | year_dob | family_id | siblings_rank
+----------+--------------+-----------+
| 1001 | 1992 | 95 | 1
| 10234 | 1995 | 95 | 2
| 10236 | 2004 | 96 | 1
| 15568 | 2006 | 96 | 2
| 1225 | 2004 | 92 | 1
+----------+--------------+-----------+
This query is working :
SET #prev := null;
SET #cnt := 1;
SELECT gr_no, gs_id, gf_id, year_dob, IF(#prev <> gf_id, #cnt := 1, #cnt := #cnt + 1) AS siblings_position, #prev := gf_id as previous_gf_id
FROM student_registered
ORDER BY gf_id, year_dob asc
This query is also working:
SELECT gr_no, gs_id, gf_id, year_dob, IF(#prev <> gf_id, #cnt := 1, #cnt := #cnt + 1) AS siblings_position, #prev := gf_id as previous_gf_id
FROM student_registered
JOIN (SELECT #prev := null) p
JOIN (SELECT #cnt := 1) c
ORDER BY gf_id, year_dob asc
............... I am unable to create view with these query?
or
If a procedure can update the student_registered column 'siblings_position' based on the queries?
you can't use sql variables in the view
here is another way to get the same result using correlated subquery
SELECT gr_no, family_id,year_dob,
( select count(*) from Table1 T1
where T1.family_id = T.family_id
and T1.year_dob <= T.year_dob) as siblings_position
FROM Table1 T
ORDER BY family_id, year_dob asc
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;
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