How to select last XX entries for each team? - 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

(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;

Related

How to select last entries for each groups?

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 |

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

how to query min and max values from table and show it like ranges/sequences?

I have a table like this one (table):
id | from | to | count
1 | 1 | 25 | 25
1 | 26 | 50 | 25
1 | 76 | 100 | 25
1 | 1501 | 1525 | 25
and what I want is group in "full" ranges like this one:
id | from | to | count
1 | 1 | 50 | 50
1 | 76 | 100 | 25
1 | 1501 | 1525 | 25
I'm using MySQL and my (horrible) query doesn't work properly because I don't return what I want.
SELECT id, MIN(from), MAX(to), SUM(count)
FROM table
GROUP BY id
HAVING (MAX(to)-MIN(from)+1) = SUM(count)
Note the following data set is a little more complicated than that provided in the question...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,val_from INT NOT NULL
,val_to INT NOT NULL
,count INT NOT NULL
);
INSERT INTO my_table VALUES
(1,1,25,25),
(2,26,35,12),
(3,36,50,13),
(4,76,100,25),
(5,1501,1525,25);
SELECT MIN(val_from) start
, MAX(val_to) finish
, SUM(count) total
FROM
(
SELECT *
, CASE WHEN val_from <= #prev_val_to + 1 THEN #i := #i ELSE #i:=#i+1 END i
, #prev_val_to := val_to prev_val_to
FROM my_table
, (SELECT #prev_val_to := null, #i:=1) vars
ORDER
BY id
) x
GROUP BY i;
+-------+--------+-------+
| start | finish | total |
+-------+--------+-------+
| 1 | 50 | 50 |
| 76 | 100 | 25 |
| 1501 | 1525 | 25 |
+-------+--------+-------+
Select
id,
case when [From] <= 50 then 1
when [From] <= 100 then 51
when [From] <= 1525 then 101 end [From],
case when [From] <= 50 then 50
when [From] <= 100 then 100
when [From] <= 1525 then 1525 end [to],
Sum(count) count
From table
group by id, case when [From] <= 50 then 1
when [From] <= 100 then 51
when [From] <= 1525 then 101 end

Get the top n results per group [duplicate]

This question already has answers here:
Get top n records for each group of grouped results
(12 answers)
Using LIMIT within GROUP BY to get N results per group?
(14 answers)
Closed 5 years ago.
I am using the sql to retrieve the last 20 rows from the table grouped by date. I would like to limit it so that within each post_day group only the top 10 rows votes DESC are selected.
SELECT *, DATE(timestamp) as post_day
FROM stories
ORDER BY post_day DESC, votes DESC
LIMIT 0, 20
This is what the table looks like:
STORYID TIMESTAMP VOTES
1 2015-03-10 1
2 2015-03-10 2
3 2015-03-9 5
4 2015-03-9 3
Schema
create table stories
( storyid int auto_increment primary key,
theDate date not null,
votes int not null
);
insert stories(theDate,votes) values
('2015-03-10',1),
('2015-03-10',2),
('2015-03-09',5),
('2015-03-09',3),
('2015-03-10',51),
('2015-03-10',26),
('2015-03-09',75),
('2015-03-09',2),
('2015-03-10',12),
('2015-03-10',32),
('2015-03-09',51),
('2015-03-09',63),
('2015-03-10',1),
('2015-03-10',11),
('2015-03-09',5),
('2015-03-09',21),
('2015-03-10',1),
('2015-03-10',2),
('2015-03-09',5),
('2015-03-09',3),
('2015-03-10',51),
('2015-03-10',26),
('2015-03-09',75),
('2015-03-09',2),
('2015-03-10',12),
('2015-03-10',44),
('2015-03-09',11),
('2015-03-09',7),
('2015-03-10',19),
('2015-03-10',7),
('2015-03-09',51),
('2015-03-09',79);
The Query
set #rn := 0, #thedate := '';
select theDate, votes
from
(
select storyid, theDate, votes,
#rn := if(#thedate = theDate, #rn + 1, 1) as rownum,
#thedate := theDate as not_used
from stories
order by theDate, votes desc
) A
where A.rownum <= 10;
The Results
+------------+-------+
| theDate | votes |
+------------+-------+
| 2015-03-09 | 79 |
| 2015-03-09 | 75 |
| 2015-03-09 | 75 |
| 2015-03-09 | 63 |
| 2015-03-09 | 51 |
| 2015-03-09 | 51 |
| 2015-03-09 | 21 |
| 2015-03-09 | 11 |
| 2015-03-09 | 7 |
| 2015-03-09 | 5 |
| 2015-03-10 | 51 |
| 2015-03-10 | 51 |
| 2015-03-10 | 44 |
| 2015-03-10 | 32 |
| 2015-03-10 | 26 |
| 2015-03-10 | 26 |
| 2015-03-10 | 19 |
| 2015-03-10 | 12 |
| 2015-03-10 | 12 |
| 2015-03-10 | 11 |
+------------+-------+
20 rows in set, 1 warning (0.00 sec)
Usually you should use ROW_NUMBER() per group to order records inside of each group and then select records with ROW_NUMBER <= 10. In MySQL there is no ROW_NUMBER() aggregate function but you can use User-Defined variables in MySQL to emulate ROW_NUMBER()
select storyId, post_day , votes
from (
select storyId,
DATE(timestamp) as post_day,
votes,
#num := if(#grp = DATE(timestamp), #num + 1, 1) as row_number,
#grp := DATE(timestamp) as dummy
from stories,(select #num := 0, #grp := null) as T
order by DATE(timestamp) DESC, votes DESC
) as x where x.row_number <= 10;
SQLFiddle demo
Also look at:
How to select the first/least/max row per group in SQL

Split data in to ranges and display count

I am not an expert in MySql. I am trying to split the data in my table in to ranges based on account_no. This is my table.
mysql> select * from manager;
+----+-------+------------+
| id | name | account_no |
+----+-------+------------+
| 1 | John | 5 |
| 2 | Peter | 15 |
| 3 | Tony | 18 |
| 4 | Mac | 35 |
| 5 | Max | 55 |
| 6 | Smith | 58 |
+----+-------+------------+
As you see the account_no is a positive number. I want to split these records in to batches of 10, based on account_no and display the count in that range.
For an example
between 0 and 10 there is only 1 record
between 11 and 20 there are 2 records
between 21 and 30 there are no records*(So this should be omitted.)*
etc...
Actually I am hoping to get an output like this.
+-------------+-----------+-------+
| range_start | range_end | count |
+-------------+-----------+-------+
| 1 | 10 | 1 | -> because there is 1 record between 1 and 10
| 11 | 20 | 2 | -> because there are 2 records between 11 and 20
| 31 | 40 | 1 | -> because there is 1 record between 31 and 40
| 51 | 60 | 2 | -> because there are 2 records between 51 and 60
+-------------+-----------+-------+
I tried several combinations but all of them give me only one row in the result. Can anybody help me?
My suggestion would be to create a table that contains the ranges - startRange and endRange:
CREATE TABLE range_values (`startRange` int, `endRange` int) ;
INSERT INTO range_values(`startRange`, `endRange`)
VALUES (1, 10), (11, 20), (21, 30), (31, 40), (51, 60);
Once the table is created, then you can easily join on the table to get the count.
select r.startRange,
r.endRange,
count(m.account_no) totalCount
from manager m
inner join range_values r
on m.account_no >=startrange
and m.account_no <= endrange
group by r.startRange, r.endRange
See SQL Fiddle with Demo.
The benefit of the table is that you are not coding the range values and can easily updated the ranges in a table without having to change your code.
This query return:
| STARTRANGE | ENDRANGE | TOTALCOUNT |
--------------------------------------
| 1 | 10 | 1 |
| 11 | 20 | 2 |
| 31 | 40 | 1 |
| 51 | 60 | 2 |
If you don't want to create a new table, then you can use something similar to the following:
select startrange,
endrange,
count(m.account_no) TotalCount
from manager m
inner join
(
select 1 startRange, 10 endrange union all
select 11 startRange, 20 endrange union all
select 21 startRange, 30 endrange union all
select 31 startRange, 40 endrange union all
select 41 startRange, 50 endrange union all
select 51 startRange, 60 endrange
) r
on m.account_no >=startrange
and m.account_no <= endrange
group by r.startRange, r.endRange
See SQL Fiddle with demo
This should give you the output you would like, and includes the ranges with zero in the count column.
SET #rs = 0; SELECT IF(#rs, #rs := #rs + 10, #rs := 1) AS range_start, #rs + 9 AS range_end, (SELECT COUNT(id) FROM manager WHERE account_no >= #rs AND account_no <= #rs + 9) AS `count` FROM manager;
To omit the rows with zero in the count column;
SET #rs = 0; SELECT * FROM (SELECT IF(#rs, #rs := #rs + 10, #rs := 1) AS range_start, #rs + 9 AS range_end, (SELECT COUNT(id) FROM manager WHERE account_no >= #rs AND account_no <= #rs + 9) AS `count` FROM manager) AS data WHERE `count` > 0;
Try with something like this:
SELECT
CASE
WHEN range_start < 10 THEN 'Under 10'
WHEN range_start BETWEEN 11 and 29 THEN '11 - 29'
(...more ranges...)
END as range,
COUNT(*) AS count
GROUP BY range
ORDER BY range
You should use functions similar to rank and dense_rank in MSSQL, you can implement them in MySQL starting at the following link:
http://www.folkstalk.com/2013/03/grouped-dense-rank-function-mysql-sql-query.html