How to select last entries for each groups? - mysql

I have the following table:
ID | team1 | team2 | Date
-----------------------------
1 | 36 | 25 | 2019-01-05
2 | 25 | 39 | 2019-01-07
3 | 36 | 39 | 2019-01-09
4 | 36 | 11 | 2019-01-10
5 | 11 | 25 | 2019-01-11
6 | 25 | 36 | 2019-01-12
How to get last 2 entries for team 25 and 36. Good result is:
ID | team1 | team2 | Date
-----------------------------
4 | 36 | 11 | 2019-01-10 > 36
5 | 11 | 25 | 2019-01-11 > 25
6 | 25 | 36 | 2019-01-12 > 25 & 36
25 and 36 is just for example. We can have a long list of teams, as well as a very large list of entries. If the search would be carried out on one column, for example team1 then the query would look like this:
SELECT * FROM (
SELECT
ID, team1, team2, `Date`,
CASE WHEN #id != team1 THEN #rownum := 1 ELSE #rownum := #rownum + 1 END AS rank,
#id := team1
FROM matches
JOIN (SELECT #rownum := 0, #id := NULL) r
WHERE team1 IN(25, 36)
OREDER BY team1, `Date` DESC
) WHERE rank <= 2

You can use a LEFT OUTER JOIN to join the table against itself for later games, and discard any rows where there is a layter game:-
SELECT a.*
FROM matches a
LEFT OUTER JOIN matches b
ON a.team1 = b.team1
AND a.`date` < b.`date`
WHERE b.id IS NULL
AND a.team1 IN (25, 36)

If you apply your query to the UNION of the 2 columns and join to the table you will get the result that you want:
SELECT DISTINCT m.*
FROM matches m INNER JOIN (
SELECT * FROM (
SELECT team, `Date`,
CASE WHEN #id != team THEN #rownum := 1 ELSE #rownum := #rownum + 1 END AS rank,
#id := team
FROM (
SELECT team1 team, date FROM matches
UNION ALL
SELECT team2, date FROM matches
) t
JOIN (SELECT #rownum := 0, #id := NULL) r
WHERE team IN (25, 36)
ORDER BY team, `Date` DESC
) t
WHERE rank <= 2
) t ON t.`Date` = m.`Date` AND t.team IN (m.team1, m.team2)
See the demo.
Results:
| ID | team1 | team2 | Date |
| --- | ----- | ----- | ------------------- |
| 4 | 36 | 11 | 2019-01-10 00:00:00 |
| 5 | 11 | 25 | 2019-01-11 00:00:00 |
| 6 | 25 | 36 | 2019-01-12 00:00:00 |

Related

How to select last XX entries for each team?

I have the following table:
ID | team1 | team2 | Date
-----------------------------
1 | 36 | 25 | 2019-01-05
2 | 25 | 39 | 2019-01-07
3 | 36 | 39 | 2019-01-09
4 | 36 | 11 | 2019-01-10
5 | 11 | 25 | 2019-01-11
6 | 25 | 36 | 2019-01-12
How to get last 2 entries for team 25 and 36. Good result is:
ID | team1 | team2 | Date
-----------------------------
4 | 36 | 11 | 2019-01-10 > 36
5 | 11 | 25 | 2019-01-11 > 25
6 | 25 | 36 | 2019-01-12 > 25 & 36
25 and 36 is just for example. We can have a long list of teams, as well as a very large list of entries. If the search would be carried out on one column, for example team1 then the query would look like this:
SELECT * FROM (
SELECT
ID, team1, team2, `Date`,
CASE WHEN #id != team1 THEN #rownum := 1 ELSE #rownum := #rownum + 1 END AS rank,
#id := team1
FROM matches
JOIN (SELECT #rownum := 0, #id := NULL) r
WHERE team1 IN(25, 36)
OREDER BY team1, `Date` DESC
) WHERE rank <= 2
(SELECT * FROM teams WHERE (team1 = 25 and team2 != 36) OR (team2 = 25 and team2 != 36) ORDER BY id DESC LIMIT 2)
UNION ALL
(SELECT * FROM teams WHERE team1 = 36 OR team2 = 36 ORDER BY id DESC LIMIT 2)
(SELECT * FROM my_table WHERE 25 IN (team1,team2) ORDER BY date DESC LIMIT 2)
UNION ALL
(SELECT * FROM my_table WHERE 36 IN (team1,team2) ORDER BY date DESC LIMIT 2)
ORDER BY id;

Get 10 latest records per category even some has less then 10

This is example of my table :
+-----+-----+------------+--------+-------------+--------------+
| LID | AID | Created | TypeID | PaymentDate | PaymentValue |
+-----+-----+------------+--------+-------------+--------------+
| 1 | 529 | 2017-05-12 | 1 | 2017-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 2 | 529 | 2018-04-10 | 4 | 2018-04-10 | 200 |
+-----+-----+------------+--------+-------------+--------------+
| 3 | 441 | 2014-01-23 | 3 | 2014-01-23 | 300 |
+-----+-----+------------+--------+-------------+--------------+
| 4 | 324 | 2017-09-14 | 1 | 2017-09-14 | 400 |
+-----+-----+------------+--------+-------------+--------------+
| 5 | 111 | 2018-05-12 | 0 | 2018-05-12 | 340 |
+-----+-----+------------+--------+-------------+--------------+
| 6 | 529 | 2018-05-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 7 | 529 | 2018-06-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 8 | 529 | 2018-07-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 9 | 529 | 2018-08-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 10 | 529 | 2018-09-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 11 | 529 | 2018-01-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 12 | 529 | 2018-05-14 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 13 | 529 | 2018-05-21 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
| 14 | 529 | 2018-03-12 | 1 | 2018-05-12 | 100 |
+-----+-----+------------+--------+-------------+--------------+
Here another table
+-----+-------+
| ID |caption|
+-----+-------+
| 0 | bad |
+-----+-------+
| 1 | good |
+-----+-------+
I need to get 10 latest records per AID. If there less than 10 records for some AID anyway i need to get ten rows and put "No payment date" into PaymentDate and Created fields, Null into TypeID and 0 into PaymentValue. I can get 10 or less latest records with
select *
from (select *,
(#rn := if(#c = AID, #rn + 1,
if(#c := AID, 1, 1)
)
) as rn
from history cross join
(select #rn := 0, #c := -1) params
order by AID, Created desc
) t
having rn <= 10;
But i dont know how force mysql to output 10 rows for each AID. Help me please.
Result should be in a form
AID,TypeId,Created,Caption
I have done it.
This query needs to create a row of 10 records to combine with distinct AID valies in the table. I was able to show the result for Amount and Create date and will leave it to you to continue since you will get the idea.
The critical part is to build a table with 10 rows times distinct AID so about 40 rows in table r. Then do a left join to table t which is similar to what you have done. Table t gets a rank of at most 10 records. Any missing rank up to 10 recs will be filled by table r. Coalesce will assign the default values such as 0 fro amount and 'no create date' for date.
http://sqlfiddle.com/#!9/855c21/2
SELECT coalesce(r.aid, t.aid) as aid,
coalesce(t.paymentvalue, 0) as paymentvalue,
coalesce(cast(t.created as char), 'no create date') as created
FROM (select * from (
select 1 as rw union
select 2 union select 3
union select 4 union select 5
union select 6 union select 7
union select 8 union select 9
union select 10) u
cross join (select distinct aid
from history) h
) as r
LEFT JOIN (
SELECT a.aid, a.paymentvalue,
a.created, count(*) rn
FROM history a
JOIN history b
ON a.aid = b.aid
AND a.created <= b.created
GROUP BY a.aid, a.created
HAVING COUNT(*) <= 10) t
on r.rw=t.rn and r.aid=t.aid
order by aid, created;
I have added RIGHT JOIN to bring in the null rows to top up to 10 (or n) rows per AID. Initially I use SELECT 1 UNION SELECT 2 ... to generate the 10 rows. In order to make it easier to increase the number of rows (say 100), I am trying this idea of generate_series equivalent for mysql. In order for this to work, the number of rows in history table must be equal to greater than the number of rows required per AID.
select t1.lid
,t2.aid
,coalesce(t1.created, "no created date") as created
,t1.typeID
,coalesce(t1.paymentdate, "no payment date") as paymentDate
,coalesce(t1.paymentvalue, 0) as paymentValue
,t2.rn
from
(
select *,
(#rn := if(#c = AID, #rn + 1,
if(#c := AID, 1, 1)
)
) as rn
from history cross join
(select #rn := 0, #c := -1) params
order by AID, Created desc
) t1
right join
( select *
from (select distinct aid from history ) h1
cross join
(select rn -- generate table with n rows numbered from 1 to n
from
(select
#num:= 0) init
cross join
(select #num := #num +1 rn
from history ) t -- assume history has at least 10 rows
limit
10 ) h2 -- n = 10; change it to the number of rows per aid required
) t2
on t1.aid = t2.aid and t1.rn = t2.rn
order by t2.aid, t2.rn

Rank users in mysql by their total points across multiple rows

I have very much similar kind of requirement as described in this question.
Rank users in mysql by their points
The only difference is in my data. The above problem has the data where table has only row per student. But in my case there may be a possibility that table contains multiple rows for a single student like this
Student 1 points 80
Student 2 points 77.5
Student 2 points 4.5
Student 3 points 77
Student 4 points 77
So now rank should be calculated based on the SUM of points (total) that user has. So in this case result would be.
Student 2 Rank 1 with 82 points
Student 1 Rank 2 with 80 points
Student 3 Rank 3 with 77 points
Student 4 Rank 3 with 77 points
SQL Fiddle for data
I tried couple of things with the solution of above question but couldn't get the result. Any help would be appreciated.
Using the same query in my previous answer just change the table student for a subquery to combine all records of every student
change [student er] for
(SELECT `id`, SUM(`points`) as `points`
FROM students
GROUP BY `id`) er
SQL DEMO
select er.*,
(#rank := if(#points = points,
#rank,
if(#points := points,
#rank + 1,
#rank + 1
)
)
) as ranking
from (SELECT `id`, SUM(`points`) as `points`
FROM students
GROUP BY `id`) er cross join
(select #rank := 0, #points := -1) params
order by points desc;
OUTPUT
| id | points | ranking |
|----|--------|---------|
| 5 | 91 | 1 |
| 6 | 81 | 2 |
| 1 | 80 | 3 |
| 2 | 78 | 4 |
| 3 | 78 | 4 |
| 4 | 77 | 5 |
| 7 | 66 | 6 |
| 8 | 15 | 7 |
Try this:
select id, points, #row := ifnull(#row, 0) + diff rank
from (select *, ifnull(#prev, 0) != points diff, #prev := points
from (select id, sum(points) points
from students
group by 1
order by 2 desc) x) y
See SQLFiddle
EDITED:
(This should work)
SELECT I.Id, I.Points, Rk.Rank
FROM
(SELECT Id, Points, #Rk := #Rk+1 As Rank
FROM (SELECT id, SUM(points) AS Points
FROM students
GROUP BY id
ORDER BY Points DESC) As T,
(SELECT #Rk := 0) AS Rk) As I
INNER JOIN
(SELECT *
FROM (
SELECT Id, Points, #Rk2 := #Rk2+1 As Rank
FROM (SELECT id, SUM(points) AS Points
FROM students
GROUP BY id
ORDER BY Points DESC) As T1,
(SELECT #Rk2 := 0) AS Rk) AS T2
GROUP BY Points) As Rk
USING(Points)
The output will be:
| Id | Points | Rank |
|----|--------|---------|
| 5 | 91 | 1 |
| 6 | 81 | 2 |
| 1 | 80 | 3 |
| 2 | 78 | 4 |
| 3 | 78 | 4 |
| 4 | 77 | 6 |
| 7 | 66 | 7 |
| 8 | 15 | 8 |
After two Ids in 4th position you'll get the 6th position because 5 Ids are before of the 6th.

MySQL - Check if consecutive columns are the same and display only those rows

I have an events table that contains IDs (id) and dates (eventDate) corresponding to those IDs (id and eventDate are not the only columns in the table).
SQLFiddle here.
+--------+----+---------------------+
| row_id | id | eventDate |
+--------+----+---------------------+
| 1 | 1 | 2014-02-27 23:19:41 |
| 2 | 1 | 2014-02-27 23:21:41 |
| 3 | 1 | 2014-02-27 23:21:41 |
| 4 | 2 | 2014-02-27 23:23:08 |
| 5 | 2 | 2014-02-27 23:25:08 |
| 6 | 2 | 2014-02-27 23:25:08 |
| 9 | 3 | 2014-02-28 15:36:55 |
| 8 | 3 | 2014-02-28 15:36:55 |
| 7 | 3 | 2014-02-28 15:34:55 |
| 10 | 4 | 2014-02-28 19:31:31 |
| 11 | 4 | 2014-02-28 19:33:31 |
| 12 | 4 | 2014-02-28 19:33:31 |
| 13 | 5 | 2014-02-28 19:33:34 |
| 14 | 5 | 2014-02-28 19:33:33 |
| 15 | 5 | 2014-02-28 19:31:33 |
| 16 | 6 | 2014-03-04 22:40:21 |
| 17 | 6 | 2014-03-04 22:38:21 |
| 18 | 6 | 2014-03-04 22:40:21 |
| 19 | 7 | 2014-03-04 23:08:37 |
| 20 | 7 | 2014-03-04 23:08:38 |
+--------+----+---------------------+
I want to select only those rows from the table, where consecutive event dates are the same for the same ID.
Thus, I would like to see only these entries -
+----+---------------------+
| id | eventDate |
+----+---------------------+
| 1 | 2014-02-27 23:21:41 |
| 1 | 2014-02-27 23:21:41 |
| 2 | 2014-02-27 23:25:08 |
| 2 | 2014-02-27 23:25:08 |
| 3 | 2014-02-28 15:36:55 |
| 3 | 2014-02-28 15:36:55 |
| 4 | 2014-02-28 19:33:31 |
| 4 | 2014-02-28 19:33:31 |
Note that there is no
| 6 | 2014-03-04 22:40:21 |
| 6 | 2014-03-04 22:40:21 |
in the above result, because they're not consecutive.
I know I can store the output of the SQL query in a file and then use a unix tool to do this, but I want to know if this is achievable directly through SQL.
Should be able to accomplish this leveraging a group by although my mySql is a bit rusty.
SELECT t.*
FROM (
SELECT
id,
eventDate,
COUNT(0) AS numRows
FROM tabl
GROUP BY id, DATE(eventDate)
HAVING COUNT(0) > 1
ORDER BY eventDate
) t
Then you just join this correlated sub query back to the original table if you need additional columns.
select id,eventDate from your_tableName where eventDate in (select eventDate from your_tableName group by id,eventDate having count(eventDate) > 1);
select ta.id, ta.eventDate from
(
select row_id as ra, t1.id, t1.eventDate
from events t1
) as ta
join
(
select row_id as rb, t2.id, t2.eventDate
from events t2
) as tb
on rb = ra+1 and ta.id = tb.id and ta.eventDate = tb.eventDate
I have found a way to match the eventDate of the next row but the only drawback is that it will return the number of consecutive dates - 1 rows. But in your code you can just loop 1 extra time.
SET #inc = 0;
SET #innerInc = 1;
SELECT t1.id, t1.eventDate
FROM (
SELECT id, eventDate, (#inc := #inc + 1) as increment FROM temp
) t1
WHERE t1.eventDate = (
SELECT t2.eventDate FROM (
SELECT eventDate, (#innerInc := #innerInc + 1) as increment FROM temp
) t2
WHERE t2.increment = t1.increment
);
Here is the SQLFiddle for this: Here
This should be able to do it with a single table scan (no subqueries,joins,etc..)
SELECT t.id,t.eventDate
FROM (
SELECT
IF(id = #prevID AND eventDate = #prevDate, #counter, #counter := #counter+1) as c,
#prevID := id as id,
#prevDate := eventDate as eventDate
FROM events e
JOIN (SELECT #counter := 0, #prevID := NULL, #prevDate := NULL) as stuff
WHERE 1 #or some where condition for events
ORDER BY row_id ASC
) as t
GROUP BY t.c
If it's not specifically that you need the positionally consecutive entry, but rather that if you grouped by eventDate and found entries with the same eventDate then you'd want those records, then the following:
select *
from Table a
join (select eventDate, count(*)
from Table
group by eventDate
having count(*) > 1) b
on (a.eventDate = b.eventDate)
The arbitrary dependence on the position of the data suggests that there is some other property you're not sharing, and it is by that property that the records are retrieved and ordered. If such a property determines the record's position, then it's precisely by ordering or grouping with that property that you can efficiently solve this.
After throwing away my self-join I think you're going to have to generate row_numbers for each sub_query:
select #rn1 := #rn1+1 as ra, t1.id, t1.eventDate
from events t1
join (select #rn1 := 0) r;
and then join that to
select #rn2 := #rn2+1 as rb, t2.id, t2.eventDate
from events t2
join (SELECT #rn2 := 0) r;
so final answer:
select ta.id, ta.eventDate from
(
select #rn1 := #rn1+1 as ra, t1.id, t1.eventDate
from events t1
join (select #rn1 := 0) r
) as ta
join
(
select #rn2 := #rn2+1 as rb, t2.id, t2.eventDate
from events t2
join (SELECT #rn2 := 0) r
) as tb
on rb = ra+1 and ta.id = tb.id and ta.eventDate = tb.eventDate
Results:
1 February, 27 2014 23:21:41+0000
2 February, 27 2014 23:25:08+0000
3 February, 28 2014 15:36:55+0000
4 February, 28 2014 19:33:31+0000

mySQL Ranking (and draws)

Next weekend we're having a competition with 3 qualifications a semifinal and a final. Only the best 15 participants could compete in the semifinal. Only the best 6 compete in the Finals.
in the qualifications you get a score from 0 to 100 for each qualification
I'm looking to find a way to select the contesters for the semi-final. This should be based on (rank of qualification1) * (rank of qualification2) * (rank of qualification3)
so i need something like:
select id, name, ((.... as RANK_OF_SCORE_1) * (.. as RANK_OF_SCORE_2) * (... as RANK_OF_SCORE_3)) as qualification_score from participants order by qualification_score desc limit 15
but of course this is not valid mySQL.
Besides this problem if tho contesters have the same score, they should be both included in the semi-finals even if this exceeds the maximum of 15.
For the finals, we would like to select the best 6 of the semi-final scores. If 2 scores are the same we would like to select on the qualifications..
option 1 : use postgres, which support windowing functions (namely RANK() and DENSE_RANK())
SELECT user_id, score, rank() over (order by score desc) from scores;
Time : 0.0014 s
option 2 : use a self- join : the rank of a user with score X is (1 +the count(*) of users with score less than X) ; this is likely to be pretty slow
CREATE TABLE scores( user_id INT PRIMARY KEY, score INT, KEY(score) );
INSERT INTO scores SELECT id, rand()*100 FROM serie LIMIT 1000;
SELECT a.user_id, a.score, 1+count(b.user_id) AS rank
FROM scores a
LEFT JOIN scores b ON (b.score>a.score)
GROUP BY user_id ORDER BY rank;
+---------+-------+------+
| user_id | score | rank |
+---------+-------+------+
| 381 | 100 | 1 |
| 777 | 100 | 1 |
| 586 | 100 | 1 |
| 907 | 100 | 1 |
| 790 | 100 | 1 |
| 253 | 99 | 6 |
| 393 | 99 | 6 |
| 429 | 99 | 6 |
| 376 | 99 | 6 |
| 857 | 99 | 6 |
| 293 | 99 | 6 |
| 156 | 99 | 6 |
| 167 | 98 | 13 |
| 594 | 98 | 13 |
| 690 | 98 | 13 |
| 510 | 98 | 13 |
| 436 | 98 | 13 |
| 671 | 98 | 13 |
time 0.7s
option 3 :
SET #rownum = 0;
SELECT a.user_id, a.score, b.r FROM
scores a
JOIN (
SELECT score, min(r) AS r FROM (
SELECT user_id, score, #rownum:=#rownum+1 AS r
FROM scores ORDER BY score DESC
) foo GROUP BY score
) b USING (score)
ORDER BY r;
time : 0.0014 s
EDIT
SET #rownum1 = 0;
SET #rownum2 = 0;
SET #rownum3 = 0;
SELECT s.*, s1.r, s2.r, s3.r FROM
scores s
JOIN
(
SELECT score_1, min(r) AS r FROM (
SELECT score_1, #rownum1:=#rownum1+1 AS r
FROM scores ORDER BY score_1 DESC
) foo GROUP BY score_1
) s1 USING (score_1) JOIN (
SELECT score_2, min(r) AS r FROM (
SELECT score_2, #rownum2:=#rownum2+1 AS r
FROM scores ORDER BY score_2 DESC
) foo GROUP BY score_2
) s2 USING (score_2) JOIN (
SELECT score_3, min(r) AS r FROM (
SELECT score_3, #rownum3:=#rownum3+1 AS r
FROM scores ORDER BY score_3 DESC
) foo GROUP BY score_3
) s3 USING (score_3)
ORDER BY s1.r * s2.r * s3.r;