Obtaining database joined table with limit [duplicate] - mysql

This question already has answers here:
mysql select top n max values
(4 answers)
Closed 5 years ago.
Ive been trying to join two tables but only showing a limited amount (2) of results from the joined table. Unfortunately I havent been able to obtain the correct results. These are my tables:
Destinations
id name
------------
1 Bahamas
2 Caribbean
3 Barbados
Sailings
id name destination
---------------------------------
1 Adventure 1
2 For Kids 2
3 All Inclusive 3
4 Seniors 1
5 Singles 2
6 Disney 1
7 Adults 2
This is the query Ive tried:
SELECT
d.name as Destination,
s.name as Sailing
FROM destinations d
JOIN sailings s
ON s.destination = d.id
LIMIT 2
But this gives me 2 due to the limit:
Destination Sailing
-------------------------
Bahamas Adventure
Caribbean For Kids
SAMPLE: SQL FIDDLE
I would like LIMIT 2 to be applied only to the joined table sailings
Expected Results:
Destination Sailing
-------------------------
Bahamas Adventure
Bahamas Seniors
Caribbean Singles
Caribbean For Kids
Can someone please point me in the right direction?

try
select tmp.name as destination,d.name as sailings from (
SELECT
id,
name,
destination
FROM
(
SELECT
id,
name,
destination,
#rn := IF(#p = destination, #rn + 1, 1) AS rn,
#p := destination
FROM sailings
JOIN (SELECT #p := NULL, #rn := 0) AS vars
ORDER BY destination
) AS T1
WHERE rn <= 2
)tmp
JOIN (SELECT * FROM destinations limit 0,2) d
ON(tmp.destination=d.id)
I have made 2 derived table and joined them

Your problem is that you want to take the two highest (or lowest) members of a group, for each group in the table. In this case, you want the first two sailings for each destination group.
The canonical way you would handle this query in a database which supported analytic functions would be to use ROW_NUMBER(). But since MySQL does not support this, we can simulate it using session variables:
SET #row_number = 0;
SET #destination = NULL;
SELECT
t.Destination,
t.Sailing
FROM
(
SELECT
#row_number:=CASE WHEN #destination = Destination
THEN #row_number + 1 ELSE 1 END AS rn,
#destination:=Destination AS Destination,
Sailing,
id
FROM
(
SELECT s.id AS id, d.name AS Destination, s.name AS Sailing
FROM destinations d
INNER JOIN sailings s
ON s.destination = d.id
) t
ORDER BY
Destination,
id
) t
WHERE t.rn <= 2
ORDER BY
t.Destination,
t.rn;
Note that Barbados appears as single row, because in your sample data it only has one sailing. If you also want to restrict to only destinations having two or more sailings, this can also be done.
Output:
Demo here:
Rextester

Can you try
SELECT
d.name as Destination,
s.name as Sailing
FROM sailings s
JOIN (SELECT * from destinations LIMIT 2) d
ON s.destination = d.id
(You say you want to limit the sailings table, but I think you might want the limit on the destinations table, based on your expected output; you can adjust as necessary)

Related

Display column values and their count on SQL

I just want to ask you please this question on SQL.
Let's consider this EMPLOYEE table :
Employee Department
A 10
A 10
A 11
A 12
B 13
B 13
What I want to display is for each employee, all distinct departments (without duplicates) AND the total number of those distinct departments. So, something like this :
Employee Department total_dept
A 10 3
A 11 3
A 12 3
B 13 1
If possible, I would even prefer something like these :
Employee Department total_dept
A 10 3
A 11 null
A 12 null
B 13 1
I have a very big table (with many columns and many data) so I thought this can be an "optimisation", no ? I mean, there is no need to store the total_dept in all rows. Just put it once it's sufficient. No problem if after this I left the column empty. But I don't know if it's possible to do such thing in SQL.
So, how can I fix this please ? I tried but it seems impossible to combine count(column) with the same column...
Thank you in advance
This might be what you are looking for
SELECT
emp,
dept,
(select count(distinct dept) from TB as tbi where tb.emp = tbi.emp ) x
FROM TB
group by emp, dept;
MySQL 8.0 supports windowed COUNT:
SELECT *,COUNT(*) OVER (PARTITION BY Employee) AS total_dept
FROM (SELECT DISTINCT * FROM Employees) e
db<>fiddle demo
You could even have second resulset(I recommend to leave presentation matter to apllication layer):
SELECT *, CASE WHEN ROW_NUMBER() OVER(PARTITION BY Employee ORDER BY Department) = 1
THEN COUNT(*) OVER (PARTITION BY Employee) END AS total_dept
FROM (SELECT DISTINCT * FROM Employees) e
ORDER BY Employee, Department;
db<>fiddle demo
For the 2nd version:
SELECT
DISTINCT e.Employee, e.Department,
CASE
WHEN e.Department =
(SELECT MIN(Department) FROM Employees WHERE Employees.Employee = e.Employee)
THEN
(SELECT COUNT(DISTINCT Department) FROM Employees WHERE Employees.Employee = e.Employee)
END AS total_dept
FROM Employees e
ORDER BY e.Employee, e.Department;
See the demo

Get the record details and calculate the ranking

I have a table of exam results. I need to get the record of a specific participant and get his/her ranking too.
for example, the participant with the participant_id 15 must have the ranking 3 amongst the total 4 records. so the result i am looking for would be:
id: 1
exam_id: 3
participant_id: 15
score: 343.23
ranking: 3
I know I can get the record and get the ranking through some PHP code, but I wonder if this is possible with Mysql queries.
I googled but did not really come up with a good solution. Any answer is highly appreciated!
This is the row_number function in postgresql and other databases which unfortunately isn't present in mysql.
This article http://www.mysqltutorial.org/mysql-row_number/ explains how to emulate it in mysql
To adapt the example from it
SET #row_number = 0;
SELECT
(#row_number:=#row_number + 1) AS rank, id, participant_id,exam_id, score
FROM
exams
LIMIT 5;
Following query will give you the required result
select t2.id,t2.exam_id,t2.exam_id,t2.participant_id,t2.score,t2.ranking from
(SELECT t.id,t.exam_id,t.participant_id,t.score,(#num:=#num+1) AS
ranking FROM table1 t cross join (SELECT #num:=0) AS dummy
ORDER BY t.score desc)as t2 where t2.participant_id=15;
You can run a query to create a separate table of results and then add condition to get the rank of required participant.
here is the query you can try
SELECT * FROM (SELECT re.*, #curRow := #curRow + 1 AS rank
FROM results re JOIN (SELECT #curRow := 0) r
WHERE 1 ORDER BY re.`score` DESC) AS tablea
WHERE participant_id=15
here is the result
id exam_id participant_id score rank
1 3 15 343.23 3

Select top x records for each team

How do I select the x most recent records per team, Home and Away?
So the below gets me the most recent games for Swansea both home and away, how do I get it for all teams?
select d.date, d.hometeam, d.awayteam
from dump d
where
d.hometeam = 'Swansea'
or d.awayteam ='Swansea'
order by STR_TO_DATE(date, '%d/%m/%Y') desc limit 6
For an example of the data that I have. I'm using the CSV data provided at football-data.co.uk: http://www.football-data.co.uk/mmz4281/1415/E0.csv
I'm using MySQL however if there is a function or Stored Procedure which you find ideal for this purpose I can use SQL Server.
Edit: Expected Output
X | Date | Home Team | Away Team
----------------------------------------
Swansea| 23/03/15|Swansea |Arsenal
----------------------------------------
Swansea| 14/03/15|Man City |Swansea
----------------------------------------
Man Utd| 14/03/15|Man Utd |Man City
----------------------------------------
Man Utd| 14/03/15|Man Utd |Liverpool
Though if you have any suggestions on how better to present it I'm open to suggestions.
Where the left is the team in question, as the above table shows 2 per team, I'm trying to get 6 per team.
You just need to GROUP BY team and date:
SELECT d.team, d.date, d.hometeam, d.awayteam
FROM dump d
GROUP BY team, date
ORDER BY STR_TO_DATE(date, '%d/%m/%Y');
You want to get most recent 6 games for all team separately
Here are the 2 things need to take care.
In your schema you don't have specific column for team. So first you've to get all the team using HomeTeam & AwayTeam columns.
2nd thing you want to get 6 most recent games for each team. Means within the team group you've to do the ranking but mysql doesn't support ranking function. Although we've an alternative to for ranking functions.
based on my analysis here is the query. please try it.
SELECT
r.homeTeamOrAwayTeam AS team
, r.date
, r.hometeam
, r.awayteam
-- , r.rank
FROM (
SELECT
d.date,
d.hometeam,
d.awayteam,
subQuery.homeTeamOrAwayTeam,
CASE WHEN #runningElement = subQuery.homeTeamOrAwayTeam THEN #groupRank := (#groupRank + 1)
ELSE #groupRank := 1
END AS rank
, CASE WHEN #runningElement = subQuery.homeTeamOrAwayTeam THEN #runningElement := subQuery.homeTeamOrAwayTeam
ELSE #runningElement := subQuery.homeTeamOrAwayTeam
END AS runnigElement
FROM
dump d
JOIN (
-- to get all the hometeam & awayteam in one column
SELECT d.hometeam AS homeTeamOrAwayTeam FROM DUMP AS d
UNION
SELECT d.awayteam AS homeTeamOrAwayTeam FROM dump AS d
) AS subQuery
ON d.hometeam = subQuery.homeTeamOrAwayTeam OR d.awayteam = subQuery.homeTeamOrAwayTeam,
-- for ranking purpose
(SELECT #groupRank := 1) a,
(SELECT #runningElement := '') b
ORDER BY
subQuery.homeTeamOrAwayTeam,
STR_TO_DATE(d.date, '%d/%m/%Y')
) as r
-- set your criteria (e.g. if want to get only 6 results per team)
WHERE r.rank between 1 and 6

Reverse join in MySQL

SO,
The problem
My question is about - how to join table in MySQL with itself in reverse order? Suppose I have:
id name
1 First
2 Second
5 Third
6 Fourth
7 Fifth
8 Sixth
9 Seventh
13 Eight
14 Nine
15 Tenth
-and now I want to create a query, which will return joined records in reverse order:
left_id name right_id name
1 First 15 Tenth
2 Second 14 Nine
5 Third 13 Eight
6 Fourth 9 Seventh
7 Fifth 8 Sixth
8 Sixth 7 Fifth
9 Seventh 6 Fourth
13 Eight 5 Third
14 Nine 2 Second
15 Tenth 1 First
My approach
I have now this query:
SELECT
l.id AS left_id,
l.name,
(SELECT COUNT(1) FROM sequences WHERE id<=left_id) AS left_order,
r.id AS right_id,
r.name,
(SELECT COUNT(1) FROM sequences WHERE id<=right_id) AS right_order
FROM
sequences AS l
LEFT JOIN
sequences AS r ON 1
HAVING
left_order+right_order=(1+(SELECT COUNT(1) FROM sequences));
-see this fiddle for sample structure & code.
Some background
There's no use case for that. I was doing that in application before. Now it's mostly curiosity if there's a way to do that in SQL - that's why I'm seeking not just 'any solution' (like mine) - but as simple as possible solution. Source table will always be small (<10.000 records) - so performance is not a thing to care, I think.
The question
Can my query be simplified somehow? Also, it's important not to use variables. Order could be included in result (like in my fiddle) - but that's not mandatory.
The only thing i can think to be improved is
SELECT
l.id AS left_id,
l.name ln,
(SELECT COUNT(1) FROM sequences WHERE id<=left_id) AS left_order,
r.id AS right_id,
r.name rn,
(SELECT COUNT(1) FROM sequences WHERE id>=right_id) AS right_order
FROM
sequences AS l
LEFT JOIN
sequences AS r ON 1
HAVING
left_order=right_order;
There are 2 changes that should make this a little bit faster:
1) Calculating right order in reverse order in the first place
2) avoid using SELECT COUNT in the last line.
Edit: I aliased the ln,rn because i couldn't see the columns in fiddle
Without the SQL standard RANK() OVER(...), you have to compute the ordering yourself as you discovered.
The RANK() of a row is simply 1 + the COUNT() of all better-ranked rows. (DENSE_RANK(), for comparison, is 1 + the COUNT() of all DISTINCT better ranks.) While RANK() can be computed as a scalar subquery in your SELECT projection — as, e.g., you have done with SELECT (SELECT COUNT(1) ...), ... — I tend to prefer joins:
SELECT lft.id AS "left_id", lft.name AS "left_name",
rgt.id AS "right_id", rgt.name AS "right_name"
FROM ( SELECT s.id, s.name, COUNT(1) AS "rank" -- Left ranking
FROM sequences s
LEFT JOIN sequences d ON s.id <= d.id
GROUP BY 1, 2) lft
INNER JOIN ( SELECT s.id, s.name, COUNT(1) AS "rank" -- Right ranking
FROM sequences s
LEFT JOIN sequences d ON s.id >= d.id
GROUP BY 1, 2) rgt
ON lft.rank = rgt.rank
ORDER BY lft.id ASC;
SET #rank1=0;
SET #rank2=0;
SELECT *
FROM (SELECT *, #rank1 := #rank1 + 1 AS row_number FROM sequences ORDER BY ID ASC) t1
INNER JOIN (SELECT *, #rank2 := #rank2 + 1 AS row_number FROM sequences ORDER BY ID DESC) t2
on t1.row_number = t2.row_number
For some reason sql fiddler does show only 3 columns for this, not sure if my query is bad.

Order by COUNT per value

I have a table which stores IDs and the city where the store is located.
I want to list all the stores starting with the stores that are in the city where there are the most stores.
TABLE
ID CITY
1 NYC
2 BOS
3 BOS
4 NYC
5 NYC
The output I want is the following since I have the most stores in NYC, I want all the NYC location to be listed first.
1 NYC
4 NYC
5 NYC
2 BOS
3 BOS
SELECT count(City), City
FROM table
GROUP BY City
ORDER BY count(City);
OR
SELECT count(City) as count, City
FROM table
GROUP BY City
ORDER BY count;
Ahh, sorry, I was misinterpreting your question. I believe Peter Langs answer was the correct one.
This one calculates the count in a separate query, joins it and orders by that count (SQL-Fiddle):
SELECT c.id, c.city
FROM cities c
JOIN ( SELECT city, COUNT(*) AS cnt
FROM cities
GROUP BY city
) c2 ON ( c2.city = c.city )
ORDER BY c2.cnt DESC;
This solution is not a very optimal one so if your table is very large it will take some time to execute but it does what you are asking.
select c.city, c.id,
(select count(*) as cnt from city c2
where c2.city = c.city) as order_col
from city c
order by order_col desc
That is, for each city that you come across you are counting the number of times that that city occurs in the database.
Disclaimer: This gives what you are asking for but I would not recommend it for production environments where the number of rows will grow too large.
SELECT `FirstAddressLine4`, count(*) AS `Count`
FROM `leads`
WHERE `Status`='Yes'
AND `broker_id`='0'
GROUPBY `FirstAddressLine4`
ORDERBY `Count` DESC
LIMIT 0, 8