Select multiple rows with highest rank in mysql - mysql

I am using MySQL.
From these two tables, I need to select all room(s) that have the highest number of nurses allocated per bed. This can be only one room or more than one if there is a tie.
Table Allocation
+-------+---------+
| nurse | room |
+-------+---------+
|911923 | 1 |
|916923 | 1 |
|931923 | 1 |
|931926 | 1 |
|931927 | 4 |
|931928 | 4 |
+-------+---------+
Table Room
+--------+--------+
| number | size |
+--------+--------+
| 1 | 2 |
| 4 | 1 |
+-------+---------+
I am trying to select the row(s) with the highest rank, but Limit 1 only limits for one value, in this example both rooms have the same rank. How can I select all rows with the highest rank, if multiple rows have the same rank?
SELECT ROOM.number,
(SELECT COUNT(*) FROM ALLOCATION
WHERE ALLOCATION.room = ROOM.number) / ROOM.size AS nurses_per_bed,
DENSE_RANK() OVER (ORDER BY nurses_per_bed DESC) AS SEQ
FROM ROOM
LIMIT 1

Step by step:
Aggregate allocations per room in order to get the rooms' numbers of nurses.
Join rooms and nurse counts (i.e. allocation aggregate results).
Rank the resulting rows by ratio.
Show only ranked #1 rows.
The query:
select room, nurses, ratio
from
(
select
r.room,
a.nurses,
a.nurses / r.size as ratio,
dense_rank() over (order by a.nurses / r.size) as rnk
from room r
join
(
select number as room, count(*) as nurses
from allocation
group by number
) a on a.room = r.room
) ranked
where rnk = 1
order by room;

Related

Combine multiple table and use Group By Function in MYSQL

I have 5 different datasets from 5 different tables.. From those 5 different tables I have taken below group by data..
select number,count(*) as total from tb01 group by number limit 5;
select number,count(*) as total from tb02 group by number limit 5;
Like that I can retrieve 5 different datasets. Here is an example.
+-----------+-------+
| number | total |
+-----------+-------+
| 114000259 | 1 |
| 114000400 | 1 |
| 114000686 | 1 |
| 114000858 | 1 |
| 114003895 | 1 |
+-----------+-------+
Now I need to combine those 5 different tables such as below tabular format.
+-----------+-------+-------+-------+
| number | tb01 | tb02 | tb03 |
+-----------+-------+-------+-------+
| 114000259 | 1 | 2 | 1 |
| 114000400 | 1 | 0 | 1 |
| 114000686 | 1 | 3 | 1 |
| 114000858 | 1 | 1 | 5 |
| 114003895 | 1 | 0 | 1 |
+-----------+-------+-------+-------+
Can someone help me to combine those 5 grouped data sets and get the union as above.
Note: I dont need the header as same as table names..these headers can be anything
Further I dont need to limit 5, above is to get a sample of 5 data only. I have a large dataset.
It's a job for JOINs and subqueries. My answer will consider three tables. It should be obvious how to expand it to five.
Your first subquery: get all possible numbers.
SELECT number FROM tb01 UNION
SELECT number FROM tb02 UNION
SELECT number FROM tb03
Then you have a subquery for each table to get the count.
SELECT number, COUNT(*) AS total
FROM tb02 GROUP BY number
Then you LEFT JOIN everything and SELECT from that.
SELECT numbers.number,
tb01.total tb01,
tb02.total tb02,
tb03.total tb03
FROM (
SELECT number FROM tb01 UNION
SELECT number FROM tb02 UNION
SELECT number FROM tb03
) numbers
LEFT JOIN (
SELECT number, COUNT(*) AS total
FROM tb01 GROUP BY number
) tb01 ON numbers.number = tb01.number
LEFT JOIN (
SELECT number, COUNT(*) AS total
FROM tb02 GROUP BY number
) tb02 ON numbers.number = tb02.number
LEFT JOIN (
SELECT number, COUNT(*) AS total
FROM tb03 GROUP BY number
) tb03 ON numbers.number = tb01.number
You can add ORDER BY and LIMIT clauses to that overall query as necessary.
The first subquery together with the LEFT JOIN ensures that you get results even if some of your tables are missing number rows. (Some DBMSs have FULL OUTER JOIN, but MySQL does not.)
Pro tip: If you use LIMIT without ORDER BY, you get an unpredictable subset of your rows. Unpredictable is worse than random, because you get the same subset in testing with small tables, but when your tables grow you may start getting different subsets. You'll never catch the problem in unit testing. LIMIT without ORDER BY is a serious error.

SQL Query sort and update by row number (SQLFiddle example)

I have a sports database where I want to sort the data by a custom field ('Rating') and update the field ('Ranking') with the row number.
I have tried the following code to sort the data by my custom field 'Rating'. It works when I sort it by a normal field, but not with a custom/calculated field. When the sorting has been done, I want it to update the field 'Ranking' with the row number.
Ie the fighter with the highest 'Rating' should have the value '1' as 'Ranking.
SELECT id,lastname, wins, Round(((avg(indrating)*13) + (avg(Fightrating)*5) * 20) / 2,2) as Rating,
ROW_NUMBER() OVER (ORDER BY 'Rating' DESC) AS num
from fighters
JOIN fights ON fights.fighter1 = fighters.id
GROUP BY id
The code above isn't sorting the Rating accurately. It sorts by row number, but the highest Rating isn't rated as #1. It seems a bit random.
SQL Fiddle: http://sqlfiddle.com/#!9/aa1fca/1 (This example is correctly sorted, but I want it to update the "Ranking" column by row number - meaning the highest rated fighter (by 'Rating') gets '1' in the Ranking column, the second highest reated fighter gets '2' in the Ranking column etc).
Also I would like to be able to add WHERE clause in the fighters table (where fighters.organization = 'UFC') for example.
First, let's fix your query so it runs on MySQL < 8.0. This requires doing the computing and sorting in a subquery, then using a variable to compute the rank:
select
id,
rating,
#rnk := #rnk + 1 ranking
from
(select #rnk := 0) r
cross join (
select
fighter1 id,
round(((avg(indrating)*13) + (avg(fightrating)*5) * 20) / 2,2) as rating
from fights
group by fighter1
order by rating desc
) x
Now we use the update ... join ... set ... syntax to update the fighters table:
update fighters f
inner join (
select
id,
rating,
#rnk := #rnk + 1 ranking
from
(select #rnk := 0) r
cross join (
select
fighter1 id,
round(((avg(indrating)*13) + (avg(fightrating)*5) * 20) / 2,2) as rating
from fights
group by fighter1
order by rating desc
) x
) y on y.id = f.id
set f.ranking = y.ranking;
Demo in a MySQL 5.6 fiddle based on the fiddle you provided in the comments.
The select query returns:
| id | rating | ranking |
| --- | ------ | ------- |
| 3 | 219.5 | 1 |
| 4 | 213 | 2 |
| 1 | 169.5 | 3 |
| 2 | 156.5 | 4 |
And here is the content of the fighters table after the update:
| id | lastname | ranking |
| --- | ---------- | ------- |
| 1 | Gustafsson | 3 |
| 2 | Cyborg | 4 |
| 3 | Jones | 1 |
| 4 | Sonnen | 2 |

select non group by columns with count in Mysql

I have a table tbl with three columns:
id | fk | dateof
1 | 1 | 2016-01-01
2 | 1 | 2016-01-02
3 | 2 | 2016-02-01
4 | 2 | 2016-03-01
5 | 3 | 2016-04-01
I want to get the results like this
Id count of Id max(dateof)
2 | 2 | 2016-01-02
4 | 2 | 2016-03-01
5 | 1 | 2016-04-01
My try
SELECT id,tbl.dateof dateof
FROM tbl
INNER JOIN
(SELECT fk, MAX(dateof) dateof ,
count(id) cnt_of_id -- How to get this count value in the result
FROM tbl
GROUP BY fk) temp
ON tbl.fk = temp.fk AND tbl.dateof = temp.dateof
This is an aggregation query, but you don't seem to want the column being aggregated. That is ok (although you cannot distinguish the rk that defines each row):
select count(*) as CountOfId, max(dateof) as maxdateof
from t
group by fk;
In other words, your subquery is pretty much all you need.
If you have a reasonable amount of data, you can use a MySQL trick:
select substring_index(group_concat(id order by dateof desc), ',', 1) as id
count(*) as CountOfId, max(dateof) as maxdateof
from t
group by fk;
Note: this is limited by the maximum intermediate size for group_concat(). This parameter can be changed and it is typically large enough for this type of query on a moderately sized table.
You obviously want one result row per fk, so group by it. Then you want the max ID, the row count and the max date for each fk:
select
max(id) as max_id,
count(*) as cnt,
max(date_of) as max_date_of
from tbl
group by fk;

MySQL - Order query and display one random row at the top

I have a table that looks like:
ID | TICKET PRICE | VIP
----------------------------
1 | $45.00 | 1
2 | $40.00 | 1
3 | $20.00 | 0
4 | $65.00 | 0
5 | $45.00 | 1
I need to query this table to order all rows by Price, but always show one random row which has a VIP=1 at the top. So for example, the query should return:
ID | TICKET PRICE | VIP
----------------------------
2 | $40.00 | 1
3 | $20.00 | 0
1 | $45.00 | 1
5 | $45.00 | 1
4 | $65.00 | 0
And when you refresh the page, row ID 5 may then become the first row, because it has a VIP=1.
I currently have my query looking like:
(SELECT * FROM tickets WHERE VIP=1 ORDER BY rand() LIMIT 1)
UNION
(SELECT * FROM tickets WHERE VIP=0 ORDER BY ticket_price ASC)
This issue with this is that it will only display one VIP row. How would I query this data properly?
Use order by. Here is one method:
select t.*
from (select t.*, (#rn := #rn + 1) as seqnum
from tickets t cross join
(select #rn := 0) params
order by vip desc, rand()
) t
order by (seqnum = 1) desc, price asc;
This uses the subquery to identify the one row to keep at the top. Then it uses this information for ordering in the outer query.
If your rows have a unique identifier, you could also do:
select t.*
from tickets t cross join
(select id from tickets where vip = 1 order by rand() limit 1) as t1
order by (t.id = t1.id) desc, price asc;

SQL query for rolling changes

I have the following two tables:
1) Table name: period
+----------+
| PeriodID |
+----------+
| 1 |
| 2 |
| 3 |
| 4 |
+----------+
2) Table name: value
+-------------+--------+
| StartPeriod | Amount |
+-------------+--------+
| 1 | 100 |
| 3 | 200 |
+-------------+--------+
The first table represents time periods, like months. The second table represents the amount for each month, but only when it's different from the previous month. The amount starts at 100, stays at 100 for period 2, then jumps up to 200 beginning in period 3, and stays at 200 after that.
I need a query (MySQL) to return the amount for each period, like so:
+----------+--------+
| PeriodID | Amount |
+----------+--------+
| 1 | 100 |
| 2 | 100 |
| 3 | 200 |
| 4 | 200 |
+----------+--------+
So the query would return the Amount for the latest StartPeriod in the value table that's less than or equal to the PeriodID. For example, for PeriodID 2 it returns the Amount for StartPeriod 1 because there is no value for StartPeriod2 and 1 is the largest number less than or equal to 2 that has an Amount in the value table.
(Sorry the tables are so ugly)
Thank you!
You can do it using a correlated sub-query:
SELECT PeriodID,
(SELECT Amount
FROM Value
WHERE StartPeriod <= PeriodID
ORDER BY StartPeriod DESC LIMIT 1) AS Amount
FROM Period AS p
Demo here
Using variables probably performs better compared to the correlated sub-query:
SELECT PeriodID,
#amount := IF(Amount IS NOT NULL, Amount, #amount) AS Amount
FROM (
SELECT PeriodID, Amount
FROM Period AS p
LEFT JOIN Value AS v ON p.PeriodID = v.StartPeriod) AS t
CROSS JOIN (SELECT #amount := -1) AS var
ORDER BY PeriodID
Demo here
A simple subselect that selects the value for the highest startperiod lower or equal to the period-id could achive that:
select
periodid,
(select amount from value where startperiod <= periodid order by startperiod desc limit 1)
from period
order by periodid;
http://sqlfiddle.com/#!9/9f29c/3