This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 4 years ago.
I have an exercise on queries and I can't seem to find the solution to one of them. I have two tables : Aircrafts and Certified.
In the table Aircrafts I have the information AID (ID of the plane), Aname (Name of the plane) and Crusingrange(Max distance the plane have) :
AID Aname Crusingrange
1 BoeingFr 25000
2 BoeingUS 50000
3 Jet01 3000
4 Jet02 4000
In the table CERTIFIED I have this information AID (ID of the plane) and EID (ID of the pilot) :
AID EID
1 199
2 199
1 110
3 110
3 109
4 109
What I want is the ID of the pilot and the ID of the plane with the greatest cruising range he/she can fly.
EID AID
199 2
110 1
109 4
I know I have to use MAX within INNER JOIN but I really don't find the solution and I have tried to break down my code but still impossible.
Thank you
Firstly, get maximum value of Crusingrange for a EID, using Join and Group By. Use this result as a Derived table.
Now, again Join back to the main tables using maximum value of Crusingrange and EID.
For MySQL, try the following:
SELECT c1.EID,
a1.AID
FROM CERTIFIED AS c1
JOIN Aircrafts AS a1 ON a1.AID = c1.AID
JOIN
(
SELECT c.EID,
MAX(a.Crusingrange) AS Crusingrange
FROM CERTIFIED AS c
JOIN Aircrafts AS a ON a.AID = c.AID
GROUP BY c.EID
) AS dt ON dt.Crusingrange = a1.Crusingrange AND
dt.EID = c1.EID
If your DB supports row_number() window function, then you can join your tables, then sort rows per EID by Crusingrange, and choose longest ones:
SELECT EID, AID FROM (
SELECT CERTIFIED.AID, CERTIFIED.EID, row_number() over(partition by CERTIFIED.EID ORDER BY Aircrafts.Crusingrange DESC) rn
FROM CERTIFIED
JOIN Aircrafts
ON CERTIFIED.AID = Aircrafts.AID
) t
WHERE rn = 1
Related
In sql help i have 3 tables, table one is asset table which is as follow
id
asset_code
asset_name
asset_group
asset_quantity
1
A001
computer
4
7
2
A002
keyboard
6
4
and another table is asset_allocation
id
asset_id
allocated_quantity
allocated_location
returned
1
1
2
IT office
no
2
2
1
main hall
yes
the last table is asset_liquidated which will present assets that are no longer going to be used
id
asset_id
liquidated_quantity
1
1
1
Now lets say that i have 7 computer out of which 2 are allocated but not returned and i have 4 keyboards out of which 1 is allocated and it is returned back and 1 computer is liquidated means it is never going to be used
so now here i want to join these 3 tables and find inventory of my current stock in hand.
Now this is the query now i need to add this
where asset_allocation.returned is enum no inside this query
SELECT id,asset_code, asset_name, asset_group, asset_quantity,allocated_quantity,liquidated_quantity,
asset_quantity - COALESCE(AA.allocated_quantity, 0) - COALESCE(AL.liquidated_quantity, 0) available_quantity
FROM asset A
LEFT JOIN (SELECT asset_id, SUM(allocated_quantity) allocated_quantity
FROM asset_allocation
GROUP BY asset_id) AA ON A.id = AA.asset_id
LEFT JOIN (SELECT asset_id, SUM(liquidated_quantity) liquidated_quantity
FROM asset_liquidated
GROUP BY asset_id) AL ON A.id = AL.asset_id;
I believe what you are looking for is adding WHERE returned = 'no' in your first JOIN like so:
SELECT id,asset_code, asset_name, asset_group, asset_quantity,allocated_quantity,liquidated_quantity,
asset_quantity - COALESCE(AA.allocated_quantity, 0) - COALESCE(AL.liquidated_quantity, 0) available_quantity
FROM asset A
LEFT JOIN (SELECT asset_id, SUM(allocated_quantity) allocated_quantity
FROM asset_allocation
WHERE returned = 'no'
GROUP BY asset_id) AA ON A.id = AA.asset_id
LEFT JOIN (SELECT asset_id, SUM(liquidated_quantity) liquidated_quantity
FROM asset_liquidated
GROUP BY asset_id) AL ON A.id = AL.asset_id;
That changes the available quantity for keyboard from 3 to 4 for me
your query:
vs. mine:
My table is:
id
student_id
exam_date
license
result
1
101
01-11-2020
B2
FAILED
2
102
15-11-2020
A
PASSED
3
103
22-11-2020
D
FAILED
4
101
01-10-2020
D
PASSED
5
104
01-12-2020
A
PASSED
6
103
29-11-2020
D
PASSED
7
101
01-12-2020
B2
PASSED
8
105
01-09-2020
B2
FAILED
9
104
01-11-2020
A
FAILED
10
105
01-11-2020
B2
PASSED
I need to select the results that would have the first result according to the exam date according to each student id and the license column. If the same student takes different license exam, these two results need to come up as well. But I need only one result row for each student id and license value.
The result should look like this:
id
student_id
exam_date
license
result
1
101
01-11-2020
B2
FAILED
2
102
15-11-2020
A
PASSED
3
103
22-11-2020
D
FAILED
4
101
01-10-2020
D
PASSED
8
105
01-09-2020
B2
FAILED
9
104
01-11-2020
A
FAILED
I've done the research and queries and so far I only got 1 row for student_id although the student takes two different license examination.
The following is my query:
SELECT scct_outer.id, scct_outer.stud_id, scct_outer.exam_date, scct_outer.license, scct_outer.result
FROM stud_cdl_comp_test AS scct_outer
INNER JOIN
(SELECT stud_id, MIN(exam_date) AS MinExamDate
FROM stud_cdl_comp_test AS scct
INNER JOIN stud AS s ON scct.stud_id = s.id
INNER JOIN agent_profile AS ap ON s.agent_profile_id = ap.id
GROUP BY stud_id) groupedscct
ON scct_outer.stud_id = groupedscct.stud_id
AND scct_outer.exam_date = groupedscct.MinExamDate
The problem with you original code is that it is missing a correlartion on the licences between the outer query and the subquery. You would phrase it as:
select s.*
from stud_cdl_comp_test s
inner join (
select student_id, licence, min(exam_date) as minexamdate
from stud_cdl_comp_test as scct
group by stud_id, licence
) s1 on s1.student_id = s.student_id and s1.license = s.license and s1.minexamdate = s.date
I have no idea what stud and agent_profile are, so I removed the from the query.
That said, this is not the method I would recommend - a simple and efficient option is to filter with a subquery:
select *
from stud_cdl_comp_test s
where s.exam_date = (
select min(s1.exam_date)
from stud_cdl_comp_test s1
where s1.student_id = s.student_id and s1.license = s.license
)
This can take advantage of an index on (student_id, license, exam_date).
Alternatively, you can use row_number(), available in MySL 8.0:
select *
from (
select s.*,
row_number() over(partition by student_id, licence order by exam_date) rn
from stud_cdl_comp_test s
) s
where rn = 1
Thinking that you are grouping by student_id in this case is almost incorrect in this case. What are actually grouping by is student + license. Let's call this key combination individual_license.
Here's what the solution will look like:
SELECT
st.id,
st.stud_id,
st.exam_date,
st.license,
st.result
FROM stud_cdl_comp_test AS st
INNER JOIN
(SELECT
MIN(exam_date) AS min_date,
st_inner.student_id,
st_inner.license
FROM stud_cdl_comp_test AS st_inner
GROUP BY st_inner.student_id, st_inner.license
) grouped_inner
ON grouped_inner.student_id = st.student_id
AND grouped_inner.license = st.license
AND grouped_inner.min_date = st.exam_date;
This should work.
I am new to sql joins. can anyone explain me with a simple example how left/right/outer joins work with respect to joining 3 tables?
say i have the following tables
Area
Area ID PersonID
1 11
2 12
3 13
Disease
DiseaseID Disease Name
4 ABC
5 DEF
Attack
AttackID Disease ID AreaID
111 4 1
222 4 2
222 5 1
I want to know the count of people who were attacked and who were not attacked by disease.
I hope it will full fill your requirement
SELECT R.AREA_ID,
COUNT(A.DISEASEID) ATTACKED_PEOPLE
FROM AREA R
LEFT JOIN ATTACK A
ON A.AREAID=R.AREA_ID
LEFT JOIN DISEASE D
ON D.SISEASE_ID=A.DISEASEID
GROUP BY R.AREA_ID;
Here we have used joining of three tables using LEFT JOIN concept. So the left side table (AREA) retrieves all the records and the right side table (ATTACK) retrieves only matched records with left side table. As well as second LEFT JOIN condition is for retrieving the count of diseases from ATTACK table. So that we can get count of people who were attacked and who were not using the above query.
Only tables AREA and ATTACK contains informations you need here (you don't want disease name),
so at first attach informations about attacks to table containing person ID:
select personid, count(diseaseid) cnt
from area left join attack using (areaid)
group by personid
PersonID Cnt
11 2
13 0
12 1
Now you can count ill and healthy persons:
select
count(case when cnt > 0 then 1 end) Affected,
count(case when cnt = 0 then 1 end) NotAffected
from (
select personid, count(diseaseid) cnt
from area left join attack using (areaid)
group by personid)
Affected NotAffected
2 1
SQLFiddle demo
There are several ways to get last output, try to find others. And example how to join all three tables:
select personid, count(diseaseid) cnt,
listagg(DiseaseName, ', ') within group (order by DiseaseName) Diseases
from area
left join attack using (areaid)
left join disease using (diseaseid)
group by personid
PERSONID CNT DISEASES
-------- ---- ----------
11 2 ABC, DEF
12 1 ABC
13 0
If you want to find only affected persons use INNER JOIN instead of LEFT.
Table semesters:
semesterID startDate
1 2013-01-01
2 2013-03-01
3 2013-06-01
Table classes:
classID class_title semesterID
1 Math 1
2 Science 1
3 Math 2
4 Science 2
5 Math 3
6 Science 3
Table persons:
personID firstName lastName
1 John Jones
2 Steve Smith
Table class_person:
classID personID
1 1
2 1
5 1
6 1
3 2
4 2
5 2
6 2
I need to get a list of all the people, with the first semester in which they took a class (semester with the oldest startDate).
firstName, lastName, semesterID, startDate
John Jones 1 2013-01-01
Steve Smith 2 2013-03-01
I've spent hours trying to figure this out. Here's the closest I've gotten (although it is not close at all!):
SELECT p.firstName, p.lastName, MIN(s.startDate) AS min_startDate
FROM semesters s
INNER JOIN classes c ON s.semesterID = c.semesterID
INNER JOIN class_person cp ON cp.classID = c.classID
INNER JOIN persons p ON p.personID = cp.personID
GROUP BY cs.personID
ORDER BY min_startDate, p.lastName, p.firstName
Any help would be massively appreciated. Thank you.
You could end up using a monster like the following (fiddle):
select persons.firstName, persons.lastName,
semesters.semesterID, semesters.startDate
from persons, semesters,
(select p.personID,
(select semesters.semesterID
from semesters, classes, class_person
where semesters.semesterID = classes.semesterID
and classes.classID = class_person.classID
and class_person.personID = p.personID
order by semesters.startDate
limit 1) as semesterID
from (select distinct personID from class_person) as p
) as ps
where persons.personID = ps.personID
and semesters.semesterID = ps.semesterID
The subquery p identifies all persons. For each, ps will contain a single row. Its personID is simply copied, its semesterID is computed by a subquery, which sorts semesters by date but returns the ID. The outermost query then re-adds the date.
If you don't really need the semesterID, you could avoid one layer. If your semesters are in order, i.e. their IDs have the same order as their startDates, then you could simply use a single query, much like your own, and return min(semesterID) and min(startDate).
On the whole, this question reminds me a lot of my own question, Select one value from a group based on order from other columns. Answers suggested there will likely apply here as well. In particular, there are approaches using user variables which I still don't feel comfortable about, but which will make this whole mess a lot easier and seem to work well enough. So adapting this answer, you get a query like this (fiddle):
SELECT p.firstName, p.lastName, s2.semesterID, s2.startDate
FROM persons p
INNER JOIN (
SELECT #rowNum:=IF(#personID=cp.personID,#rowNum+1,1) rowNum,
#personId:=cp.personID personID,
s.semesterID, s.startDate
FROM (SELECT #personID:=NULL,#rowNum:=0) dummy
INNER JOIN semesters s
INNER JOIN classes c ON s.semesterID = c.semesterID
INNER JOIN class_person cp ON cp.classID = c.classID
ORDER BY cp.personID, s.startDate
) s2 ON p.personID = s2.personID
WHERE s2.rowNum = 1
I'll leave adapting the other answers as an excercise.
I'm trying to find the leagues (lid) where two users are apart of.
Here are my tables:
Table leagues:
*id* lname
--------------
1 Hard C
3 Fun
5 Crazy
Table match:
*userid* *lid*
-----------------
1 1
4 5
1 3
2 1
4 1
4 3
*Are primary keys
match.lid is foreign key to leagues.id (a user cannot not be part of the same league twice)
Here's what I have so far (a start):
SELECT t1.lid, t2.lname
FROM match t1
JOIN leagues t2 on t1.lid = t2.id
So far I managed to join the two tables and get the names. My ultimate goal is to show the lid's where two users are part of the same league, say userid 1 and 4.
userid 1 is a member of lid 1 and 3
userid 4 is a member of lid 5, 1, and 3
Both users meet in league(lid) 1 and 3
So I need a query that shows only the league where both users meet. Like this:
lid lname
--------------
1 Hard C
3 Fun
Since userid 1 and 4 meet in league 1 and 3, the results should show that. I can run two queries for each user and check which leagues both users meet via php, but I think it's more efficient to run one query.
SELECT m1.lid, l.lname FROM
`match` m1, `match` m2, leagues l
WHERE m1.lid = m2.lid AND m1.lid = l.id
AND m1.userid = 1
AND m2.userid = 4
There are a few ways. The most straightforward is:
SELECT id AS lid,
lname
FROM leagues
WHERE id IN
( SELECT lid
FROM match
WHERE userid = 1
)
AND id IN
( SELECT lid
FROM match
WHERE userid = 4
)
;
Another way, which is a bit less direct, but may perform better — you can try it and see — is to use JOIN:
SELECT id AS lid,
lname
FROM leagues
JOIN match AS match1
ON match1.lid = leagues.id
AND match1.userid = 1
JOIN match AS match2
ON match2.lid = leagues.id
AND match2.userid = 4
;