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;
Related
I want to find a user's position in a leaderboard and return the 4 users above and 4 users below their position.
My table, 'predictions', looks something like this:
+----+---------+--------+-------+---------+
| id | userId | score | rank | gameId |
+----+---------+--------+-------+---------+
| 1 | 12 | 11 | 1 | 18 |
| 2 | 1 | 6 | 4 | 18 |
| 3 | 43 | 7 | 3 | 12 |
| 4 | 4 | 9 | 2 | 18 |
| 5 | 98 | 2 | 5 | 19 |
| 6 | 3 | 0 | 6 | 18 |
+----+---------+--------+-------+---------+
Obviously this isn't properly ordered, so I run this:
SELECT l.userId,
l.rank,
l.score,
l.createdAt,
#curRow := #curRow + 1 AS row_number
FROM (SELECT * FROM `predictions` WHERE gameId = 18) l
JOIN (SELECT #curRow := 0) r
ORDER BY rank ASC
which gets me a nice table with each entry numbered.
I then want to search this generated table, find the row_number where userId = X, and then return the values 'around' that result.
I think I have the logic of the query down, I just can't work out how to reference the table 'generated' by the above query.
It would be something like this:
SELECT *
FROM (
SELECT l.userId,
l.rank,
l.score,
l.createdAt,
#curRow := #curRow + 1 AS row_number
FROM (SELECT * FROM `predictions` WHERE gameId = 18) l
JOIN (SELECT #curRow := 0) r
ORDER BY rank ASC) generated_ordered_table
WHERE row_number < (SELECT row_number FROM generated_ordered_table WHERE userId = 1)
ORDER BY row_number DESC
LIMIT 0,5
This fails. What I'm trying to do is to generate my first table with the correct query, give it an alias of generated_ordered_table, and then reference this 'table' later on in this query.
How do I do this?
MySQL version 8+ could have allowed the usage of Window functions, and Common Table Expressions (CTEs); which would have simplified the query quite a bit.
Now, in the older versions (your case), the "Generated Rank Table" (Derived Table) cannot be referenced again in a subquery inside the WHERE clause. One way would be to do the same thing twice (select clause to get generated table) again inside the subquery, but that would be relatively inefficient.
So, another approach can be to use Temporary Tables. We create a temp table first storing the ranks. And, then reference that temp table to get results accordingly:
CREATE TEMPORARY TABLE IF NOT EXISTS gen_rank_tbl AS
(SELECT l.userId,
l.rank,
l.score,
l.createdAt,
#curRow := #curRow + 1 AS row_number
FROM (SELECT * FROM `predictions` WHERE gameId = 18) l
JOIN (SELECT #curRow := 0) r
ORDER BY rank ASC)
Now, you can reference this temp table to get the desired results:
SELECT *
FROM gen_rank_tbl
WHERE row_number < (SELECT row_number FROM gen_rank_tbl WHERE userId = 1)
ORDER BY row_number DESC
LIMIT 0,5
You could use a bunch of unions
select userid,rank,'eq'
from t where gameid = 18 and userid = 1
union
(
select userid,rank,'lt'
from t
where gameid = 18 and rank < (select rank from t t1 where t1.userid = 1 and t1.gameid = t.gameid)
order by rank desc limit 4
)
union
(
select userid,rank,'gt'
from t
where gameid = 18 and rank > (select rank from t t1 where t1.userid = 1 and t1.gameid = t.gameid)
order by rank desc limit 4
);
+--------+------+----+
| userid | rank | eq |
+--------+------+----+
| 1 | 4 | eq |
| 4 | 2 | lt |
| 12 | 1 | lt |
| 3 | 6 | gt |
+--------+------+----+
4 rows in set (0.04 sec)
But it's not pretty
You can use two derived tables:
SELECT p.*,
(#user_curRow = CASE WHEN user_id = #x THEN rn END) as user_rn
FROM (SELECT p.*, #curRow := #curRow + 1 AS rn
FROM (SELECT p.*
FROM predictions p
WHERE p.gameId = 18
ORDER BY rank ASC
) p CROSS JOIN
(SELECT #curRow := 0, #user_curRow := -1) params
) p
HAVING rn BETWEEN #user_curRow - 4 AND #user_currow + 4;
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 want to write a query that will update duplicates per group:
INPUT
+-------+-------+
| group | name |
+-------+-------+
| 1 | name1 |
| 1 | name1 |
| 1 | name1 |
| 1 | name2 |
| 2 | name1 |
| 2 | name1 |
| 3 | name1 |
| 3 | name2 |
+-------+-------+
OUTPUT
+-------+----------------+
| group | name |
+-------+----------------+
| 1 | name1 |
| 1 | name1 - Copy 1 |
| 1 | name1 - Copy 2 |
| 1 | name2 |
| 2 | name1 |
| 2 | name1 - Copy 1 |
| 3 | name1 |
| 3 | name2 |
+-------+----------------+
There is something like that here Renaming the duplicate data in sql but my problem is how to deal with groups.
It is not so important how to name this duplicates but it will be cool if I can do it as specified in my example.
If you have a primary key id column, then try this:
update (
select `group`, name, min(id) as min_id
from test
group by `group`, name
) x
join test t using (`group`, name)
set t.name =
case when t.id <> x.min_id
then concat(t.name, ' - Copy ', t.id - x.min_id)
else t.name
end
;
Demo: http://rextester.com/AWEX77086
Here is another way, which is probably slower, but will guarantee consecutive copy numbers.
update (
select l.id, count(*) as copy_nr
from test l
join test r
on r.group = l.group
and r.name = l.name
and r.id < l.id
group by l.id
) x
join test t using (id)
set t.name = concat(t.name, ' - Copy ', x.copy_nr);
Demo: http://rextester.com/NWSF57017
Try the below, replace Groups, with Group.
Ok, I was pointed out that this is a MySQL question, so the below wont work for MySQL, but only for t-sql.
SELECT Groups,
CASE WHEN Duplicate > 1
THEN Name + ' - Copy ' + CONVERT(VARCHAR(10), Duplicate)
ELSE Name
END AS Name
FROM
(
SELECT Groups,
Name,
ROW_NUMBER() OVER(PARTITION BY Name, Groups ORDER BY Name ) As Duplicate
FROM TableName
) AS Data
ORDER BY Groups
This is untested, but you can similate the LAG function seen in many other RDBMS' with variables (more info here).
The idea is you store the group and name fields and compare against them before updating them.
SET #RowNumber = 0;
SET #PreviousGroup = NULL;
SET #PreviousName = NULL;
SELECT
#PreviousGroup AS PreviousGroup,
#PreviousName AS PreviousName,
CASE
WHEN #PreviousGroup = `group` AND #PreviousName = `name` THEN (#RowNumber := #RowNumber + 1)
ELSE #RowNumber := 0
END AS `Counter`,
CASE
WHEN #PreviousGroup = `group` AND #PreviousName = `name` THEN CONCAT(`name`,'- Copy ',#RowNumber)
ELSE `name`
END AS `Name`,
#PreviousGroup := `group` AS RawGroup,
#PreviousName := `name` AS RawName
FROM
tbl1
ORDER BY
`group` ASC,
`name` ASC;
You can do this using variables. I would recommend:
set #i = 0;
set #gn := '';
update t
set name = concat_ws(' - Copy ', name,
nullif(if(#gn = concat_ws(':', group, name), #i := #i + 1,
if(#gn := concat_ws(':', group, name), #i := 1, #i := 1)
), 0)
order by t.group, name;
How to find the latest grade of an employee has the same id.
my table is like:
+----+-------+-------+
| ID | NAME | GRADE |
| 1 | ANN | A0 |
| 1 | ANN | A3 |
| 2 | JOE | B1 |
| 3 | KIM | B3 |
| 2 | JOE | B2 |
| 3 | KIM | C1 |
| 3 | KIM | C3 |
+----+-------+-------+
How to find the latest grade of ann, Kim, and joe
my output is like:
name latestgrade
ann A3
joe B2
kim C3
Give a row number group by ID and order by the descending order of Grade
Sql Server Query
;with cte as(
select [rn] = row_number() over(
partition by [ID]
order by len([GRADE]) desc, [GRADE] desc
), *
from [your_table_name]
)
select [ID], [NAME], [GRADE]
from cte
where [rn] = 1;
demo
MySql Query
select t1.`ID`, t1.`NAMME`, t1.`GRADE` from (
select `ID`, `NAME`, `GRADE`, (
case `ID` when #curA
then #curRow := #curRow + 1
else #curRow := 1 and #curA := `ID` end
) as `rn`
from `your_table_name` t,
(select #curRow := 0, #curA := '') r
order by `ID`, length(`GRADE`) desc, `GRADE` desc
)t1
where t1.`rn` = 1;
demo
try row_number
;with cte
as
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY
SUBSTRING(GRADE,1,1) desc,CAST(SUBSTRING(GRADE,2,LEN(GRADE)) AS INT) DESC),
*
FROM YourTable
)
SELECT
*
FROM CTE
WHERE SeqNo = 1
If you wish to insert the above result into a new table, just write a insert into before the select
;with cte
as
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY
SUBSTRING(GRADE,1,1) desc,CAST(SUBSTRING(GRADE,2,LEN(GRADE)) AS INT) DESC),
*
FROM YourTable
)
insert into MyNewTable
SELECT
*
FROM CTE
WHERE SeqNo = 1
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