How to get multiple fields from one criteria using SQL group by? - sql

This query could return the wrong name, because the names I want to query are the ones of the smallest animals of each species. How could I get the right a.name
SELECT
a.name,
MIN(a.size)
FROM animal a
LEFT JOIN species s ON s.idSpecies = a.idAnimal
GROUP BY s.id

One approach for this, is to first find the smallest size of animal per species (as you have done), although I am assuming that species can never be null since an animal must belong to a species, it also does not require a join to species at this point:
SELECT a.IDSpecies, MIN(a.Size) AS Size
FROM Animal AS a
GROUP BY a.IDSpecies
Now you can join the result of this query back to your main query to filter the results.
SELECT a.Name AS AnimalName,
a.Size,
s.Name AS SpeciesName
FROM Animal AS a
INNER JOIN Species AS s
ON s.ID = a.IDSpecies
INNER JOIN
( SELECT a.IDSpecies, MIN(a.Size) AS Size
FROM Animal AS a
GROUP BY a.IDSpecies
) AS ma
ON ma.IDSpecies = a.IDSpecies
AND ma.Size = a.Size;
Another way of doing this, is to use NOT EXISTS:
SELECT a.Name AS AnimalName,
a.Size,
s.Name AS SpeciesName
FROM Animal AS a
INNER JOIN Species AS s
ON s.ID = a.IDSpecies
WHERE NOT EXISTS
( SELECT 1
FROM Animal AS a2
WHERE a2.IDSpecies = a.IDSpecies
AND a2.Size < a.Size
);
So you start with a simple select, then use NOT EXISTS to remove any animals, where a smaller animal exists in the same species.
Since MySQL will optimize LEFT JOIN/IS NULL better than NOT EXISTS, then the better way of writing the query in MySQL is:
SELECT a.Name AS AnimalName,
a.Size,
s.Name AS SpeciesName
FROM Animal AS a
INNER JOIN Species AS s
ON s.ID = a.IDSpecies
LEFT JOIN Animal AS a2
ON a2.IDSpecies = a.IDSpecies
AND a2.Size < a.Size
WHERE a2.ID IS NULL;
The concept is exactly the same as NOT EXISTS, but does not require a correlated subquery.

Following the simple example of Group By :
SELECT C.CountryName Country, SN.StateName, COUNT(CN.ID) CityCount
FROM Table_StatesName SN
JOIN Table_Countries C ON C.ID = SN.CountryID
JOIN Table_CityName CN ON CN.StateID = SN.ID
GROUP BY C.CountryName, SN.StateName ORDER BY SN.StateName
OUTPUT :
Country StateName CityCount
Australia Australian Capital Territory 219
Australia New South Wales 2250
Australia Northern Territory 218
Australia Queensland 2250
Australia South Australia 1501
Australia Tasmania 613
Australia Victoria 2250

Get all rows with size = smallest size. Use a correlated sub-quert to find that smallest size:
SELECT *
FROM animal a
LEFT JOIN species s ON s.idSpecies = a.idAnimal
where a.size = (select min(a2.size)
from animal a2
LEFT JOIN species s2 ON ss.idSpecies = as.idAnimal
where s.id = s2.id)

Related

How to join few table and connect id with name column from another table

I have these tables with the following column names:
players:
id first_name last_name age position salary hire_date skills_data_id team_id
Skills:
id, dribbling, pace, passing, shooting, speed, strength
towns:
id, name, country_id
teams:
id name established fan_base stadium_id
On this base, I have found the players with the max speed in terms of towns where their team played.
At the same time, I have to skip players that played in team ‘Devify’.
At the moment I have tried with this code, but the final result is not correct.
select max(s.speed) as `max_speed`,tt.name as `town_name`
from skills_data as s
right join players as p on s.id = p.skills_data_id
inner join teams as t on p.team_id = t.id
inner join towns as tt on p.team_id = tt.id
where t.name not like 'Devify'
group by s.id
order by max(s.speed) desc, town_name;
The result should be something like that:
max_speed town_name
97 Smolensk
92 Bromma
92 Lühua
...
NULL Zavolzh’ye
My result is:
max_speed town_name
97 Montréal-Ouest
92 Dalubian
92 Samsan
Thank you in advance.
There is a problem in your group by clause: you should be grouping by town rather than by skill id.
I am also quite suspicious about the join condition that brings in the towns (see my comment under your question): but if you are getting results for your existing query, it must be right...
Other changes to your query:
changed your right join to an inner join
used more meaningful table aliases
used an inequality condition instead of not like to exclude the unwanted team
New query:
select
max(sk.speed) as max_speed,
to.name as `town_name`
from skills_data as sk
inner join players as pl on sk.id = pl.skills_data_id
inner join teams as te on pl.team_id = te.id and te.name <> 'Devify'
inner join towns as to on pl.team_id = to.id
group by to.id, to.name
order by max_speed desc, town_name;

How can I write SQL Query?

I need a request that displays the name and surname of the clients with the smallest credit limit - among married women who do not live in Japan, Brazil or Italy.
Diagram:
This will Give all the people not in 'Japan', 'Brazil' or 'Italy') ,
Select C.Cust_first_name,C.Cust_Last_name from Customers C
Inner Join Countries C1
on C.Country_Id=C1.Country_Id
Where C1.Country_Name Not in('Japan', 'Brazil' or 'Italy')
and
C.Cust_Credit_Limit=(Select Min(Cust_Credit_Limit) From from Customers C)
If we Convert The Question to Code that will be the above code,
The Script would return the name and last name of person not in ('Japan', 'Brazil' or 'Italy') And has the lowest salary In the entire customer Base.
select top 1 * from Customers C
Inner Join
(
select MIn(cust_credit_limit) from Customers C1
Inner Join Countries CT on C1.Country_id = C1.Country_id
where CT.Country_Name Not in ('Japan', 'Brazil','Italy')
)
C2 on C2.cust_credit_limit = C.cust_credit_limit

battles, ships sql query

Here is a schema about battleships and the battles they fought in:
Ships(name, yearLaunched, country, numGuns, gunSize, displacement)
Battles(ship, battleName, result)
A typical Ships tuple would be:
('New Jersey', 1943, 'USA', 9, 16, 46000)
which means that the battleship New Jersey was launched in 1943; it belonged to the USA, carried 9 guns of size 16-inch (bore, or inside diameter of the barrel), and weighted (displaced, in nautical terms) 46,000 tons. A typical tuple for Battles is:
('Hood', 'North Atlantic', 'sunk')
That is, H.M.S. Hood was sunk in the battle of the North Atlantic. The other possible results are 'ok' and 'damaged'
Question: List all the pairs of countries that fought each other in battles. List each pair only once, and list them with the country that comes first in alphabetical order first
Answer: I wrote this:
SELECT
a.country, b.country
FROM
ships a, ships b, battles b1, battles b2
WHERE
name = ship
and b1.battleName = b2.battleName
and a.country > b.country
But it says ambiguous column name. How do I resolve it? Thanks in advance
Well, name = ship is the problem. name could be from a or b, and ship from b1 or b2
you could do something like that :
select distinct s1.country, s2.country
from ships s1
inner join Battles b1 on b.ship = s1.name
inner join Battles b2 on b2.ship <> s1.name and b2.battleName = b1.battleName
inner join ships s2 on s2.name = b2.ship and s2.country < s1.country
Could you try a nested query getting a table of winners and losers and then joining them on the battle name?
SELECT
WINNER.country as Country1
,LOSER.country as Country2
FROM
(
SELECT DISTINCT country, battleName
FROM Battles
INNER JOIN Ships ON Battles.ship = Ships.name
WHERE Battles.Result = 1
) AS WINNER
INNER JOIN
(
SELECT DISTINCT country, battleName
FROM Battles
INNER JOIN Ships ON Battles.ship = Ships.name
WHERE Battles.Result = 0
) AS LOSER
ON WINNER.battlename = LOSER.battlename
ORDER BY WINNER.country
try this, i think it's concise:
SELECT DISTINCT s1.country, s2.country
FROM ships s1, ships s2, battles b1, battles b2 -- i just list all tables here but you can use the left join etc.
WHERE s1.name=b1.ship AND s2.name=b2.ship AND b1.battleName=b2.battleName -- for the tables to join each other
AND s1.country < s2.country --key condition
ORDER BY s1.country ASC

Trying to find all the cities that there is not a direct flight to from a city (PostgreSQL)

I'm trying to write a query that determines which cities I can't fly to directly from a city, say London. Given the schema:
cities:
| c_id | city_name |
flights:
| f_id | departure_city_id | destination_city_id |
currently my query returns the opposite, i.e. it returns the cities for which there is a direct flight from London
SELECT c2.city_name as "City"
FROM flights AS f
JOIN cities AS c2 ON f.destination_city_id != c2.c_id
JOIN cities AS c ON c.c_id = c.c_id
WHERE c.city_name = 'London'
AND c.c_id != c2.c_id
AND f.departure_city_id = c.c_id;
I would have thought it would be easy to change it to get what I want.
I thought changing the third line to
JOIN cities AS c2 ON f.destination_city_id = c2.c_id
Would have done the trick but it didn't. Any help?
cities I can't fly to directly from a city, say London.
Meaning one can fly there, just not directly from London. So JOIN (not LEFT JOIN) city to flight via destination_city_id:
SELECT DISTINCT c.city_name
FROM cities c
JOIN flights f ON f.destination_city_id = c.c_id
JOIN cities c2 ON c2.c_id = f.departure_city_id
WHERE c2.city_name <> 'London';
Then I only have to exclude flights originating from London, apply DISTINCT to get unique city names and we are done.
A more sophisticated interpretation of this question would be:
"Cities you can fly to from London, just not directly"
But since this looks like basic homework I don't assume they'd expect a recursive query from you.
Try something like:
SELECT *
FROM cities c
WHERE c.c_id NOT IN
(SELECT f.destination_city_id
FROM flights f
JOIN cities c2 ON f.departure_city_id = c.c_id
WHERE c2.city_name = 'London')

LEFT JOIN confusion -- I need to retrieve a student count

I have four tables:
students
classes
teachers
teacher_assignments
classes and teachers has a many-to-many relationship and so teacher_assignments acts as the xref table (with fields teacher_id and class_id).
Each student in students has a class_id (many-to-one -- many students to one class).
I should also mention that teacher_assignments has an active column (BOOL) which indicates whether that assignment is currently active
What I want to do:
I want to retrieve the following:
class_name -- a concat of its level and sub_level, e.g. 3 and A
teacher_names -- the names of the teachers currently assigned to that class
student_count -- a count of the students in each class
At first, I tried retrieving just the class_name and teacher_names, like so:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names
FROM
teacher_assignments
LEFT JOIN teachers
ON teachers.id = teacher_assignments.teacher_id
AND teacher_assignments.active = TRUE
LEFT JOIN classes
ON classes.id = teacher_assignments.class_id
GROUP BY classes.id
This works fine and outputs:
class_name | teacher_names
--------------------------------------
1A | NULL
2A | John, Sam
3B | Sam, Sarah
(Class 1A has no teachers currently, and so the NULL is expected)
... BUT, now I have no idea how to work the student_count into this.
My question:
How exactly should the students table be joined with the others in the above query so I can produce a student_count column?
Use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COUNT(s.id) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Column aliases can be referenced in the GROUP BY when using MySQL, otherwise you'd have to duplicate the logic that produces the class_name column value. This is also the column to GROUP on, as GROUP_CONCAT and COUNT are aggregate functions.
To get zero as the count value, you might need to use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COALESCE(COUNT(s.id), 0) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Just thinking off the top of my head...
Join classes and students tables to get the student count...
Instead of doing a left join on classes in your above query, you will do a left join with the result from #1 (essentially an inner join between classes and students tables) that allows you to pull the student count.
I dont think I would use a join but instead would use an inline column select on student like this:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
( SELECT COUNT(*) FROM students WHERE students.class_id = classes.id ) AS student_count
FROM ...