SQL ranking multiple groups - mysql

For a project I was asked to design a DB for a small Olympic games. This is the ER diagram I made for the DB.
For one of the required queries, I need to list the sport, event, name, country, and result(medal) for everyone. So far the only problem I am having is ranking(assigning GOLD, SILVER, or BRONZE) each of the results based only on others in the same event.
My query so far:
SELECT cy.countryname COUNTRY,
c.firstname || ' ' || c.lastname "COMPETITOR",
s.sportname,
e.eventname EVENT,
rs.result
FROM COUNTRY cy
JOIN COMPETITOR c ON cy.COUNTRYID = c.COUNTRYID
JOIN RESULT rs ON c.competitorid = rs.competitorid
JOIN EVENT e ON rs.eventid = e.eventid
JOIN SPORT s ON e.sportid = s.sportid
ORDER BY e.eventname, rs.result;
Produces:
UNITED STATES NORRIS HOLMWOOD SWIMMING 100 METER BUTTERFLY 49.82
ITALY LEANDRO ROCCO SWIMMING 100 METER BUTTERFLY 50.65
ARGENTINA ORFEO SILVA SWIMMING 100 METER BUTTERFLY 50.69
IRAN HAMUND NAMAD SWIMMING 100 METER BUTTERFLY 51.2
CHINA MU KWOK SWIMMING 100 METER BUTTERFLY 51.32
RUSSIA MITRODFAN KRUPIN SWIMMING 100 METER BUTTERFLY 52.01
ARGENTINA NICOLAO VARELA TRACK AND FIELD 100 METER DASH 9.76
ITALY STEFANO PAVONI TRACK AND FIELD 100 METER DASH 9.98
UNITED STATES ROBBY TURNBULL TRACK AND FIELD 100 METER DASH 10.1
RUSSIA IRINEY POLZIN TRACK AND FIELD 100 METER DASH 10.35
CHINA TU JIANG TRACK AND FIELD 100 METER DASH 10.54
IRAN SAKHR NAGI TRACK AND FIELD 100 METER DASH 10.56
UNITED STATES SCARLETT NOWELL SWIMMING 200 METER BACKSTROKE 116.32
IRAN MALIKA NEJEM SWIMMING 200 METER BACKSTROKE 116.88
etc...
How would I go about assigning each competitor a GOLD, SILVER, BRONZE, or NO MEDAL based only on the other results in the same event?

use two variables
SELECT cy.countryname COUNTRY, c.firstname || ' ' || c.lastname "COMPETITOR", s.sportname, e.eventname EVENT, rs.result,
if(#curGame !=s.sportname,#curRank :=0,#curRank) AS setrank, #curRank := #curRank + 1 as rank,#curGame :=s.sportname as game_name
FROM COUNTRY cy
JOIN COMPETITOR c ON cy.COUNTRYID = c.COUNTRYID
JOIN RESULT rs ON c.competitorid = rs.competitorid
JOIN EVENT e ON rs.eventid = e.eventid
JOIN SPORT s ON e.sportid = s.sportid,(SELECT #curRank := 0,#curGame := "") r
ORDER BY e.eventname, rs.result;

Related

How to order by from third table (related to Max(), join)

Let me explain the question with the following example: (long but easy to understand)
This is how my MySQL database looks like:
table name: general_info
Movie_ID movie_title
1 Iron Man
2 Superman
3 Batman
table name: cast
Movie_ID Cast_Name
1 Robert Downey Jr.
1 Gwyneth Paltrow
2 Henry Cavill
2 Amy Adams
3 Christian Bale
3 Heath Ledger
Table name production_companies
Movie_ID Production_name
1 Marvel
1 Paramount
2 Legendary Pictures
2 DC Entertainment
3 Snycopy
table name user_cast_preference
user_id user_cast_name user_cast_rating
1 Robert Downey Jr. 95
1 Gwyneth Paltrow 45
1 Christian Bale 80
1 Heath Ledger 90
table name user_production_preference
user_id user_production_name user_production_rating
1 Marvel 85
1 Paraamount 70
1 Syncopy 65
Now, I am able to fetch all movies that are in user's preferred cast + preferred production company, using this query
select general_info.movie_id,movie_title from general_info
inner join cast on general_info.movie_id = cast.movie_id
inner join production_companies on general_info.movie_id = production_companies.movie_id
where cast.cast_name in (select user_cast_name from user_cast_preference)
or production_companies.production_companie_name in (select user_production_name from user_production_preference)
group by movie_title
Current Result:
movie_id moive_title
3 Batman
1 Iron Man
Only batman and Ironman got fetched because at least one of the actor or production company was involved in it (which is also in user's preferred list)
Till now everything is just fine. But I want to do this:
I want to order movies by this algorithem.
I will compare all fetched movies with the given rating in my tables and order them by Top to bottom.
In my case, let's compare Batman and Iron Man.
This is how, I decided to compare.
Take the top rated cast from Ironman + Top rated production company from iron man
Take the top rated cast from Batman + Top rated production company from batman
that is:
95 (iron man) + 85 (marvel) = 180
90 (heath ledger) + 65 (syncopy) = 155
Now Iron man has more rating than Batman, so expected result will be:
movie_id moive_title total_rating
1 Iron Man 180
3 BatMan 155
I hope, I made myself clear.
You want all movies where the user either have an actor or a company (or both) in their preferences. Then you want to sort by the sum of top preferences.
I'd look up the maximum points of user-rated casts and maximum points of user-rated companies per movie first and then outer join these aggregation results to the movie table:
select gi.*
from general_info
left join
(
select c.movie_id, max(ucp.user_cast_rating) as points
from cast c
join user_cast_preference ucp on ucp.user_cast_name = c.cast_name
where ucp.user_id = 1
group by c.movie_id
) p1 on p1.movie_id = gi.movie_id
left join
(
select pc.movie_id, max(upp.user_cast_rating) as points
from production_companies pc
join user_production_preference upp on upp.user_cast_name = pc.cast_name
where upp.user_id = 1
group by pc.movie_id
) p2 on p1.movie_id = gi.movie_id
where p1.points > 0 or p2.points > 0
order by coalesce(p1.points, 0) + coalesce(p2.points, 0) desc;
Not sure where you are getting "production_companies.production_companie_name" in your original code
SELECT general_info.Movie_ID,
general_info.movie_title,
MAX(user_cast_preference.user_cast_rating + user_production_preference.user_production_rating) AS total_rating
FROM ((((general_info
INNER JOIN CAST ON general_info.Movie_ID = cast.Movie_ID)
INNER JOIN production_companies ON cast.Movie_ID = production_companies.Movie_ID)
INNER JOIN user_production_preference ON production_companies.Production_name = user_production_preference.user_production_name)
INNER JOIN user_cast_preference ON cast.Cast_Name = user_cast_preference.user_cast_name)
GROUP BY movie_title
ORDER BY total_rating DESC;

MySQL having three joins that needs form in one join

i need to join tables to see which product is cheaper in terms of their continent. i have provided more detail below.
I have the following four tables:
country Product1 Product2 Product3
Austria 0.01 0.011 0.018
Cuba 0.15 0.015 0.012
Angola 0.17 0.017 0.019
Benin 0.84 0.015 0.025
Belize 0.24 0.019 0.018
where table name is PriceComparison
Code name continent_code
AD Andorra EU
AF Afghanistan AS
AI Anguilla NA
AL Albania EU
AO Angola AF
table name is countries
Id Name countrycode
1 Albania AL
2 Algeria DZ
3 Andorra AD
4 Angola AO
5 Austria AT
table name is CountryList
code name
AF Africa
AN Antarctica
AS Asia
EU Europe
NA North America
OC Ocenia
SA South America
table name is continents
Code name continent_code
AD Andorra EU
AF Afghanistan AS
AI Anguilla NA
AL Albania EU
AO Angola AF
table name is countries
Id Name countrycode
1 Albania AL
2 Algeria DZ
3 Andorra AD
4 Angola AO
5 Austria AT
table name is CountryList
code name
AF Africa
AN Antarctica
AS Asia
EU Europe
NA North America
OC Ocenia
SA South America
table name is continents
I need to update my PriceComparison with all the other tables i have. The objective here is to see which continent we are cheaper in terms of our products. Product1 is my product. Product1's price needs to be compared with product2's price and again product1's price needs to be compared to product3's price in terms of where my product is cheaper per continent.
Can someone assist with this query?
Tried so far:
select countries.continent_code,
continents.code,
CountryList.countrycode,
countries.code,
PriceComaprison.country,
CountryList.name
from CountryList as CL
inner join countries
on countries.continent_code = continents.code
inner join CL
on CL.countrycode = countries.code
inner join PriceComparison
on PriceComparison.country = CL.name
sample data:
countries: code (AS, AI,AO, BB,CA) name (the actual country name - eg.America Samao) continent_code (OC,NA,AN - the actual continent_code of the country)
CountryList table: id Name countrycode
continents: code name
Based on what you explained in the comments, this could be close to what you need:
select con.code continenct_code, con.name as continent, product1, Product2, product3
from PriceComparison p join countries c on p.country = c.name
join continents con on con.code = c.continent_code
where Product1 < Product2 and Product1 < Product3
However, the sample data you have provided is not correct. As it is already mentioned in the comments to your question, there are countries in the PriceComparison table that do not exist in the Countries table-if you run this with your real data I would assume this should work.
I did modify a little your data and you can check it here.
This query will show the average price of each product per continent, if you want the smallest price per continent use MIN() instead of AVG()
SELECT con.code,
AVG(p.product1),
AVG(p.product2),
AVG(p.product3)
FROM PriceComparison p
JOIN countries c ON p.country = c.name
JOIN continents con ON con.code = c.continent_code
GROUP BY con.code

How to multiple concatenate values from multiple relation tables in a single mysql query

I have a big issue for my "traveling offer" project, working 99% OK, but not 100%.
I have main table with offers, where each offer can have set multiple department cities as well as multiple destination cities (this is reduced sample with reduced columns).
For example, I'm offering some travels from England, where department cities can be from London, Leeds and Manchester. Destination cities are Prague, Bratislava, Budapest and Belgrade.
Offer 1 is set to department cities London or Leeds, and destinations are Prague and Budapest.
Offer 2 is set to department city London, and destinations are Bratislava and Belgrade.
Offer 3 is set to department city Manchester or Leeds, and destination is Prague.
Table offers
----------------------------
id title price
----------------------------
1 Offer 1 title 300 Eur
2 Offer 2 title 250 Eur
3 Offer 3 title 350 Eur
Now relation tables and city name tables
Table departments
----------------------------
id name
----------------------------
1 London
2 Leeds
3 Manchester
relation Table rel_departments
------------------------
offer_id rel_id
------------------------
1 1
1 2
2 1
3 2
3 3
Table destinations
----------------------------
id name
----------------------------
1 Prague
2 Bratislava
3 Budapest
4 Belgrade
relation Table rel_destinations
------------------------
offer_id rel_id
------------------------
1 1
1 3
2 2
2 4
3 1
As SQL result I expect for each offer concatenated values bot for department cities and destination cities
If I search all with following sql I got OK result:
SELECT offers.*,
GROUP_CONCAT(DISTINCT DEPC.name SEPARATOR ', ') AS depCities,
GROUP_CONCAT(DISTINCT DESTC.name SEPARATOR ', ') AS destCities
FROM offers
INNER JOIN `rel_departments` ON (`rel_departments`.`offer_id` = `offers`.`id`)
INNER JOIN `departments` as DEPC ON (DEPC.`id` = `rel_departments`.`rel_id`)
INNER JOIN `rel_destinations` ON (`rel_destinations`.`offer_id` = `offers`.`id`)
INNER JOIN `destinations` as DESTC ON (DESTC.`id` = `rel_destinations`.`rel_id`)
GROUP BY offers.id
result would be okay:
---------------------------------------------------------------------
id title price depCities destCities
---------------------------------------------------------------------
1 Offer 1 title 300 Eur London, Leeds Prague, Budapest
2 Offer 2 title 250 Eur London Bratislava, Belgrade
3 Offer 3 title 350 Eur Leeds, Manchester Prague
And I need results like this whatever WHERE clause is. But, whenever I put where clause, I loose one of results in concatenation. For example, I search for all offers with Prague as a destination. If I add to the end of the sql statement:
where rel_destinations.rel_id=1
result is as following:
---------------------------------------------------------------------
id title price depCities destCities
---------------------------------------------------------------------
1 Offer 1 title 300 Eur London, Leeds Prague
3 Offer 3 title 350 Eur Leeds, Manchester Prague
If you can notice, there is no Budapest in offer 1. What to do to get complete concatenation string... Not that WHERE clause can be more complex, i.e. to search for department city or any other parameter.
Any help is appreciated :)
You need to use a different join with rel_destinations to get the offers with Prague as a destination. Join this with your original query.
SELECT offers.*,
GROUP_CONCAT(DISTINCT DEPC.name SEPARATOR ', ') AS depCities,
GROUP_CONCAT(DISTINCT DESTC.name SEPARATOR ', ') AS destCities
FROM offers
INNER JOIN `rel_departments` ON (`rel_departments`.`offer_id` = `offers`.`id`)
INNER JOIN `departments` as DEPC ON (DEPC.`id` = `rel_departments`.`rel_id`)
INNER JOIN `rel_destinations` ON (`rel_destinations`.`offer_id` = `offers`.`id`)
INNER JOIN `destinations` as DESTC ON (DESTC.`id` = `rel_destinations`.`rel_id`)
INNER JOIN rel_destinations AS d1 ON d1.offer_id = offers.id
WHERE d1.rel_id = 1
GROUP BY offers.id
DEMO

Tricky SQL Query about COUNT

Here are two table with schema:
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'.
And here is the tricky query:
For the battle of Surigao Strait, for each country engaged in that battle (had one or more battleships participating), give the number of its battleships that were sunk. Note: this question is very tricky. In particular, you need to deal with the (historical) case that a country engaged in the battle but did not have any ships sunk.
What I've tried so far.
SELECT country,COUNT(name)
FROM ships RIGHT JOIN battles
ON ships.name=battles.ship
WHERE battleName='Battle1' AND result='sunk'
GROUP BY country
country engaged in that battle (had one or more battleships
participating)
It means has at least one record in the battles where the battleName is 'Surigao Strait'. It means an INNER JOIN.
give the number of its battleships that were sunk
It is a conditional count, and here is the "trick". You can use a SUM with a condition end then you can have the count of the ships sunk.
SELECT country,SUM(CASE WHEN result = 'sunk' THEN 1 ELSE 0 END) AS TotalShipSunk
FROM ships
INNER JOIN battles
ON ships.name=battles.ship
WHERE battleName='Surigao Strait'
GROUP BY country
You need to join the tables and I guess the relationship with is other is by name of the ship. try this one,
SELECT a.country,
SUM(CASE WHEN b.result = 'SUNK' THEN 1 ELSE 0 END) totalSunkShips,
SUM(CASE WHEN b.result = 'OK' THEN 1 ELSE 0 END) totalUNSunkShips
FROM battles a
INNER JOIN ships b
ON a.ship = b.name
WHERE b.battleName = 'Surigao Strait'
GROUP BY a.country
Sample Ships Record
name yearLaunched country numGuns gunSize displacement
New Jersey 1943 USA 9 16 46000
Surigao Strait Battle ship USA 1800 USA 9 16 5000
Surigao Strait Battle ship USA 1800 USA 9 16 5000
Surigao Strait Battle ship UK 1800 UK 7 16 27000
Surigao Strait Battle ship France 1800 France 9 16 5000
Surigao Strait Battle ship Urugaya 1800 Urugaya 7 16 27000
New Jersey 1943 UK 9 16 46000
Sample Battle Record
ship battleName result
Hood North Atlantic sunk
Surigao Strait Battle ship USA Surigao Strait sunk
Surigao Strait Battle ship UK Surigao Strait damaged
Surigao Strait Battle ship France Surigao Strait ok
Surigao Strait Battle ship Urugaya Surigao Strait sunk
Is this what you are looking for
SELECT s.country,Count(s.name) AS Cnt
FROM ships s
JOIN (SELECT *
FROM Battles
WHERE battleName='Surigao Strait' AND result='sunk' )b
ON s.name=b.ship
GROUP BY s.country
Result
country Cnt
Urugaya 1
USA 2

How can I write a query as a value in a row?

I have two tables on MySQL (using phpMyAdmin), looking like the following:
Table 1:
Country Total Minutes
USA 100
USA 90
Canada 60
Mexico 80
UK 90
France 70
France 10
Germany 10
In Table 2, what I need to do is the following:
Region Total Minutes
North America USA+USA+Canada+Mexico Mins
Europe UK+France+France+Germany Mins
Is there a way to have a row be the result of a query?
You either need a region column in table 1:
SELECT region, SUM(`Total Minutes`)
FROM timespent
GROUP BY region;
Or a separate region <-> country table:
SELECT region, SUM(`Total Minutes`)
FROM myregions r
INNER JOIN timespent t USING (country)
GROUP BY r.region;
The regions table would look like this:
region | country
--------------+--------
North America | USA
North America | Mexico
If you can't change anything in your database, look at Andomar's solution :)
You could translate the countries to regions in a subquery. The outer query can then group by on region:
select Region
, sum(TotalMinutes) as TotalMinutes
from (
select case country
when 'USA' then 'North America'
when 'France' then 'Europe'
end as Region
, TotalMinutes
from YourTable
) as SubQueryAlias
group by
Region